##// END OF EJS Templates
Tidy up history retrieval APIs, and magic commands using them (%hist, %macro, %save, %edit)
Thomas Kluyver -
Show More
@@ -13,8 +13,8 b''
13 13 from __future__ import print_function
14 14
15 15 # Stdlib imports
16 import fnmatch
17 16 import os
17 import re
18 18 import sqlite3
19 19
20 20 # Our own packages
@@ -123,46 +123,14 b' class HistoryManager(Configurable):'
123 123 name='session_number'""", (self.session_number+1,))
124 124 self.db.commit()
125 125
126 def get_db_history(self, session, start=1, stop=None, raw=True):
127 """Retrieve input history from the database by session.
128
129 Parameters
130 ----------
131 session : int
132 Session number to retrieve. If negative, counts back from current
133 session (so -1 is previous session).
134 start : int
135 First line to retrieve.
136 stop : int
137 Last line to retrieve. If None, retrieve to the end of the session.
138 raw : bool
139 If True, return raw input
140
141 Returns
142 -------
143 An iterator over the desired lines.
144 """
145 toget = 'source_raw' if raw else 'source'
146 if session < 0:
147 session += self.session_number
148
149 if stop:
150 cur = self.db.execute("SELECT " + toget + """ FROM history WHERE
151 session==? AND line BETWEEN ? and ?""",
152 (session, start, stop))
153 else:
154 cur = self.db.execute("SELECT " + toget + """ FROM history WHERE
155 session==? AND line>=?""", (session, start))
156 return (x[0] for x in cur)
157
158 def tail_db_history(self, n=10, raw=True):
126 def get_hist_tail(self, n=10, raw=True):
159 127 """Get the last n lines from the history database."""
160 128 toget = 'source_raw' if raw else 'source'
161 cur = self.db.execute("SELECT " + toget + """ FROM history ORDER BY
162 session DESC, line DESC LIMIT ?""", (n,))
163 return (x[0] for x in reversed(cur.fetchall()))
129 cur = self.db.execute("SELECT session, line, " + toget +\
130 " FROM history ORDER BY session DESC, line DESC LIMIT ?""", (n,))
131 return reversed(cur.fetchall())
164 132
165 def globsearch_db(self, pattern="*"):
133 def get_hist_search(self, pattern="*", raw=True):
166 134 """Search the database using unix glob-style matching (wildcards * and
167 135 ?, escape using \).
168 136
@@ -170,58 +138,86 b' class HistoryManager(Configurable):'
170 138 -------
171 139 An iterator over tuples: (session, line_number, command)
172 140 """
173 return self.db.execute("""SELECT session, line, source_raw FROM history
174 WHERE source_raw GLOB ?""", (pattern,))
141 toget = "source_raw" if raw else source
142 return self.db.execute("SELECT session, line, " +toget+ \
143 "FROM history WHERE" +toget+ "GLOB ?", (pattern,))
175 144
176 def get_history(self, start=1, stop=None, raw=False, output=True):
177 """Get the history list.
145 def _get_hist_session(self, start=1, stop=None, raw=True, output=False):
146 """Get input and output history from the current session. Called by
147 get_history, and takes similar parameters."""
148 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
178 149
179 Get the input and output history.
150 n = len(input_hist)
151 if start < 0:
152 start += n
153 if not stop:
154 stop = n
155 elif stop < 0:
156 stop += n
157
158 for i in range(start, stop):
159 if output:
160 line = (input_hist[i], self.output_hist.get(i))
161 else:
162 line = input_hist[i]
163 yield (0, i, line)
164
165 def get_history(self, session=0, start=1, stop=None, raw=True,output=False):
166 """Retrieve input by session.
180 167
181 168 Parameters
182 169 ----------
170 session : int
171 Session number to retrieve. The current session is 0, and negative
172 numbers count back from current session, so -1 is previous session.
183 173 start : int
184 From (prompt number in the current session). Negative numbers count
185 back from the end.
174 First line to retrieve.
186 175 stop : int
187 To (prompt number in the current session, exclusive). Negative
188 numbers count back from the end, and None goes to the end.
176 Last line to retrieve. If None, retrieve to the end of the session.
189 177 raw : bool
190 If True, return the raw input.
178 If True, return untranslated input
191 179 output : bool
192 If True, then return the output as well.
193 this_session : bool
194 If True, indexing is from 1 at the start of this session.
195 If False, indexing is from 1 at the start of the whole history.
180 If True, attempt to include output. This will be 'real' Python
181 objects for the current session, or text reprs from previous
182 sessions if db_log_output was enabled at the time. Where no output
183 is found, None is used.
196 184
197 185 Returns
198 186 -------
199 If output is True, then return a dict of tuples, keyed by the prompt
200 numbers and with values of (input, output). If output is False, then
201 a dict, keyed by the prompt number with the values of input.
187 An iterator over the desired lines. Each line is a 3-tuple, either
188 (session, line, input) if output is False, or
189 (session, line, (input, output)) if output is True.
202 190 """
203 if raw:
204 input_hist = self.input_hist_raw
205 else:
206 input_hist = self.input_hist_parsed
207 if output:
208 output_hist = self.output_hist
209
210 n = len(input_hist)
211 if start < 0:
212 start += n
213 if not stop:
214 stop = n
215 elif stop < 0:
216 stop += n
191 if session == 0 or session==self.session_number: # Current session
192 return self._get_hist_session(start, stop, raw, output)
193 if session < 0:
194 session += self.session_number
217 195
218 hist = {}
219 for i in range(start, stop):
196 # Assemble the SQL query:
197 sqlfrom = "history"
198 toget = 'source_raw' if raw else 'source'
220 199 if output:
221 hist[i] = (input_hist[i], output_hist.get(i))
200 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
201 toget = "history.%s, output_history.output" % toget
202 if stop:
203 lineclause = "line BETWEEN ? and ?"
204 params = (session, start, stop)
222 205 else:
223 hist[i] = input_hist[i]
224 return hist
206 lineclause = "line>=?"
207 params = (session, start)
208
209 cur = self.db.execute("SELECT %s FROM %s WHERE session==? AND %s"\
210 %(toget, sqlfrom, lineclause), params)
211 if output: # Regroup into 3-tuples
212 return ((ses, lin (inp, out)) for ses, lin, inp, out in cur)
213 return cur
214
215 def get_hist_from_rangestr(self, rangestr, raw=True, output=False):
216 """Get lines of history from a string of ranges, as used by magic
217 commands %hist, %save, %macro, etc."""
218 for parts in extract_hist_ranges(rangestr):
219 for line in self.get_history(*parts, raw=raw, output=output):
220 yield line
225 221
226 222 def store_inputs(self, line_num, source, source_raw=None):
227 223 """Store source and raw input in history and create input cache
@@ -305,6 +301,52 b' class HistoryManager(Configurable):'
305 301 # The directory history can't be completely empty
306 302 self.dir_hist[:] = [os.getcwd()]
307 303
304 # To match, e.g. ~5#8-~2#3
305 range_re = re.compile(r"""
306 ((?P<startsess>~?\d+)\#)?
307 (?P<start>\d+) # Only the start line num is compulsory
308 ((?P<sep>[\-:])
309 ((?P<endsess>~?\d+)\#)?
310 (?P<end>\d+))?
311 """, re.VERBOSE)
312
313 def extract_hist_ranges(ranges_str):
314 """Turn a string of history ranges into 3-tuples of (session, start, stop).
315
316 Examples
317 --------
318 list(extract_input_ranges("~8#5-~7#4 2"))
319 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
320 """
321 print(ranges_str)
322 for range_str in ranges_str.split():
323 rmatch = range_re.match(range_str)
324 start = int(rmatch.group("start"))
325 end = rmatch.group("end")
326 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
327 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
328 end += 1
329 startsess = rmatch.group("startsess") or "0"
330 endsess = rmatch.group("endsess") or startsess
331 startsess = int(startsess.replace("~","-"))
332 endsess = int(endsess.replace("~","-"))
333 assert endsess >= startsess
334
335 if endsess == startsess:
336 yield (startsess, start, end)
337 continue
338 # Multiple sessions in one range:
339 yield (startsess, start, None)
340 for sess in range(startsess+1, endsess):
341 yield (sess, 1, None)
342 yield (endsess, 1, end)
343
344 def _format_lineno(session, line):
345 """Helper function to format line numbers properly."""
346 if session == 0:
347 return str(line)
348 return "%s#%s" % (session, line)
349
308 350 @testdec.skip_doctest
309 351 def magic_history(self, parameter_s = ''):
310 352 """Print input history (_i<n> variables), with most recent last.
@@ -344,6 +386,9 b" def magic_history(self, parameter_s = ''):"
344 386 This includes the saved history (almost all commands ever written).
345 387 Use '%hist -g' to show full saved history (may be very long).
346 388
389 -l: get the last n lines from all sessions. Specify n as a single arg, or
390 the default is the last 10 lines.
391
347 392 -f FILENAME: instead of printing the output to the screen, redirect it to
348 393 the given file. The file is always overwritten, though IPython asks for
349 394 confirmation first if it already exists.
@@ -361,7 +406,7 b" def magic_history(self, parameter_s = ''):"
361 406 if not self.shell.displayhook.do_full_cache:
362 407 print('This feature is only available if numbered prompts are in use.')
363 408 return
364 opts,args = self.parse_options(parameter_s,'gnoptsrf:',mode='list')
409 opts,args = self.parse_options(parameter_s,'noprtglf:',mode='string')
365 410
366 411 # For brevity
367 412 history_manager = self.shell.history_manager
@@ -383,53 +428,52 b" def magic_history(self, parameter_s = ''):"
383 428 close_at_end = True
384 429
385 430 print_nums = 'n' in opts
386 print_outputs = 'o' in opts
431 get_output = 'o' in opts
387 432 pyprompts = 'p' in opts
388 433 # Raw history is the default
389 434 raw = not('t' in opts)
390 435
391 436 default_length = 40
392 437 pattern = None
393 if 'g' in opts:
394 start = 1; stop = None
395 parts = parameter_s.split(None, 1)
396 if len(parts) == 1:
397 parts += '*'
398 head, pattern = parts
399 pattern = "*" + pattern + "*"
400 elif len(args) == 0:
401 start = 1; stop = None
402 elif len(args) == 1:
403 start = -int(args[0]); stop=None
404 elif len(args) == 2:
405 start = int(args[0]); stop = int(args[1])
406 else:
407 warn('%hist takes 0, 1 or 2 arguments separated by spaces.')
408 print(self.magic_hist.__doc__, file=IPython.utils.io.Term.cout)
409 return
410
411 hist = history_manager.get_history(start, stop, raw, print_outputs)
412 438
413 width = len(str(max(hist.iterkeys())))
414 line_sep = ['','\n']
439 # Glob search:
440 if 'g' in opts:
441 pattern = "*" + args + "*" if args else "*"
415 442
416 found = False
417 if pattern is not None:
418 for session, line, s in history_manager.globsearch_db(pattern):
443 # Display:
444 matches_current_session = []
445 for session, line, s in history_manager.get_hist_search(pattern, raw):
446 if session == history_manager.session_number:
447 matches_current_session.append(line, s)
448 continue
419 449 print("%d#%d: %s" %(session, line, s.expandtabs(4)), file=outfile)
420 found = True
450 if matches_current_session:
451 print("=== Current session: ===", file=outfile)
452 for line, s in matches_current_session:
453 print("%d: %s" %(line, s.expandtabs(4)), file=outfile)
454 return
455
456 if 'l' in opts: # Get 'tail'
457 try:
458 n = int(args)
459 except ValueError, IndexError:
460 n = 10
461 hist = history_manager.get_hist_tail(n, raw=raw)
462 else:
463 if args: # Get history by ranges
464 hist = history_manager.get_hist_from_rangestr(args, raw, get_output)
465 else: # Just get history for the current session
466 hist = history_manager.get_history(raw=raw, output=get_output)
467 # Pull hist into a list, so we can get the widest number in it.
468 hist = list(hist)
421 469
422 if found:
423 print("===", file=outfile)
424 print("shadow history ends, fetch by %rep session#line",
425 file=outfile)
426 print("=== start of normal history ===", file=outfile)
470 width = max(len(_format_lineno(s, l)) for s, l, _ in hist)
427 471
428 for in_num, inline in sorted(hist.iteritems()):
472 for session, lineno, inline in hist:
429 473 # Print user history with tabs expanded to 4 spaces. The GUI clients
430 474 # use hard tabs for easier usability in auto-indented code, but we want
431 475 # to produce PEP-8 compliant history for safe pasting into an editor.
432 if print_outputs:
476 if get_output:
433 477 inline, output = inline
434 478 inline = inline.expandtabs(4).rstrip()
435 479
@@ -437,15 +481,16 b" def magic_history(self, parameter_s = ''):"
437 481 continue
438 482
439 483 multiline = "\n" in inline
484 line_sep = '\n' if multiline else ''
440 485 if print_nums:
441 print('%s:%s' % (str(in_num).ljust(width), line_sep[multiline]),
442 file=outfile, end='')
486 print('%s:%s' % (_format_lineno(session, lineno).ljust(width),
487 line_sep[multiline]), file=outfile, end='')
443 488 if pyprompts:
444 489 print(">>> ", end="", file=outfile)
445 490 if multiline:
446 491 inline = "\n... ".join(inline.splitlines()) + "\n..."
447 492 print(inline, file=outfile)
448 if print_outputs and output:
493 if get_output and output:
449 494 print(repr(output), file=outfile)
450 495
451 496 if close_at_end:
@@ -1551,7 +1551,7 b' class InteractiveShell(Configurable, Magic):'
1551 1551 readline.set_history_length(self.history_length)
1552 1552
1553 1553 # Load the last 1000 lines from history
1554 for cell in self.history_manager.tail_db_history(1000):
1554 for _, _, cell in self.history_manager.get_hist_tail(1000):
1555 1555 if cell.strip(): # Ignore blank lines
1556 1556 for line in cell.splitlines():
1557 1557 readline.add_history(line)
@@ -165,14 +165,15 b' python-profiler package from non-free.""")'
165 165 out.sort()
166 166 return out
167 167
168 def extract_input_slices(self,slices,raw=False):
168 def extract_input_lines(self, range_str, raw=False):
169 169 """Return as a string a set of input history slices.
170 170
171 171 Inputs:
172 172
173 - slices: the set of slices is given as a list of strings (like
174 ['1','4:8','9'], since this function is for use by magic functions
175 which get their arguments as strings.
173 - range_str: the set of slices is given as a string, like
174 "~5#6-~4#2 4:8 9", since this function is for use by magic functions
175 which get their arguments as strings. The number before the # is the
176 session number: ~n goes n back from the current session.
176 177
177 178 Optional inputs:
178 179
@@ -184,21 +185,9 b' python-profiler package from non-free.""")'
184 185 N:M -> standard python form, means including items N...(M-1).
185 186
186 187 N-M -> include items N..M (closed endpoint)."""
187 history_manager = self.shell.history_manager
188
189 cmds = []
190 for chunk in slices:
191 if ':' in chunk:
192 ini,fin = map(int,chunk.split(':'))
193 elif '-' in chunk:
194 ini,fin = map(int,chunk.split('-'))
195 fin += 1
196 else:
197 ini = int(chunk)
198 fin = ini+1
199 hist = history_manager.get_history((ini,fin), raw=raw, output=False)
200 cmds.append('\n'.join(hist[i] for i in sorted(hist.iterkeys())))
201 return cmds
188 lines = self.shell.history_manager.\
189 get_hist_from_rangestr(range_str, raw=raw)
190 return "\n".join(x for _, _, x in lines)
202 191
203 192 def arg_err(self,func):
204 193 """Print docstring if incorrect arguments were passed"""
@@ -2036,11 +2025,11 b' Currently the magic system has the following functions:\\n"""'
2036 2025 if len(args) == 1:
2037 2026 raise UsageError(
2038 2027 "%macro insufficient args; usage '%macro name n1-n2 n3-4...")
2039 name,ranges = args[0], args[1:]
2028 name, ranges = args[0], " ".join(args[1:])
2040 2029
2041 2030 #print 'rng',ranges # dbg
2042 lines = self.extract_input_slices(ranges,'r' in opts)
2043 macro = Macro("\n".join(lines))
2031 lines = self.extract_input_lines(ranges,'r' in opts)
2032 macro = Macro(lines)
2044 2033 self.shell.define_macro(name, macro)
2045 2034 print 'Macro `%s` created. To execute, type its name (without quotes).' % name
2046 2035 print 'Macro contents:'
@@ -2067,7 +2056,7 b' Currently the magic system has the following functions:\\n"""'
2067 2056 it asks for confirmation before overwriting existing files."""
2068 2057
2069 2058 opts,args = self.parse_options(parameter_s,'r',mode='list')
2070 fname,ranges = args[0], args[1:]
2059 fname,ranges = args[0], " ".join(args[1:])
2071 2060 if not fname.endswith('.py'):
2072 2061 fname += '.py'
2073 2062 if os.path.isfile(fname):
@@ -2075,7 +2064,7 b' Currently the magic system has the following functions:\\n"""'
2075 2064 if ans.lower() not in ['y','yes']:
2076 2065 print 'Operation cancelled.'
2077 2066 return
2078 cmds = '\n'.join(self.extract_input_slices(ranges, 'r' in opts))
2067 cmds = self.extract_input_lines(ranges, 'r' in opts)
2079 2068 with open(fname,'w') as f:
2080 2069 f.write(cmds)
2081 2070 print 'The following commands were written to file `%s`:' % fname
@@ -2261,13 +2250,13 b' Currently the magic system has the following functions:\\n"""'
2261 2250
2262 2251 opts,args = self.parse_options(parameter_s,'prxn:')
2263 2252 # Set a few locals from the options for convenience:
2264 opts_p = opts.has_key('p')
2265 opts_r = opts.has_key('r')
2253 opts_prev = 'p' in opts
2254 opts_raw = 'r' in opts
2266 2255
2267 2256 # Default line number value
2268 2257 lineno = opts.get('n',None)
2269 2258
2270 if opts_p:
2259 if opts_prev:
2271 2260 args = '_%s' % last_call[0]
2272 2261 if not self.shell.user_ns.has_key(args):
2273 2262 args = last_call[1]
@@ -2276,7 +2265,7 b' Currently the magic system has the following functions:\\n"""'
2276 2265 # let it be clobbered by successive '-p' calls.
2277 2266 try:
2278 2267 last_call[0] = self.shell.displayhook.prompt_count
2279 if not opts_p:
2268 if not opts_prev:
2280 2269 last_call[1] = parameter_s
2281 2270 except:
2282 2271 pass
@@ -2290,8 +2279,7 b' Currently the magic system has the following functions:\\n"""'
2290 2279 # Mode where user specifies ranges of lines, like in %macro.
2291 2280 # This means that you can't edit files whose names begin with
2292 2281 # numbers this way. Tough.
2293 ranges = args.split()
2294 data = '\n'.join(self.extract_input_slices(ranges,opts_r))
2282 data = self.extract_input_lines(args, opts_raw)
2295 2283 elif args.endswith('.py'):
2296 2284 filename = make_filename(args)
2297 2285 use_temp = False
General Comments 0
You need to be logged in to leave comments. Login now