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