##// 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 from __future__ import print_function
13 from __future__ import print_function
14
14
15 # Stdlib imports
15 # Stdlib imports
16 import fnmatch
17 import os
16 import os
17 import re
18 import sqlite3
18 import sqlite3
19
19
20 # Our own packages
20 # Our own packages
@@ -122,47 +122,15 b' class HistoryManager(Configurable):'
122 self.db.execute("""UPDATE singletons SET value=? WHERE
122 self.db.execute("""UPDATE singletons SET value=? WHERE
123 name='session_number'""", (self.session_number+1,))
123 name='session_number'""", (self.session_number+1,))
124 self.db.commit()
124 self.db.commit()
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
125
158 def tail_db_history(self, n=10, raw=True):
126 def get_hist_tail(self, n=10, raw=True):
159 """Get the last n lines from the history database."""
127 """Get the last n lines from the history database."""
160 toget = 'source_raw' if raw else 'source'
128 toget = 'source_raw' if raw else 'source'
161 cur = self.db.execute("SELECT " + toget + """ FROM history ORDER BY
129 cur = self.db.execute("SELECT session, line, " + toget +\
162 session DESC, line DESC LIMIT ?""", (n,))
130 " FROM history ORDER BY session DESC, line DESC LIMIT ?""", (n,))
163 return (x[0] for x in reversed(cur.fetchall()))
131 return reversed(cur.fetchall())
164
132
165 def globsearch_db(self, pattern="*"):
133 def get_hist_search(self, pattern="*", raw=True):
166 """Search the database using unix glob-style matching (wildcards * and
134 """Search the database using unix glob-style matching (wildcards * and
167 ?, escape using \).
135 ?, escape using \).
168
136
@@ -170,42 +138,14 b' class HistoryManager(Configurable):'
170 -------
138 -------
171 An iterator over tuples: (session, line_number, command)
139 An iterator over tuples: (session, line_number, command)
172 """
140 """
173 return self.db.execute("""SELECT session, line, source_raw FROM history
141 toget = "source_raw" if raw else source
174 WHERE source_raw GLOB ?""", (pattern,))
142 return self.db.execute("SELECT session, line, " +toget+ \
175
143 "FROM history WHERE" +toget+ "GLOB ?", (pattern,))
176 def get_history(self, start=1, stop=None, raw=False, output=True):
144
177 """Get the history list.
145 def _get_hist_session(self, start=1, stop=None, raw=True, output=False):
178
146 """Get input and output history from the current session. Called by
179 Get the input and output history.
147 get_history, and takes similar parameters."""
180
148 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
181 Parameters
182 ----------
183 start : int
184 From (prompt number in the current session). Negative numbers count
185 back from the end.
186 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.
189 raw : bool
190 If True, return the raw input.
191 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.
196
197 Returns
198 -------
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.
202 """
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
149
210 n = len(input_hist)
150 n = len(input_hist)
211 if start < 0:
151 if start < 0:
@@ -215,13 +155,69 b' class HistoryManager(Configurable):'
215 elif stop < 0:
155 elif stop < 0:
216 stop += n
156 stop += n
217
157
218 hist = {}
219 for i in range(start, stop):
158 for i in range(start, stop):
220 if output:
159 if output:
221 hist[i] = (input_hist[i], output_hist.get(i))
160 line = (input_hist[i], self.output_hist.get(i))
222 else:
161 else:
223 hist[i] = input_hist[i]
162 line = input_hist[i]
224 return hist
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.
167
168 Parameters
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.
173 start : int
174 First line to retrieve.
175 stop : int
176 Last line to retrieve. If None, retrieve to the end of the session.
177 raw : bool
178 If True, return untranslated input
179 output : bool
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.
184
185 Returns
186 -------
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.
190 """
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
195
196 # Assemble the SQL query:
197 sqlfrom = "history"
198 toget = 'source_raw' if raw else 'source'
199 if output:
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)
205 else:
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 def store_inputs(self, line_num, source, source_raw=None):
222 def store_inputs(self, line_num, source, source_raw=None):
227 """Store source and raw input in history and create input cache
223 """Store source and raw input in history and create input cache
@@ -304,6 +300,52 b' class HistoryManager(Configurable):'
304 self.output_hist.clear()
300 self.output_hist.clear()
305 # The directory history can't be completely empty
301 # The directory history can't be completely empty
306 self.dir_hist[:] = [os.getcwd()]
302 self.dir_hist[:] = [os.getcwd()]
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)
307
349
308 @testdec.skip_doctest
350 @testdec.skip_doctest
309 def magic_history(self, parameter_s = ''):
351 def magic_history(self, parameter_s = ''):
@@ -343,6 +385,9 b" def magic_history(self, parameter_s = ''):"
343 -g: treat the arg as a pattern to grep for in (full) history.
385 -g: treat the arg as a pattern to grep for in (full) history.
344 This includes the saved history (almost all commands ever written).
386 This includes the saved history (almost all commands ever written).
345 Use '%hist -g' to show full saved history (may be very long).
387 Use '%hist -g' to show full saved history (may be very long).
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.
346
391
347 -f FILENAME: instead of printing the output to the screen, redirect it to
392 -f FILENAME: instead of printing the output to the screen, redirect it to
348 the given file. The file is always overwritten, though IPython asks for
393 the given file. The file is always overwritten, though IPython asks for
@@ -361,7 +406,7 b" def magic_history(self, parameter_s = ''):"
361 if not self.shell.displayhook.do_full_cache:
406 if not self.shell.displayhook.do_full_cache:
362 print('This feature is only available if numbered prompts are in use.')
407 print('This feature is only available if numbered prompts are in use.')
363 return
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 # For brevity
411 # For brevity
367 history_manager = self.shell.history_manager
412 history_manager = self.shell.history_manager
@@ -383,53 +428,52 b" def magic_history(self, parameter_s = ''):"
383 close_at_end = True
428 close_at_end = True
384
429
385 print_nums = 'n' in opts
430 print_nums = 'n' in opts
386 print_outputs = 'o' in opts
431 get_output = 'o' in opts
387 pyprompts = 'p' in opts
432 pyprompts = 'p' in opts
388 # Raw history is the default
433 # Raw history is the default
389 raw = not('t' in opts)
434 raw = not('t' in opts)
390
435
391 default_length = 40
436 default_length = 40
392 pattern = None
437 pattern = None
438
439 # Glob search:
393 if 'g' in opts:
440 if 'g' in opts:
394 start = 1; stop = None
441 pattern = "*" + args + "*" if args else "*"
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
442
411 hist = history_manager.get_history(start, stop, raw, print_outputs)
443 # Display:
412
444 matches_current_session = []
413 width = len(str(max(hist.iterkeys())))
445 for session, line, s in history_manager.get_hist_search(pattern, raw):
414 line_sep = ['','\n']
446 if session == history_manager.session_number:
415
447 matches_current_session.append(line, s)
416 found = False
448 continue
417 if pattern is not None:
418 for session, line, s in history_manager.globsearch_db(pattern):
419 print("%d#%d: %s" %(session, line, s.expandtabs(4)), file=outfile)
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:
470 width = max(len(_format_lineno(s, l)) for s, l, _ in hist)
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)
427
471
428 for in_num, inline in sorted(hist.iteritems()):
472 for session, lineno, inline in hist:
429 # Print user history with tabs expanded to 4 spaces. The GUI clients
473 # Print user history with tabs expanded to 4 spaces. The GUI clients
430 # use hard tabs for easier usability in auto-indented code, but we want
474 # use hard tabs for easier usability in auto-indented code, but we want
431 # to produce PEP-8 compliant history for safe pasting into an editor.
475 # to produce PEP-8 compliant history for safe pasting into an editor.
432 if print_outputs:
476 if get_output:
433 inline, output = inline
477 inline, output = inline
434 inline = inline.expandtabs(4).rstrip()
478 inline = inline.expandtabs(4).rstrip()
435
479
@@ -437,15 +481,16 b" def magic_history(self, parameter_s = ''):"
437 continue
481 continue
438
482
439 multiline = "\n" in inline
483 multiline = "\n" in inline
484 line_sep = '\n' if multiline else ''
440 if print_nums:
485 if print_nums:
441 print('%s:%s' % (str(in_num).ljust(width), line_sep[multiline]),
486 print('%s:%s' % (_format_lineno(session, lineno).ljust(width),
442 file=outfile, end='')
487 line_sep[multiline]), file=outfile, end='')
443 if pyprompts:
488 if pyprompts:
444 print(">>> ", end="", file=outfile)
489 print(">>> ", end="", file=outfile)
445 if multiline:
490 if multiline:
446 inline = "\n... ".join(inline.splitlines()) + "\n..."
491 inline = "\n... ".join(inline.splitlines()) + "\n..."
447 print(inline, file=outfile)
492 print(inline, file=outfile)
448 if print_outputs and output:
493 if get_output and output:
449 print(repr(output), file=outfile)
494 print(repr(output), file=outfile)
450
495
451 if close_at_end:
496 if close_at_end:
@@ -1551,7 +1551,7 b' class InteractiveShell(Configurable, Magic):'
1551 readline.set_history_length(self.history_length)
1551 readline.set_history_length(self.history_length)
1552
1552
1553 # Load the last 1000 lines from history
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 if cell.strip(): # Ignore blank lines
1555 if cell.strip(): # Ignore blank lines
1556 for line in cell.splitlines():
1556 for line in cell.splitlines():
1557 readline.add_history(line)
1557 readline.add_history(line)
@@ -165,14 +165,15 b' python-profiler package from non-free.""")'
165 out.sort()
165 out.sort()
166 return out
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 """Return as a string a set of input history slices.
169 """Return as a string a set of input history slices.
170
170
171 Inputs:
171 Inputs:
172
172
173 - slices: the set of slices is given as a list of strings (like
173 - range_str: the set of slices is given as a string, like
174 ['1','4:8','9'], since this function is for use by magic functions
174 "~5#6-~4#2 4:8 9", since this function is for use by magic functions
175 which get their arguments as strings.
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 Optional inputs:
178 Optional inputs:
178
179
@@ -184,21 +185,9 b' python-profiler package from non-free.""")'
184 N:M -> standard python form, means including items N...(M-1).
185 N:M -> standard python form, means including items N...(M-1).
185
186
186 N-M -> include items N..M (closed endpoint)."""
187 N-M -> include items N..M (closed endpoint)."""
187 history_manager = self.shell.history_manager
188 lines = self.shell.history_manager.\
188
189 get_hist_from_rangestr(range_str, raw=raw)
189 cmds = []
190 return "\n".join(x for _, _, x in lines)
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
202
191
203 def arg_err(self,func):
192 def arg_err(self,func):
204 """Print docstring if incorrect arguments were passed"""
193 """Print docstring if incorrect arguments were passed"""
@@ -2036,11 +2025,11 b' Currently the magic system has the following functions:\\n"""'
2036 if len(args) == 1:
2025 if len(args) == 1:
2037 raise UsageError(
2026 raise UsageError(
2038 "%macro insufficient args; usage '%macro name n1-n2 n3-4...")
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 #print 'rng',ranges # dbg
2030 #print 'rng',ranges # dbg
2042 lines = self.extract_input_slices(ranges,'r' in opts)
2031 lines = self.extract_input_lines(ranges,'r' in opts)
2043 macro = Macro("\n".join(lines))
2032 macro = Macro(lines)
2044 self.shell.define_macro(name, macro)
2033 self.shell.define_macro(name, macro)
2045 print 'Macro `%s` created. To execute, type its name (without quotes).' % name
2034 print 'Macro `%s` created. To execute, type its name (without quotes).' % name
2046 print 'Macro contents:'
2035 print 'Macro contents:'
@@ -2067,7 +2056,7 b' Currently the magic system has the following functions:\\n"""'
2067 it asks for confirmation before overwriting existing files."""
2056 it asks for confirmation before overwriting existing files."""
2068
2057
2069 opts,args = self.parse_options(parameter_s,'r',mode='list')
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 if not fname.endswith('.py'):
2060 if not fname.endswith('.py'):
2072 fname += '.py'
2061 fname += '.py'
2073 if os.path.isfile(fname):
2062 if os.path.isfile(fname):
@@ -2075,7 +2064,7 b' Currently the magic system has the following functions:\\n"""'
2075 if ans.lower() not in ['y','yes']:
2064 if ans.lower() not in ['y','yes']:
2076 print 'Operation cancelled.'
2065 print 'Operation cancelled.'
2077 return
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 with open(fname,'w') as f:
2068 with open(fname,'w') as f:
2080 f.write(cmds)
2069 f.write(cmds)
2081 print 'The following commands were written to file `%s`:' % fname
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 opts,args = self.parse_options(parameter_s,'prxn:')
2251 opts,args = self.parse_options(parameter_s,'prxn:')
2263 # Set a few locals from the options for convenience:
2252 # Set a few locals from the options for convenience:
2264 opts_p = opts.has_key('p')
2253 opts_prev = 'p' in opts
2265 opts_r = opts.has_key('r')
2254 opts_raw = 'r' in opts
2266
2255
2267 # Default line number value
2256 # Default line number value
2268 lineno = opts.get('n',None)
2257 lineno = opts.get('n',None)
2269
2258
2270 if opts_p:
2259 if opts_prev:
2271 args = '_%s' % last_call[0]
2260 args = '_%s' % last_call[0]
2272 if not self.shell.user_ns.has_key(args):
2261 if not self.shell.user_ns.has_key(args):
2273 args = last_call[1]
2262 args = last_call[1]
@@ -2276,7 +2265,7 b' Currently the magic system has the following functions:\\n"""'
2276 # let it be clobbered by successive '-p' calls.
2265 # let it be clobbered by successive '-p' calls.
2277 try:
2266 try:
2278 last_call[0] = self.shell.displayhook.prompt_count
2267 last_call[0] = self.shell.displayhook.prompt_count
2279 if not opts_p:
2268 if not opts_prev:
2280 last_call[1] = parameter_s
2269 last_call[1] = parameter_s
2281 except:
2270 except:
2282 pass
2271 pass
@@ -2290,8 +2279,7 b' Currently the magic system has the following functions:\\n"""'
2290 # Mode where user specifies ranges of lines, like in %macro.
2279 # Mode where user specifies ranges of lines, like in %macro.
2291 # This means that you can't edit files whose names begin with
2280 # This means that you can't edit files whose names begin with
2292 # numbers this way. Tough.
2281 # numbers this way. Tough.
2293 ranges = args.split()
2282 data = self.extract_input_lines(args, opts_raw)
2294 data = '\n'.join(self.extract_input_slices(ranges,opts_r))
2295 elif args.endswith('.py'):
2283 elif args.endswith('.py'):
2296 filename = make_filename(args)
2284 filename = make_filename(args)
2297 use_temp = False
2285 use_temp = False
General Comments 0
You need to be logged in to leave comments. Login now