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