##// 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
@@ -122,47 +122,15 b' class HistoryManager(Configurable):'
122 122 self.db.execute("""UPDATE singletons SET value=? WHERE
123 123 name='session_number'""", (self.session_number+1,))
124 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 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,42 +138,14 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,))
175
176 def get_history(self, start=1, stop=None, raw=False, output=True):
177 """Get the history list.
178
179 Get the input and output history.
180
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
141 toget = "source_raw" if raw else source
142 return self.db.execute("SELECT session, line, " +toget+ \
143 "FROM history WHERE" +toget+ "GLOB ?", (pattern,))
144
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
209 149
210 150 n = len(input_hist)
211 151 if start < 0:
@@ -215,13 +155,69 b' class HistoryManager(Configurable):'
215 155 elif stop < 0:
216 156 stop += n
217 157
218 hist = {}
219 158 for i in range(start, stop):
220 159 if output:
221 hist[i] = (input_hist[i], output_hist.get(i))
160 line = (input_hist[i], self.output_hist.get(i))
222 161 else:
223 hist[i] = input_hist[i]
224 return hist
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.
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 222 def store_inputs(self, line_num, source, source_raw=None):
227 223 """Store source and raw input in history and create input cache
@@ -304,6 +300,52 b' class HistoryManager(Configurable):'
304 300 self.output_hist.clear()
305 301 # The directory history can't be completely empty
306 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 350 @testdec.skip_doctest
309 351 def magic_history(self, parameter_s = ''):
@@ -343,6 +385,9 b" def magic_history(self, parameter_s = ''):"
343 385 -g: treat the arg as a pattern to grep for in (full) history.
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).
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 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
@@ -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
438
439 # Glob search:
393 440 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
441 pattern = "*" + args + "*" if args else "*"
410 442
411 hist = history_manager.get_history(start, stop, raw, print_outputs)
412
413 width = len(str(max(hist.iterkeys())))
414 line_sep = ['','\n']
415
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