##// END OF EJS Templates
Small fix and cleanup for exit/quit command filtering.
Fernando Perez -
Show More
@@ -1,515 +1,525 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 json
18 18 import os
19 19 import sys
20 20
21 21 # Our own packages
22 22 import IPython.utils.io
23 23
24 24 from IPython.utils.pickleshare import PickleShareDB
25 25 from IPython.utils.io import ask_yes_no
26 26 from IPython.utils.warn import warn
27 27
28 28 #-----------------------------------------------------------------------------
29 29 # Classes and functions
30 30 #-----------------------------------------------------------------------------
31 31
32 32 class HistoryManager(object):
33 33 """A class to organize all history-related functionality in one place.
34 34 """
35 35 # Public interface
36 36
37 37 # An instance of the IPython shell we are attached to
38 38 shell = None
39 39 # A list to hold processed history
40 40 input_hist_parsed = None
41 41 # A list to hold raw history (as typed by user)
42 42 input_hist_raw = None
43 43 # A list of directories visited during session
44 44 dir_hist = None
45 45 # A dict of output history, keyed with ints from the shell's execution count
46 46 output_hist = None
47 47 # String with path to the history file
48 48 hist_file = None
49 49 # PickleShareDB instance holding the raw data for the shadow history
50 50 shadow_db = None
51 51 # ShadowHist instance with the actual shadow history
52 52 shadow_hist = None
53 53
54 54 # Private interface
55 55 # Variables used to store the three last inputs from the user. On each new
56 56 # history update, we populate the user's namespace with these, shifted as
57 57 # necessary.
58 58 _i00, _i, _ii, _iii = '','','',''
59 59
60 # A set with all forms of the exit command, so that we don't store them in
61 # the history (it's annoying to rewind the first entry and land on an exit
62 # call).
63 _exit_commands = None
64
60 65 def __init__(self, shell):
61 66 """Create a new history manager associated with a shell instance.
62 67 """
63 68 # We need a pointer back to the shell for various tasks.
64 69 self.shell = shell
65 70
66 71 # List of input with multi-line handling.
67 72 self.input_hist_parsed = []
68 73 # This one will hold the 'raw' input history, without any
69 74 # pre-processing. This will allow users to retrieve the input just as
70 75 # it was exactly typed in by the user, with %hist -r.
71 76 self.input_hist_raw = []
72 77
73 78 # list of visited directories
74 79 try:
75 80 self.dir_hist = [os.getcwd()]
76 81 except OSError:
77 82 self.dir_hist = []
78 83
79 84 # dict of output history
80 85 self.output_hist = {}
81 86
82 87 # Now the history file
83 88 if shell.profile:
84 89 histfname = 'history-%s' % shell.profile
85 90 else:
86 91 histfname = 'history'
87 92 self.hist_file = os.path.join(shell.ipython_dir, histfname + '.json')
88 93
89 94 # Objects related to shadow history management
90 95 self._init_shadow_hist()
91 96
92 97 self._i00, self._i, self._ii, self._iii = '','','',''
93 98
99 self._exit_commands = set(['Quit', 'quit', 'Exit', 'exit', '%Quit',
100 '%quit', '%Exit', '%exit'])
101
94 102 # Object is fully initialized, we can now call methods on it.
95 103
96 104 # Fill the history zero entry, user counter starts at 1
97 105 self.store_inputs('\n', '\n')
98 106
99 107 def _init_shadow_hist(self):
100 108 try:
101 109 self.shadow_db = PickleShareDB(os.path.join(
102 110 self.shell.ipython_dir, 'db'))
103 111 except UnicodeDecodeError:
104 112 print("Your ipython_dir can't be decoded to unicode!")
105 113 print("Please set HOME environment variable to something that")
106 114 print(r"only has ASCII characters, e.g. c:\home")
107 115 print("Now it is", self.ipython_dir)
108 116 sys.exit()
109 117 self.shadow_hist = ShadowHist(self.shadow_db, self.shell)
110 118
111 119 def populate_readline_history(self):
112 120 """Populate the readline history from the raw history.
113 121
114 122 We only store one copy of the raw history, which is persisted to a json
115 123 file on disk. The readline history is repopulated from the contents of
116 124 this file."""
117 125
118 126 try:
119 127 self.shell.readline.clear_history()
120 128 except AttributeError:
121 129 pass
122 130 else:
123 131 for h in self.input_hist_raw:
124 132 if not h.isspace():
125 133 for line in h.splitlines():
126 134 self.shell.readline.add_history(line)
127 135
128 136 def save_history(self):
129 137 """Save input history to a file (via readline library)."""
130 138 hist = dict(raw=self.input_hist_raw, #[-self.shell.history_length:],
131 139 parsed=self.input_hist_parsed) #[-self.shell.history_length:])
132 140 with open(self.hist_file,'wt') as hfile:
133 141 json.dump(hist, hfile,
134 142 sort_keys=True, indent=4)
135 143
136 144 def reload_history(self):
137 145 """Reload the input history from disk file."""
138 146
139 147 with open(self.hist_file,'rt') as hfile:
140 148 hist = json.load(hfile)
141 149 self.input_hist_parsed = hist['parsed']
142 150 self.input_hist_raw = hist['raw']
143 151 if self.shell.has_readline:
144 152 self.populate_readline_history()
145 153
146 154 def get_history(self, index=None, raw=False, output=True):
147 155 """Get the history list.
148 156
149 157 Get the input and output history.
150 158
151 159 Parameters
152 160 ----------
153 161 index : n or (n1, n2) or None
154 162 If n, then the last entries. If a tuple, then all in
155 163 range(n1, n2). If None, then all entries. Raises IndexError if
156 164 the format of index is incorrect.
157 165 raw : bool
158 166 If True, return the raw input.
159 167 output : bool
160 168 If True, then return the output as well.
161 169
162 170 Returns
163 171 -------
164 172 If output is True, then return a dict of tuples, keyed by the prompt
165 173 numbers and with values of (input, output). If output is False, then
166 174 a dict, keyed by the prompt number with the values of input. Raises
167 175 IndexError if no history is found.
168 176 """
169 177 if raw:
170 178 input_hist = self.input_hist_raw
171 179 else:
172 180 input_hist = self.input_hist_parsed
173 181 if output:
174 182 output_hist = self.output_hist
175 183 n = len(input_hist)
176 184 if index is None:
177 185 start=0; stop=n
178 186 elif isinstance(index, int):
179 187 start=n-index; stop=n
180 188 elif isinstance(index, tuple) and len(index) == 2:
181 189 start=index[0]; stop=index[1]
182 190 else:
183 191 raise IndexError('Not a valid index for the input history: %r'
184 192 % index)
185 193 hist = {}
186 194 for i in range(start, stop):
187 195 if output:
188 196 hist[i] = (input_hist[i], output_hist.get(i))
189 197 else:
190 198 hist[i] = input_hist[i]
191 199 if not hist:
192 200 raise IndexError('No history for range of indices: %r' % index)
193 201 return hist
194 202
195 203 def store_inputs(self, source, source_raw=None):
196 204 """Store source and raw input in history and create input cache
197 205 variables _i*.
198 206
199 207 Parameters
200 208 ----------
201 209 source : str
202 210 Python input.
203 211
204 212 source_raw : str, optional
205 213 If given, this is the raw input without any IPython transformations
206 214 applied to it. If not given, ``source`` is used.
207 215 """
208 216 if source_raw is None:
209 217 source_raw = source
210 # do not store quit/exit commands
211 if source_raw in ['Quit', 'quit', 'Exit', 'exit', '%Quit', '%quit', '%Exit', '%exit']:
218
219 # do not store exit/quit commands
220 if source_raw.strip() in self._exit_commands:
212 221 return
222
213 223 self.input_hist_parsed.append(source.rstrip())
214 224 self.input_hist_raw.append(source_raw.rstrip())
215 225 self.shadow_hist.add(source)
216 226
217 227 # update the auto _i variables
218 228 self._iii = self._ii
219 229 self._ii = self._i
220 230 self._i = self._i00
221 231 self._i00 = source_raw
222 232
223 233 # hackish access to user namespace to create _i1,_i2... dynamically
224 234 new_i = '_i%s' % self.shell.execution_count
225 235 to_main = {'_i': self._i,
226 236 '_ii': self._ii,
227 237 '_iii': self._iii,
228 238 new_i : self._i00 }
229 239 self.shell.user_ns.update(to_main)
230 240
231 241 def sync_inputs(self):
232 242 """Ensure raw and translated histories have same length."""
233 243 if len(self.input_hist_parsed) != len (self.input_hist_raw):
234 244 self.input_hist_raw[:] = self.input_hist_parsed
235 245
236 246 def reset(self):
237 247 """Clear all histories managed by this object."""
238 248 self.input_hist_parsed[:] = []
239 249 self.input_hist_raw[:] = []
240 250 self.output_hist.clear()
241 251 # The directory history can't be completely empty
242 252 self.dir_hist[:] = [os.getcwd()]
243 253
244 254
245 255 def magic_history(self, parameter_s = ''):
246 256 """Print input history (_i<n> variables), with most recent last.
247 257
248 258 %history -> print at most 40 inputs (some may be multi-line)\\
249 259 %history n -> print at most n inputs\\
250 260 %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\
251 261
252 262 By default, input history is printed without line numbers so it can be
253 263 directly pasted into an editor.
254 264
255 265 With -n, each input's number <n> is shown, and is accessible as the
256 266 automatically generated variable _i<n> as well as In[<n>]. Multi-line
257 267 statements are printed starting at a new line for easy copy/paste.
258 268
259 269 Options:
260 270
261 271 -n: print line numbers for each input.
262 272 This feature is only available if numbered prompts are in use.
263 273
264 274 -o: also print outputs for each input.
265 275
266 276 -p: print classic '>>>' python prompts before each input. This is useful
267 277 for making documentation, and in conjunction with -o, for producing
268 278 doctest-ready output.
269 279
270 280 -r: (default) print the 'raw' history, i.e. the actual commands you typed.
271 281
272 282 -t: print the 'translated' history, as IPython understands it. IPython
273 283 filters your input and converts it all into valid Python source before
274 284 executing it (things like magics or aliases are turned into function
275 285 calls, for example). With this option, you'll see the native history
276 286 instead of the user-entered version: '%cd /' will be seen as
277 287 'get_ipython().magic("%cd /")' instead of '%cd /'.
278 288
279 289 -g: treat the arg as a pattern to grep for in (full) history.
280 290 This includes the "shadow history" (almost all commands ever written).
281 291 Use '%hist -g' to show full shadow history (may be very long).
282 292 In shadow history, every index nuwber starts with 0.
283 293
284 294 -f FILENAME: instead of printing the output to the screen, redirect it to
285 295 the given file. The file is always overwritten, though IPython asks for
286 296 confirmation first if it already exists.
287 297 """
288 298
289 299 if not self.shell.displayhook.do_full_cache:
290 300 print('This feature is only available if numbered prompts are in use.')
291 301 return
292 302 opts,args = self.parse_options(parameter_s,'gnoptsrf:',mode='list')
293 303
294 304 # Check if output to specific file was requested.
295 305 try:
296 306 outfname = opts['f']
297 307 except KeyError:
298 308 outfile = IPython.utils.io.Term.cout # default
299 309 # We don't want to close stdout at the end!
300 310 close_at_end = False
301 311 else:
302 312 if os.path.exists(outfname):
303 313 if not ask_yes_no("File %r exists. Overwrite?" % outfname):
304 314 print('Aborting.')
305 315 return
306 316
307 317 outfile = open(outfname,'w')
308 318 close_at_end = True
309 319
310 320 if 't' in opts:
311 321 input_hist = self.shell.history_manager.input_hist_parsed
312 322 elif 'r' in opts:
313 323 input_hist = self.shell.history_manager.input_hist_raw
314 324 else:
315 325 # Raw history is the default
316 326 input_hist = self.shell.history_manager.input_hist_raw
317 327
318 328 default_length = 40
319 329 pattern = None
320 330 if 'g' in opts:
321 331 init = 1
322 332 final = len(input_hist)
323 333 parts = parameter_s.split(None, 1)
324 334 if len(parts) == 1:
325 335 parts += '*'
326 336 head, pattern = parts
327 337 pattern = "*" + pattern + "*"
328 338 elif len(args) == 0:
329 339 final = len(input_hist)-1
330 340 init = max(1,final-default_length)
331 341 elif len(args) == 1:
332 342 final = len(input_hist)
333 343 init = max(1, final-int(args[0]))
334 344 elif len(args) == 2:
335 345 init, final = map(int, args)
336 346 else:
337 347 warn('%hist takes 0, 1 or 2 arguments separated by spaces.')
338 348 print(self.magic_hist.__doc__, file=IPython.utils.io.Term.cout)
339 349 return
340 350
341 351 width = len(str(final))
342 352 line_sep = ['','\n']
343 353 print_nums = 'n' in opts
344 354 print_outputs = 'o' in opts
345 355 pyprompts = 'p' in opts
346 356
347 357 found = False
348 358 if pattern is not None:
349 359 sh = self.shell.history_manager.shadowhist.all()
350 360 for idx, s in sh:
351 361 if fnmatch.fnmatch(s, pattern):
352 362 print("0%d: %s" %(idx, s.expandtabs(4)), file=outfile)
353 363 found = True
354 364
355 365 if found:
356 366 print("===", file=outfile)
357 367 print("shadow history ends, fetch by %rep <number> (must start with 0)",
358 368 file=outfile)
359 369 print("=== start of normal history ===", file=outfile)
360 370
361 371 for in_num in range(init, final):
362 372 # Print user history with tabs expanded to 4 spaces. The GUI clients
363 373 # use hard tabs for easier usability in auto-indented code, but we want
364 374 # to produce PEP-8 compliant history for safe pasting into an editor.
365 375 inline = input_hist[in_num].expandtabs(4)
366 376
367 377 if pattern is not None and not fnmatch.fnmatch(inline, pattern):
368 378 continue
369 379
370 380 multiline = int(inline.count('\n') > 1)
371 381 if print_nums:
372 382 print('%s:%s' % (str(in_num).ljust(width), line_sep[multiline]),
373 383 file=outfile)
374 384 if pyprompts:
375 385 print('>>>', file=outfile)
376 386 if multiline:
377 387 lines = inline.splitlines()
378 388 print('\n... '.join(lines), file=outfile)
379 389 print('... ', file=outfile)
380 390 else:
381 391 print(inline, end='', file=outfile)
382 392 else:
383 393 print(inline, end='', file=outfile)
384 394 if print_outputs:
385 395 output = self.shell.history_manager.output_hist.get(in_num)
386 396 if output is not None:
387 397 print(repr(output), file=outfile)
388 398
389 399 if close_at_end:
390 400 outfile.close()
391 401
392 402
393 403 def magic_hist(self, parameter_s=''):
394 404 """Alternate name for %history."""
395 405 return self.magic_history(parameter_s)
396 406
397 407
398 408 def rep_f(self, arg):
399 409 r""" Repeat a command, or get command to input line for editing
400 410
401 411 - %rep (no arguments):
402 412
403 413 Place a string version of last computation result (stored in the special '_'
404 414 variable) to the next input prompt. Allows you to create elaborate command
405 415 lines without using copy-paste::
406 416
407 417 $ l = ["hei", "vaan"]
408 418 $ "".join(l)
409 419 ==> heivaan
410 420 $ %rep
411 421 $ heivaan_ <== cursor blinking
412 422
413 423 %rep 45
414 424
415 425 Place history line 45 to next input prompt. Use %hist to find out the
416 426 number.
417 427
418 428 %rep 1-4 6-7 3
419 429
420 430 Repeat the specified lines immediately. Input slice syntax is the same as
421 431 in %macro and %save.
422 432
423 433 %rep foo
424 434
425 435 Place the most recent line that has the substring "foo" to next input.
426 436 (e.g. 'svn ci -m foobar').
427 437 """
428 438
429 439 opts,args = self.parse_options(arg,'',mode='list')
430 440 if not args:
431 441 self.set_next_input(str(self.shell.user_ns["_"]))
432 442 return
433 443
434 444 if len(args) == 1 and not '-' in args[0]:
435 445 arg = args[0]
436 446 if len(arg) > 1 and arg.startswith('0'):
437 447 # get from shadow hist
438 448 num = int(arg[1:])
439 449 line = self.shell.shadowhist.get(num)
440 450 self.set_next_input(str(line))
441 451 return
442 452 try:
443 453 num = int(args[0])
444 454 self.set_next_input(str(self.shell.input_hist_raw[num]).rstrip())
445 455 return
446 456 except ValueError:
447 457 pass
448 458
449 459 for h in reversed(self.shell.input_hist_raw):
450 460 if 'rep' in h:
451 461 continue
452 462 if fnmatch.fnmatch(h,'*' + arg + '*'):
453 463 self.set_next_input(str(h).rstrip())
454 464 return
455 465
456 466 try:
457 467 lines = self.extract_input_slices(args, True)
458 468 print("lines", lines)
459 469 self.run_cell(lines)
460 470 except ValueError:
461 471 print("Not found in recent history:", args)
462 472
463 473
464 474 _sentinel = object()
465 475
466 476 class ShadowHist(object):
467 477 def __init__(self, db, shell):
468 478 # cmd => idx mapping
469 479 self.curidx = 0
470 480 self.db = db
471 481 self.disabled = False
472 482 self.shell = shell
473 483
474 484 def inc_idx(self):
475 485 idx = self.db.get('shadowhist_idx', 1)
476 486 self.db['shadowhist_idx'] = idx + 1
477 487 return idx
478 488
479 489 def add(self, ent):
480 490 if self.disabled:
481 491 return
482 492 try:
483 493 old = self.db.hget('shadowhist', ent, _sentinel)
484 494 if old is not _sentinel:
485 495 return
486 496 newidx = self.inc_idx()
487 497 #print("new", newidx) # dbg
488 498 self.db.hset('shadowhist',ent, newidx)
489 499 except:
490 500 self.shell.showtraceback()
491 501 print("WARNING: disabling shadow history")
492 502 self.disabled = True
493 503
494 504 def all(self):
495 505 d = self.db.hdict('shadowhist')
496 506 items = [(i,s) for (s,i) in d.iteritems()]
497 507 items.sort()
498 508 return items
499 509
500 510 def get(self, idx):
501 511 all = self.all()
502 512
503 513 for k, v in all:
504 514 if k == idx:
505 515 return v
506 516
507 517
508 518 def init_ipython(ip):
509 519 ip.define_magic("rep",rep_f)
510 520 ip.define_magic("hist",magic_hist)
511 521 ip.define_magic("history",magic_history)
512 522
513 523 # XXX - ipy_completers are in quarantine, need to be updated to new apis
514 524 #import ipy_completers
515 525 #ipy_completers.quick_completer('%hist' ,'-g -t -r -n')
General Comments 0
You need to be logged in to leave comments. Login now