##// END OF EJS Templates
Remove suspicious double quote
Takafumi Arakaki -
Show More
@@ -1,760 +1,760 b''
1 1 """ History related magics and functionality """
2 2 #-----------------------------------------------------------------------------
3 3 # Copyright (C) 2010-2011 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 atexit
17 17 import datetime
18 18 import os
19 19 import re
20 20 try:
21 21 import sqlite3
22 22 except ImportError:
23 23 sqlite3 = None
24 24 import threading
25 25
26 26 # Our own packages
27 27 from IPython.config.configurable import Configurable
28 28 from IPython.external.decorator import decorator
29 29 from IPython.utils.path import locate_profile
30 30 from IPython.utils.traitlets import (
31 31 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
32 32 )
33 33 from IPython.utils.warn import warn
34 34
35 35 #-----------------------------------------------------------------------------
36 36 # Classes and functions
37 37 #-----------------------------------------------------------------------------
38 38
39 39 class DummyDB(object):
40 40 """Dummy DB that will act as a black hole for history.
41 41
42 42 Only used in the absence of sqlite"""
43 43 def execute(*args, **kwargs):
44 44 return []
45 45
46 46 def commit(self, *args, **kwargs):
47 47 pass
48 48
49 49 def __enter__(self, *args, **kwargs):
50 50 pass
51 51
52 52 def __exit__(self, *args, **kwargs):
53 53 pass
54 54
55 55
56 56 @decorator
57 57 def needs_sqlite(f, self, *a, **kw):
58 58 """return an empty list in the absence of sqlite"""
59 59 if sqlite3 is None or not self.enabled:
60 60 return []
61 61 else:
62 62 return f(self, *a, **kw)
63 63
64 64
65 65 class HistoryAccessor(Configurable):
66 66 """Access the history database without adding to it.
67 67
68 68 This is intended for use by standalone history tools. IPython shells use
69 69 HistoryManager, below, which is a subclass of this."""
70 70
71 71 # String holding the path to the history file
72 72 hist_file = Unicode(config=True,
73 73 help="""Path to file to use for SQLite history database.
74 74
75 75 By default, IPython will put the history database in the IPython
76 76 profile directory. If you would rather share one history among
77 77 profiles, you can set this value in each, so that they are consistent.
78 78
79 79 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
80 80 mounts. If you see IPython hanging, try setting this to something on a
81 81 local disk, e.g::
82 82
83 83 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
84 84
85 85 """)
86 86
87 87 enabled = Bool(True, config=True,
88 88 help="""enable the SQLite history
89 89
90 90 set enabled=False to disable the SQLite history,
91 91 in which case there will be no stored history, no SQLite connection,
92 92 and no background saving thread. This may be necessary in some
93 93 threaded environments where IPython is embedded.
94 94 """
95 95 )
96 96
97 97 connection_options = Dict(config=True,
98 98 help="""Options for configuring the SQLite connection
99 99
100 100 These options are passed as keyword args to sqlite3.connect
101 101 when establishing database conenctions.
102 102 """
103 103 )
104 104
105 105 # The SQLite database
106 106 db = Any()
107 107 def _db_changed(self, name, old, new):
108 108 """validate the db, since it can be an Instance of two different types"""
109 109 connection_types = (DummyDB,)
110 110 if sqlite3 is not None:
111 111 connection_types = (DummyDB, sqlite3.Connection)
112 112 if not isinstance(new, connection_types):
113 113 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
114 114 (self.__class__.__name__, new)
115 115 raise TraitError(msg)
116 116
117 117 def __init__(self, profile='default', hist_file=u'', config=None, **traits):
118 118 """Create a new history accessor.
119 119
120 120 Parameters
121 121 ----------
122 122 profile : str
123 123 The name of the profile from which to open history.
124 124 hist_file : str
125 125 Path to an SQLite history database stored by IPython. If specified,
126 126 hist_file overrides profile.
127 127 config :
128 128 Config object. hist_file can also be set through this.
129 129 """
130 130 # We need a pointer back to the shell for various tasks.
131 131 super(HistoryAccessor, self).__init__(config=config, **traits)
132 132 # defer setting hist_file from kwarg until after init,
133 133 # otherwise the default kwarg value would clobber any value
134 134 # set by config
135 135 if hist_file:
136 136 self.hist_file = hist_file
137 137
138 138 if self.hist_file == u'':
139 139 # No one has set the hist_file, yet.
140 140 self.hist_file = self._get_hist_file_name(profile)
141 141
142 142 if sqlite3 is None and self.enabled:
143 143 warn("IPython History requires SQLite, your history will not be saved\n")
144 144 self.enabled = False
145 145
146 146 if sqlite3 is not None:
147 147 DatabaseError = sqlite3.DatabaseError
148 148 else:
149 149 DatabaseError = Exception
150 150
151 151 try:
152 152 self.init_db()
153 153 except DatabaseError:
154 154 if os.path.isfile(self.hist_file):
155 155 # Try to move the file out of the way
156 156 base,ext = os.path.splitext(self.hist_file)
157 157 newpath = base + '-corrupt' + ext
158 158 os.rename(self.hist_file, newpath)
159 159 print("ERROR! History file wasn't a valid SQLite database.",
160 160 "It was moved to %s" % newpath, "and a new file created.")
161 161 self.init_db()
162 162 else:
163 163 # The hist_file is probably :memory: or something else.
164 164 raise
165 165
166 166 def _get_hist_file_name(self, profile='default'):
167 167 """Find the history file for the given profile name.
168 168
169 169 This is overridden by the HistoryManager subclass, to use the shell's
170 170 active profile.
171 171
172 172 Parameters
173 173 ----------
174 174 profile : str
175 175 The name of a profile which has a history file.
176 176 """
177 177 return os.path.join(locate_profile(profile), 'history.sqlite')
178 178
179 179 def init_db(self):
180 180 """Connect to the database, and create tables if necessary."""
181 181 if not self.enabled:
182 182 self.db = DummyDB()
183 183 return
184 184
185 185 # use detect_types so that timestamps return datetime objects
186 186 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
187 187 kwargs.update(self.connection_options)
188 188 self.db = sqlite3.connect(self.hist_file, **kwargs)
189 189 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
190 190 primary key autoincrement, start timestamp,
191 191 end timestamp, num_cmds integer, remark text)""")
192 192 self.db.execute("""CREATE TABLE IF NOT EXISTS history
193 193 (session integer, line integer, source text, source_raw text,
194 194 PRIMARY KEY (session, line))""")
195 195 # Output history is optional, but ensure the table's there so it can be
196 196 # enabled later.
197 197 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
198 198 (session integer, line integer, output text,
199 199 PRIMARY KEY (session, line))""")
200 200 self.db.commit()
201 201
202 202 def writeout_cache(self):
203 203 """Overridden by HistoryManager to dump the cache before certain
204 204 database lookups."""
205 205 pass
206 206
207 207 ## -------------------------------
208 208 ## Methods for retrieving history:
209 209 ## -------------------------------
210 210 def _run_sql(self, sql, params, raw=True, output=False):
211 211 """Prepares and runs an SQL query for the history database.
212 212
213 213 Parameters
214 214 ----------
215 215 sql : str
216 216 Any filtering expressions to go after SELECT ... FROM ...
217 217 params : tuple
218 218 Parameters passed to the SQL query (to replace "?")
219 219 raw, output : bool
220 220 See :meth:`get_range`
221 221
222 222 Returns
223 223 -------
224 224 Tuples as :meth:`get_range`
225 225 """
226 226 toget = 'source_raw' if raw else 'source'
227 227 sqlfrom = "history"
228 228 if output:
229 229 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
230 230 toget = "history.%s, output_history.output" % toget
231 231 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
232 232 (toget, sqlfrom) + sql, params)
233 233 if output: # Regroup into 3-tuples, and parse JSON
234 234 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
235 235 return cur
236 236
237 237 @needs_sqlite
238 238 def get_session_info(self, session=0):
239 239 """get info about a session
240 240
241 241 Parameters
242 242 ----------
243 243
244 244 session : int
245 245 Session number to retrieve. The current session is 0, and negative
246 246 numbers count back from current session, so -1 is previous session.
247 247
248 248 Returns
249 249 -------
250 250
251 251 (session_id [int], start [datetime], end [datetime], num_cmds [int],
252 252 remark [unicode])
253 253
254 254 Sessions that are running or did not exit cleanly will have `end=None`
255 255 and `num_cmds=None`.
256 256
257 257 """
258 258
259 259 if session <= 0:
260 260 session += self.session_number
261 261
262 262 query = "SELECT * from sessions where session == ?"
263 263 return self.db.execute(query, (session,)).fetchone()
264 264
265 265 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
266 266 """Get the last n lines from the history database.
267 267
268 268 Parameters
269 269 ----------
270 270 n : int
271 271 The number of lines to get
272 272 raw, output : bool
273 273 See :meth:`get_range`
274 274 include_latest : bool
275 275 If False (default), n+1 lines are fetched, and the latest one
276 276 is discarded. This is intended to be used where the function
277 277 is called by a user command, which it should not return.
278 278
279 279 Returns
280 280 -------
281 281 Tuples as :meth:`get_range`
282 282 """
283 283 self.writeout_cache()
284 284 if not include_latest:
285 285 n += 1
286 286 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
287 287 (n,), raw=raw, output=output)
288 288 if not include_latest:
289 289 return reversed(list(cur)[1:])
290 290 return reversed(list(cur))
291 291
292 292 def search(self, pattern="*", raw=True, search_raw=True,
293 293 output=False):
294 294 """Search the database using unix glob-style matching (wildcards
295 295 * and ?).
296 296
297 297 Parameters
298 298 ----------
299 299 pattern : str
300 300 The wildcarded pattern to match when searching
301 301 search_raw : bool
302 302 If True, search the raw input, otherwise, the parsed input
303 303 raw, output : bool
304 304 See :meth:`get_range`
305 305
306 306 Returns
307 307 -------
308 308 Tuples as :meth:`get_range`
309 309 """
310 310 tosearch = "source_raw" if search_raw else "source"
311 311 if output:
312 312 tosearch = "history." + tosearch
313 313 self.writeout_cache()
314 314 return self._run_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
315 315 raw=raw, output=output)
316 316
317 317 def get_range(self, session, start=1, stop=None, raw=True,output=False):
318 318 """Retrieve input by session.
319 319
320 320 Parameters
321 321 ----------
322 322 session : int
323 323 Session number to retrieve.
324 324 start : int
325 325 First line to retrieve.
326 326 stop : int
327 327 End of line range (excluded from output itself). If None, retrieve
328 328 to the end of the session.
329 329 raw : bool
330 330 If True, return untranslated input
331 331 output : bool
332 332 If True, attempt to include output. This will be 'real' Python
333 333 objects for the current session, or text reprs from previous
334 334 sessions if db_log_output was enabled at the time. Where no output
335 335 is found, None is used.
336 336
337 337 Returns
338 338 -------
339 339 An iterator over the desired lines. Each line is a 3-tuple, either
340 340 (session, line, input) if output is False, or
341 341 (session, line, (input, output)) if output is True.
342 342 """
343 343 if stop:
344 344 lineclause = "line >= ? AND line < ?"
345 345 params = (session, start, stop)
346 346 else:
347 347 lineclause = "line>=?"
348 348 params = (session, start)
349 349
350 return self._run_sql("WHERE session==? AND %s""" % lineclause,
350 return self._run_sql("WHERE session==? AND %s" % lineclause,
351 351 params, raw=raw, output=output)
352 352
353 353 def get_range_by_str(self, rangestr, raw=True, output=False):
354 354 """Get lines of history from a string of ranges, as used by magic
355 355 commands %hist, %save, %macro, etc.
356 356
357 357 Parameters
358 358 ----------
359 359 rangestr : str
360 360 A string specifying ranges, e.g. "5 ~2/1-4". See
361 361 :func:`magic_history` for full details.
362 362 raw, output : bool
363 363 As :meth:`get_range`
364 364
365 365 Returns
366 366 -------
367 367 Tuples as :meth:`get_range`
368 368 """
369 369 for sess, s, e in extract_hist_ranges(rangestr):
370 370 for line in self.get_range(sess, s, e, raw=raw, output=output):
371 371 yield line
372 372
373 373
374 374 class HistoryManager(HistoryAccessor):
375 375 """A class to organize all history-related functionality in one place.
376 376 """
377 377 # Public interface
378 378
379 379 # An instance of the IPython shell we are attached to
380 380 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
381 381 # Lists to hold processed and raw history. These start with a blank entry
382 382 # so that we can index them starting from 1
383 383 input_hist_parsed = List([""])
384 384 input_hist_raw = List([""])
385 385 # A list of directories visited during session
386 386 dir_hist = List()
387 387 def _dir_hist_default(self):
388 388 try:
389 389 return [os.getcwdu()]
390 390 except OSError:
391 391 return []
392 392
393 393 # A dict of output history, keyed with ints from the shell's
394 394 # execution count.
395 395 output_hist = Dict()
396 396 # The text/plain repr of outputs.
397 397 output_hist_reprs = Dict()
398 398
399 399 # The number of the current session in the history database
400 400 session_number = Integer()
401 401 # Should we log output to the database? (default no)
402 402 db_log_output = Bool(False, config=True)
403 403 # Write to database every x commands (higher values save disk access & power)
404 404 # Values of 1 or less effectively disable caching.
405 405 db_cache_size = Integer(0, config=True)
406 406 # The input and output caches
407 407 db_input_cache = List()
408 408 db_output_cache = List()
409 409
410 410 # History saving in separate thread
411 411 save_thread = Instance('IPython.core.history.HistorySavingThread')
412 412 try: # Event is a function returning an instance of _Event...
413 413 save_flag = Instance(threading._Event)
414 414 except AttributeError: # ...until Python 3.3, when it's a class.
415 415 save_flag = Instance(threading.Event)
416 416
417 417 # Private interface
418 418 # Variables used to store the three last inputs from the user. On each new
419 419 # history update, we populate the user's namespace with these, shifted as
420 420 # necessary.
421 421 _i00 = Unicode(u'')
422 422 _i = Unicode(u'')
423 423 _ii = Unicode(u'')
424 424 _iii = Unicode(u'')
425 425
426 426 # A regex matching all forms of the exit command, so that we don't store
427 427 # them in the history (it's annoying to rewind the first entry and land on
428 428 # an exit call).
429 429 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
430 430
431 431 def __init__(self, shell=None, config=None, **traits):
432 432 """Create a new history manager associated with a shell instance.
433 433 """
434 434 # We need a pointer back to the shell for various tasks.
435 435 super(HistoryManager, self).__init__(shell=shell, config=config,
436 436 **traits)
437 437 self.save_flag = threading.Event()
438 438 self.db_input_cache_lock = threading.Lock()
439 439 self.db_output_cache_lock = threading.Lock()
440 440 if self.enabled and self.hist_file != ':memory:':
441 441 self.save_thread = HistorySavingThread(self)
442 442 self.save_thread.start()
443 443
444 444 self.new_session()
445 445
446 446 def _get_hist_file_name(self, profile=None):
447 447 """Get default history file name based on the Shell's profile.
448 448
449 449 The profile parameter is ignored, but must exist for compatibility with
450 450 the parent class."""
451 451 profile_dir = self.shell.profile_dir.location
452 452 return os.path.join(profile_dir, 'history.sqlite')
453 453
454 454 @needs_sqlite
455 455 def new_session(self, conn=None):
456 456 """Get a new session number."""
457 457 if conn is None:
458 458 conn = self.db
459 459
460 460 with conn:
461 461 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
462 462 NULL, "") """, (datetime.datetime.now(),))
463 463 self.session_number = cur.lastrowid
464 464
465 465 def end_session(self):
466 466 """Close the database session, filling in the end time and line count."""
467 467 self.writeout_cache()
468 468 with self.db:
469 469 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
470 470 session==?""", (datetime.datetime.now(),
471 471 len(self.input_hist_parsed)-1, self.session_number))
472 472 self.session_number = 0
473 473
474 474 def name_session(self, name):
475 475 """Give the current session a name in the history database."""
476 476 with self.db:
477 477 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
478 478 (name, self.session_number))
479 479
480 480 def reset(self, new_session=True):
481 481 """Clear the session history, releasing all object references, and
482 482 optionally open a new session."""
483 483 self.output_hist.clear()
484 484 # The directory history can't be completely empty
485 485 self.dir_hist[:] = [os.getcwdu()]
486 486
487 487 if new_session:
488 488 if self.session_number:
489 489 self.end_session()
490 490 self.input_hist_parsed[:] = [""]
491 491 self.input_hist_raw[:] = [""]
492 492 self.new_session()
493 493
494 494 # ------------------------------
495 495 # Methods for retrieving history
496 496 # ------------------------------
497 497 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
498 498 """Get input and output history from the current session. Called by
499 499 get_range, and takes similar parameters."""
500 500 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
501 501
502 502 n = len(input_hist)
503 503 if start < 0:
504 504 start += n
505 505 if not stop or (stop > n):
506 506 stop = n
507 507 elif stop < 0:
508 508 stop += n
509 509
510 510 for i in range(start, stop):
511 511 if output:
512 512 line = (input_hist[i], self.output_hist_reprs.get(i))
513 513 else:
514 514 line = input_hist[i]
515 515 yield (0, i, line)
516 516
517 517 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
518 518 """Retrieve input by session.
519 519
520 520 Parameters
521 521 ----------
522 522 session : int
523 523 Session number to retrieve. The current session is 0, and negative
524 524 numbers count back from current session, so -1 is previous session.
525 525 start : int
526 526 First line to retrieve.
527 527 stop : int
528 528 End of line range (excluded from output itself). If None, retrieve
529 529 to the end of the session.
530 530 raw : bool
531 531 If True, return untranslated input
532 532 output : bool
533 533 If True, attempt to include output. This will be 'real' Python
534 534 objects for the current session, or text reprs from previous
535 535 sessions if db_log_output was enabled at the time. Where no output
536 536 is found, None is used.
537 537
538 538 Returns
539 539 -------
540 540 An iterator over the desired lines. Each line is a 3-tuple, either
541 541 (session, line, input) if output is False, or
542 542 (session, line, (input, output)) if output is True.
543 543 """
544 544 if session <= 0:
545 545 session += self.session_number
546 546 if session==self.session_number: # Current session
547 547 return self._get_range_session(start, stop, raw, output)
548 548 return super(HistoryManager, self).get_range(session, start, stop, raw,
549 549 output)
550 550
551 551 ## ----------------------------
552 552 ## Methods for storing history:
553 553 ## ----------------------------
554 554 def store_inputs(self, line_num, source, source_raw=None):
555 555 """Store source and raw input in history and create input cache
556 556 variables _i*.
557 557
558 558 Parameters
559 559 ----------
560 560 line_num : int
561 561 The prompt number of this input.
562 562
563 563 source : str
564 564 Python input.
565 565
566 566 source_raw : str, optional
567 567 If given, this is the raw input without any IPython transformations
568 568 applied to it. If not given, ``source`` is used.
569 569 """
570 570 if source_raw is None:
571 571 source_raw = source
572 572 source = source.rstrip('\n')
573 573 source_raw = source_raw.rstrip('\n')
574 574
575 575 # do not store exit/quit commands
576 576 if self._exit_re.match(source_raw.strip()):
577 577 return
578 578
579 579 self.input_hist_parsed.append(source)
580 580 self.input_hist_raw.append(source_raw)
581 581
582 582 with self.db_input_cache_lock:
583 583 self.db_input_cache.append((line_num, source, source_raw))
584 584 # Trigger to flush cache and write to DB.
585 585 if len(self.db_input_cache) >= self.db_cache_size:
586 586 self.save_flag.set()
587 587
588 588 # update the auto _i variables
589 589 self._iii = self._ii
590 590 self._ii = self._i
591 591 self._i = self._i00
592 592 self._i00 = source_raw
593 593
594 594 # hackish access to user namespace to create _i1,_i2... dynamically
595 595 new_i = '_i%s' % line_num
596 596 to_main = {'_i': self._i,
597 597 '_ii': self._ii,
598 598 '_iii': self._iii,
599 599 new_i : self._i00 }
600 600
601 601 self.shell.push(to_main, interactive=False)
602 602
603 603 def store_output(self, line_num):
604 604 """If database output logging is enabled, this saves all the
605 605 outputs from the indicated prompt number to the database. It's
606 606 called by run_cell after code has been executed.
607 607
608 608 Parameters
609 609 ----------
610 610 line_num : int
611 611 The line number from which to save outputs
612 612 """
613 613 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
614 614 return
615 615 output = self.output_hist_reprs[line_num]
616 616
617 617 with self.db_output_cache_lock:
618 618 self.db_output_cache.append((line_num, output))
619 619 if self.db_cache_size <= 1:
620 620 self.save_flag.set()
621 621
622 622 def _writeout_input_cache(self, conn):
623 623 with conn:
624 624 for line in self.db_input_cache:
625 625 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
626 626 (self.session_number,)+line)
627 627
628 628 def _writeout_output_cache(self, conn):
629 629 with conn:
630 630 for line in self.db_output_cache:
631 631 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
632 632 (self.session_number,)+line)
633 633
634 634 @needs_sqlite
635 635 def writeout_cache(self, conn=None):
636 636 """Write any entries in the cache to the database."""
637 637 if conn is None:
638 638 conn = self.db
639 639
640 640 with self.db_input_cache_lock:
641 641 try:
642 642 self._writeout_input_cache(conn)
643 643 except sqlite3.IntegrityError:
644 644 self.new_session(conn)
645 645 print("ERROR! Session/line number was not unique in",
646 646 "database. History logging moved to new session",
647 647 self.session_number)
648 648 try:
649 649 # Try writing to the new session. If this fails, don't
650 650 # recurse
651 651 self._writeout_input_cache(conn)
652 652 except sqlite3.IntegrityError:
653 653 pass
654 654 finally:
655 655 self.db_input_cache = []
656 656
657 657 with self.db_output_cache_lock:
658 658 try:
659 659 self._writeout_output_cache(conn)
660 660 except sqlite3.IntegrityError:
661 661 print("!! Session/line number for output was not unique",
662 662 "in database. Output will not be stored.")
663 663 finally:
664 664 self.db_output_cache = []
665 665
666 666
667 667 class HistorySavingThread(threading.Thread):
668 668 """This thread takes care of writing history to the database, so that
669 669 the UI isn't held up while that happens.
670 670
671 671 It waits for the HistoryManager's save_flag to be set, then writes out
672 672 the history cache. The main thread is responsible for setting the flag when
673 673 the cache size reaches a defined threshold."""
674 674 daemon = True
675 675 stop_now = False
676 676 enabled = True
677 677 def __init__(self, history_manager):
678 678 super(HistorySavingThread, self).__init__()
679 679 self.history_manager = history_manager
680 680 self.enabled = history_manager.enabled
681 681 atexit.register(self.stop)
682 682
683 683 @needs_sqlite
684 684 def run(self):
685 685 # We need a separate db connection per thread:
686 686 try:
687 687 self.db = sqlite3.connect(self.history_manager.hist_file,
688 688 **self.history_manager.connection_options
689 689 )
690 690 while True:
691 691 self.history_manager.save_flag.wait()
692 692 if self.stop_now:
693 693 return
694 694 self.history_manager.save_flag.clear()
695 695 self.history_manager.writeout_cache(self.db)
696 696 except Exception as e:
697 697 print(("The history saving thread hit an unexpected error (%s)."
698 698 "History will not be written to the database.") % repr(e))
699 699
700 700 def stop(self):
701 701 """This can be called from the main thread to safely stop this thread.
702 702
703 703 Note that it does not attempt to write out remaining history before
704 704 exiting. That should be done by calling the HistoryManager's
705 705 end_session method."""
706 706 self.stop_now = True
707 707 self.history_manager.save_flag.set()
708 708 self.join()
709 709
710 710
711 711 # To match, e.g. ~5/8-~2/3
712 712 range_re = re.compile(r"""
713 713 ((?P<startsess>~?\d+)/)?
714 714 (?P<start>\d+) # Only the start line num is compulsory
715 715 ((?P<sep>[\-:])
716 716 ((?P<endsess>~?\d+)/)?
717 717 (?P<end>\d+))?
718 718 $""", re.VERBOSE)
719 719
720 720
721 721 def extract_hist_ranges(ranges_str):
722 722 """Turn a string of history ranges into 3-tuples of (session, start, stop).
723 723
724 724 Examples
725 725 --------
726 726 list(extract_input_ranges("~8/5-~7/4 2"))
727 727 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
728 728 """
729 729 for range_str in ranges_str.split():
730 730 rmatch = range_re.match(range_str)
731 731 if not rmatch:
732 732 continue
733 733 start = int(rmatch.group("start"))
734 734 end = rmatch.group("end")
735 735 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
736 736 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
737 737 end += 1
738 738 startsess = rmatch.group("startsess") or "0"
739 739 endsess = rmatch.group("endsess") or startsess
740 740 startsess = int(startsess.replace("~","-"))
741 741 endsess = int(endsess.replace("~","-"))
742 742 assert endsess >= startsess
743 743
744 744 if endsess == startsess:
745 745 yield (startsess, start, end)
746 746 continue
747 747 # Multiple sessions in one range:
748 748 yield (startsess, start, None)
749 749 for sess in range(startsess+1, endsess):
750 750 yield (sess, 1, None)
751 751 yield (endsess, 1, end)
752 752
753 753
754 754 def _format_lineno(session, line):
755 755 """Helper function to format line numbers properly."""
756 756 if session == 0:
757 757 return str(line)
758 758 return "%s#%s" % (session, line)
759 759
760 760
General Comments 0
You need to be logged in to leave comments. Login now