##// END OF EJS Templates
Tweak to history_manager.store_inputs to pass tests.
Thomas Kluyver -
Show More
@@ -1,656 +1,656 b''
1 """ History related magics and functionality """
1 """ History related magics and functionality """
2 #-----------------------------------------------------------------------------
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2010 The IPython Development Team.
3 # Copyright (C) 2010 The IPython Development Team.
4 #
4 #
5 # Distributed under the terms of the BSD License.
5 # Distributed under the terms of the BSD License.
6 #
6 #
7 # The full license is in the file COPYING.txt, distributed with this software.
7 # The full license is in the file COPYING.txt, distributed with this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 from __future__ import print_function
13 from __future__ import print_function
14
14
15 # Stdlib imports
15 # Stdlib imports
16 import datetime
16 import datetime
17 import json
17 import json
18 import os
18 import os
19 import re
19 import re
20 import sqlite3
20 import sqlite3
21
21
22 from collections import defaultdict
22 from collections import defaultdict
23
23
24 # Our own packages
24 # Our own packages
25 from IPython.config.configurable import Configurable
25 from IPython.config.configurable import Configurable
26 import IPython.utils.io
26 import IPython.utils.io
27
27
28 from IPython.testing import decorators as testdec
28 from IPython.testing import decorators as testdec
29 from IPython.utils.io import ask_yes_no
29 from IPython.utils.io import ask_yes_no
30 from IPython.utils.traitlets import Bool, Dict, Instance, Int, List, Unicode
30 from IPython.utils.traitlets import Bool, Dict, Instance, Int, List, Unicode
31 from IPython.utils.warn import warn
31 from IPython.utils.warn import warn
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Classes and functions
34 # Classes and functions
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 class HistoryManager(Configurable):
37 class HistoryManager(Configurable):
38 """A class to organize all history-related functionality in one place.
38 """A class to organize all history-related functionality in one place.
39 """
39 """
40 # Public interface
40 # Public interface
41
41
42 # An instance of the IPython shell we are attached to
42 # An instance of the IPython shell we are attached to
43 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
43 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
44 # Lists to hold processed and raw history. These start with a blank entry
44 # Lists to hold processed and raw history. These start with a blank entry
45 # so that we can index them starting from 1
45 # so that we can index them starting from 1
46 input_hist_parsed = List([""])
46 input_hist_parsed = List([""])
47 input_hist_raw = List([""])
47 input_hist_raw = List([""])
48 # A list of directories visited during session
48 # A list of directories visited during session
49 dir_hist = List()
49 dir_hist = List()
50 # A dict of output history, keyed with ints from the shell's
50 # A dict of output history, keyed with ints from the shell's
51 # execution count. If there are several outputs from one command,
51 # execution count. If there are several outputs from one command,
52 # only the last one is stored.
52 # only the last one is stored.
53 output_hist = Dict()
53 output_hist = Dict()
54 # Contains all outputs, in lists of reprs.
54 # Contains all outputs, in lists of reprs.
55 output_hist_reprs = Instance(defaultdict)
55 output_hist_reprs = Instance(defaultdict)
56
56
57 # String holding the path to the history file
57 # String holding the path to the history file
58 hist_file = Unicode()
58 hist_file = Unicode()
59 # The SQLite database
59 # The SQLite database
60 db = Instance(sqlite3.Connection)
60 db = Instance(sqlite3.Connection)
61 # The number of the current session in the history database
61 # The number of the current session in the history database
62 session_number = Int()
62 session_number = Int()
63 # Should we log output to the database? (default no)
63 # Should we log output to the database? (default no)
64 db_log_output = Bool(False, config=True)
64 db_log_output = Bool(False, config=True)
65 # Write to database every x commands (higher values save disk access & power)
65 # Write to database every x commands (higher values save disk access & power)
66 # Values of 1 or less effectively disable caching.
66 # Values of 1 or less effectively disable caching.
67 db_cache_size = Int(0, config=True)
67 db_cache_size = Int(0, config=True)
68 # The input and output caches
68 # The input and output caches
69 db_input_cache = List()
69 db_input_cache = List()
70 db_output_cache = List()
70 db_output_cache = List()
71
71
72 # Private interface
72 # Private interface
73 # Variables used to store the three last inputs from the user. On each new
73 # Variables used to store the three last inputs from the user. On each new
74 # history update, we populate the user's namespace with these, shifted as
74 # history update, we populate the user's namespace with these, shifted as
75 # necessary.
75 # necessary.
76 _i00, _i, _ii, _iii = '','','',''
76 _i00, _i, _ii, _iii = '','','',''
77
77
78 # A set with all forms of the exit command, so that we don't store them in
78 # A set with all forms of the exit command, so that we don't store them in
79 # the history (it's annoying to rewind the first entry and land on an exit
79 # the history (it's annoying to rewind the first entry and land on an exit
80 # call).
80 # call).
81 _exit_commands = None
81 _exit_commands = None
82
82
83 def __init__(self, shell, config=None):
83 def __init__(self, shell, config=None):
84 """Create a new history manager associated with a shell instance.
84 """Create a new history manager associated with a shell instance.
85 """
85 """
86 # We need a pointer back to the shell for various tasks.
86 # We need a pointer back to the shell for various tasks.
87 super(HistoryManager, self).__init__(shell=shell, config=config)
87 super(HistoryManager, self).__init__(shell=shell, config=config)
88
88
89 # list of visited directories
89 # list of visited directories
90 try:
90 try:
91 self.dir_hist = [os.getcwd()]
91 self.dir_hist = [os.getcwd()]
92 except OSError:
92 except OSError:
93 self.dir_hist = []
93 self.dir_hist = []
94
94
95 # Now the history file
95 # Now the history file
96 if shell.profile:
96 if shell.profile:
97 histfname = 'history-%s' % shell.profile
97 histfname = 'history-%s' % shell.profile
98 else:
98 else:
99 histfname = 'history'
99 histfname = 'history'
100 self.hist_file = os.path.join(shell.ipython_dir, histfname + '.sqlite')
100 self.hist_file = os.path.join(shell.ipython_dir, histfname + '.sqlite')
101 self.init_db()
101 self.init_db()
102 self.new_session()
102 self.new_session()
103
103
104 self._i00, self._i, self._ii, self._iii = '','','',''
104 self._i00, self._i, self._ii, self._iii = '','','',''
105 self.output_hist_reprs = defaultdict(list)
105 self.output_hist_reprs = defaultdict(list)
106
106
107 self._exit_commands = set(['Quit', 'quit', 'Exit', 'exit', '%Quit',
107 self._exit_commands = set(['Quit', 'quit', 'Exit', 'exit', '%Quit',
108 '%quit', '%Exit', '%exit'])
108 '%quit', '%Exit', '%exit'])
109
109
110 def init_db(self):
110 def init_db(self):
111 """Connect to the database, and create tables if necessary."""
111 """Connect to the database, and create tables if necessary."""
112 self.db = sqlite3.connect(self.hist_file)
112 self.db = sqlite3.connect(self.hist_file)
113 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
113 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
114 primary key autoincrement, start timestamp,
114 primary key autoincrement, start timestamp,
115 end timestamp, num_cmds integer, remark text)""")
115 end timestamp, num_cmds integer, remark text)""")
116 self.db.execute("""CREATE TABLE IF NOT EXISTS history
116 self.db.execute("""CREATE TABLE IF NOT EXISTS history
117 (session integer, line integer, source text, source_raw text,
117 (session integer, line integer, source text, source_raw text,
118 PRIMARY KEY (session, line))""")
118 PRIMARY KEY (session, line))""")
119 # Output history is optional, but ensure the table's there so it can be
119 # Output history is optional, but ensure the table's there so it can be
120 # enabled later.
120 # enabled later.
121 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
121 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
122 (session integer, line integer, output text,
122 (session integer, line integer, output text,
123 PRIMARY KEY (session, line))""")
123 PRIMARY KEY (session, line))""")
124 self.db.commit()
124 self.db.commit()
125
125
126 def new_session(self):
126 def new_session(self):
127 """Get a new session number."""
127 """Get a new session number."""
128 with self.db:
128 with self.db:
129 cur = self.db.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
129 cur = self.db.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
130 NULL, "") """, (datetime.datetime.now(),))
130 NULL, "") """, (datetime.datetime.now(),))
131 self.session_number = cur.lastrowid
131 self.session_number = cur.lastrowid
132
132
133 def end_session(self):
133 def end_session(self):
134 """Close the database session, filling in the end time and line count."""
134 """Close the database session, filling in the end time and line count."""
135 self.writeout_cache()
135 self.writeout_cache()
136 with self.db:
136 with self.db:
137 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
137 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
138 session==?""", (datetime.datetime.now(),
138 session==?""", (datetime.datetime.now(),
139 len(self.input_hist_parsed)-1, self.session_number))
139 len(self.input_hist_parsed)-1, self.session_number))
140 self.session_number = 0
140 self.session_number = 0
141
141
142 def name_session(self, name):
142 def name_session(self, name):
143 """Give the current session a name in the history database."""
143 """Give the current session a name in the history database."""
144 with self.db:
144 with self.db:
145 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
145 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
146 (name, self.session_number))
146 (name, self.session_number))
147
147
148 def reset(self, new_session=True):
148 def reset(self, new_session=True):
149 """Clear the session history, releasing all object references, and
149 """Clear the session history, releasing all object references, and
150 optionally open a new session."""
150 optionally open a new session."""
151 if self.session_number:
151 if self.session_number:
152 self.end_session()
152 self.end_session()
153 self.input_hist_parsed[:] = [""]
153 self.input_hist_parsed[:] = [""]
154 self.input_hist_raw[:] = [""]
154 self.input_hist_raw[:] = [""]
155 self.output_hist.clear()
155 self.output_hist.clear()
156 # The directory history can't be completely empty
156 # The directory history can't be completely empty
157 self.dir_hist[:] = [os.getcwd()]
157 self.dir_hist[:] = [os.getcwd()]
158
158
159 if new_session:
159 if new_session:
160 self.new_session()
160 self.new_session()
161
161
162 ## -------------------------------
162 ## -------------------------------
163 ## Methods for retrieving history:
163 ## Methods for retrieving history:
164 ## -------------------------------
164 ## -------------------------------
165 def _get_hist_sql(self, sql, params, raw=True, output=False):
165 def _get_hist_sql(self, sql, params, raw=True, output=False):
166 """Prepares and runs an SQL query for the history database.
166 """Prepares and runs an SQL query for the history database.
167
167
168 Parameters
168 Parameters
169 ----------
169 ----------
170 sql : str
170 sql : str
171 Any filtering expressions to go after SELECT ... FROM ...
171 Any filtering expressions to go after SELECT ... FROM ...
172 params : tuple
172 params : tuple
173 Parameters passed to the SQL query (to replace "?")
173 Parameters passed to the SQL query (to replace "?")
174 raw : bool
174 raw : bool
175 If True, get raw input.
175 If True, get raw input.
176 output : bool
176 output : bool
177 If True, include output where available.
177 If True, include output where available.
178
178
179 Returns
179 Returns
180 -------
180 -------
181 An iterator over 3-tuples: (session, line_number, command), or if output
181 An iterator over 3-tuples: (session, line_number, command), or if output
182 is True, (session, line_number, (command, output)).
182 is True, (session, line_number, (command, output)).
183 """
183 """
184 toget = 'source_raw' if raw else 'source'
184 toget = 'source_raw' if raw else 'source'
185 sqlfrom = "history"
185 sqlfrom = "history"
186 if output:
186 if output:
187 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
187 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
188 toget = "history.%s, output_history.output" % toget
188 toget = "history.%s, output_history.output" % toget
189 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
189 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
190 (toget, sqlfrom) + sql, params)
190 (toget, sqlfrom) + sql, params)
191 if output: # Regroup into 3-tuples, and parse JSON
191 if output: # Regroup into 3-tuples, and parse JSON
192 loads = lambda out: json.loads(out) if out else None
192 loads = lambda out: json.loads(out) if out else None
193 return ((ses, lin, (inp, loads(out))) \
193 return ((ses, lin, (inp, loads(out))) \
194 for ses, lin, inp, out in cur)
194 for ses, lin, inp, out in cur)
195 return cur
195 return cur
196
196
197
197
198 def get_hist_tail(self, n=10, raw=True, output=False):
198 def get_hist_tail(self, n=10, raw=True, output=False):
199 """Get the last n lines from the history database."""
199 """Get the last n lines from the history database."""
200 self.writeout_cache()
200 self.writeout_cache()
201 cur = self._get_hist_sql("ORDER BY session DESC, line DESC LIMIT ?",
201 cur = self._get_hist_sql("ORDER BY session DESC, line DESC LIMIT ?",
202 (n,), raw=raw, output=output)
202 (n,), raw=raw, output=output)
203 return reversed(list(cur))
203 return reversed(list(cur))
204
204
205 def get_hist_search(self, pattern="*", raw=True, search_raw=True,
205 def get_hist_search(self, pattern="*", raw=True, search_raw=True,
206 output=False):
206 output=False):
207 """Search the database using unix glob-style matching (wildcards * and
207 """Search the database using unix glob-style matching (wildcards * and
208 ?, escape using \).
208 ?, escape using \).
209
209
210 Returns
210 Returns
211 -------
211 -------
212 An iterator over tuples: (session, line_number, command)
212 An iterator over tuples: (session, line_number, command)
213 """
213 """
214 tosearch = "source_raw" if search_raw else "source"
214 tosearch = "source_raw" if search_raw else "source"
215 if output:
215 if output:
216 tosearch = "history." + tosearch
216 tosearch = "history." + tosearch
217 self.writeout_cache()
217 self.writeout_cache()
218 return self._get_hist_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
218 return self._get_hist_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
219 raw=raw, output=output)
219 raw=raw, output=output)
220
220
221 def _get_hist_session(self, start=1, stop=None, raw=True, output=False):
221 def _get_hist_session(self, start=1, stop=None, raw=True, output=False):
222 """Get input and output history from the current session. Called by
222 """Get input and output history from the current session. Called by
223 get_history, and takes similar parameters."""
223 get_history, and takes similar parameters."""
224 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
224 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
225
225
226 n = len(input_hist)
226 n = len(input_hist)
227 if start < 0:
227 if start < 0:
228 start += n
228 start += n
229 if not stop:
229 if not stop:
230 stop = n
230 stop = n
231 elif stop < 0:
231 elif stop < 0:
232 stop += n
232 stop += n
233
233
234 for i in range(start, stop):
234 for i in range(start, stop):
235 if output:
235 if output:
236 line = (input_hist[i], self.output_hist_reprs.get(i))
236 line = (input_hist[i], self.output_hist_reprs.get(i))
237 else:
237 else:
238 line = input_hist[i]
238 line = input_hist[i]
239 yield (0, i, line)
239 yield (0, i, line)
240
240
241 def get_history(self, session=0, start=1, stop=None, raw=True,output=False):
241 def get_history(self, session=0, start=1, stop=None, raw=True,output=False):
242 """Retrieve input by session.
242 """Retrieve input by session.
243
243
244 Parameters
244 Parameters
245 ----------
245 ----------
246 session : int
246 session : int
247 Session number to retrieve. The current session is 0, and negative
247 Session number to retrieve. The current session is 0, and negative
248 numbers count back from current session, so -1 is previous session.
248 numbers count back from current session, so -1 is previous session.
249 start : int
249 start : int
250 First line to retrieve.
250 First line to retrieve.
251 stop : int
251 stop : int
252 End of line range (excluded from output itself). If None, retrieve
252 End of line range (excluded from output itself). If None, retrieve
253 to the end of the session.
253 to the end of the session.
254 raw : bool
254 raw : bool
255 If True, return untranslated input
255 If True, return untranslated input
256 output : bool
256 output : bool
257 If True, attempt to include output. This will be 'real' Python
257 If True, attempt to include output. This will be 'real' Python
258 objects for the current session, or text reprs from previous
258 objects for the current session, or text reprs from previous
259 sessions if db_log_output was enabled at the time. Where no output
259 sessions if db_log_output was enabled at the time. Where no output
260 is found, None is used.
260 is found, None is used.
261
261
262 Returns
262 Returns
263 -------
263 -------
264 An iterator over the desired lines. Each line is a 3-tuple, either
264 An iterator over the desired lines. Each line is a 3-tuple, either
265 (session, line, input) if output is False, or
265 (session, line, input) if output is False, or
266 (session, line, (input, output)) if output is True.
266 (session, line, (input, output)) if output is True.
267 """
267 """
268 if session == 0 or session==self.session_number: # Current session
268 if session == 0 or session==self.session_number: # Current session
269 return self._get_hist_session(start, stop, raw, output)
269 return self._get_hist_session(start, stop, raw, output)
270 if session < 0:
270 if session < 0:
271 session += self.session_number
271 session += self.session_number
272
272
273 if stop:
273 if stop:
274 lineclause = "line >= ? AND line < ?"
274 lineclause = "line >= ? AND line < ?"
275 params = (session, start, stop)
275 params = (session, start, stop)
276 else:
276 else:
277 lineclause = "line>=?"
277 lineclause = "line>=?"
278 params = (session, start)
278 params = (session, start)
279
279
280 return self._get_hist_sql("WHERE session==? AND %s""" % lineclause,
280 return self._get_hist_sql("WHERE session==? AND %s""" % lineclause,
281 params, raw=raw, output=output)
281 params, raw=raw, output=output)
282
282
283 def get_hist_from_rangestr(self, rangestr, raw=True, output=False):
283 def get_hist_from_rangestr(self, rangestr, raw=True, output=False):
284 """Get lines of history from a string of ranges, as used by magic
284 """Get lines of history from a string of ranges, as used by magic
285 commands %hist, %save, %macro, etc."""
285 commands %hist, %save, %macro, etc."""
286 for sess, s, e in extract_hist_ranges(rangestr):
286 for sess, s, e in extract_hist_ranges(rangestr):
287 for line in self.get_history(sess, s, e, raw=raw, output=output):
287 for line in self.get_history(sess, s, e, raw=raw, output=output):
288 yield line
288 yield line
289
289
290 ## ----------------------------
290 ## ----------------------------
291 ## Methods for storing history:
291 ## Methods for storing history:
292 ## ----------------------------
292 ## ----------------------------
293 def store_inputs(self, line_num, source, source_raw=None):
293 def store_inputs(self, line_num, source, source_raw=None):
294 """Store source and raw input in history and create input cache
294 """Store source and raw input in history and create input cache
295 variables _i*.
295 variables _i*.
296
296
297 Parameters
297 Parameters
298 ----------
298 ----------
299 line_num : int
299 line_num : int
300 The prompt number of this input.
300 The prompt number of this input.
301
301
302 source : str
302 source : str
303 Python input.
303 Python input.
304
304
305 source_raw : str, optional
305 source_raw : str, optional
306 If given, this is the raw input without any IPython transformations
306 If given, this is the raw input without any IPython transformations
307 applied to it. If not given, ``source`` is used.
307 applied to it. If not given, ``source`` is used.
308 """
308 """
309 if source_raw is None:
309 if source_raw is None:
310 source_raw = source
310 source_raw = source
311 source = source.rstrip()
311 source = source.rstrip('\n')
312 source_raw = source_raw.rstrip()
312 source_raw = source_raw.rstrip('\n')
313
313
314 # do not store exit/quit commands
314 # do not store exit/quit commands
315 if source_raw.strip() in self._exit_commands:
315 if source_raw.strip() in self._exit_commands:
316 return
316 return
317
317
318 self.input_hist_parsed.append(source)
318 self.input_hist_parsed.append(source)
319 self.input_hist_raw.append(source_raw)
319 self.input_hist_raw.append(source_raw)
320
320
321 self.db_input_cache.append((self.session_number, line_num,
321 self.db_input_cache.append((self.session_number, line_num,
322 source, source_raw))
322 source, source_raw))
323 # Trigger to flush cache and write to DB.
323 # Trigger to flush cache and write to DB.
324 if len(self.db_input_cache) >= self.db_cache_size:
324 if len(self.db_input_cache) >= self.db_cache_size:
325 self.writeout_cache()
325 self.writeout_cache()
326
326
327 # update the auto _i variables
327 # update the auto _i variables
328 self._iii = self._ii
328 self._iii = self._ii
329 self._ii = self._i
329 self._ii = self._i
330 self._i = self._i00
330 self._i = self._i00
331 self._i00 = source_raw
331 self._i00 = source_raw
332
332
333 # hackish access to user namespace to create _i1,_i2... dynamically
333 # hackish access to user namespace to create _i1,_i2... dynamically
334 new_i = '_i%s' % line_num
334 new_i = '_i%s' % line_num
335 to_main = {'_i': self._i,
335 to_main = {'_i': self._i,
336 '_ii': self._ii,
336 '_ii': self._ii,
337 '_iii': self._iii,
337 '_iii': self._iii,
338 new_i : self._i00 }
338 new_i : self._i00 }
339 self.shell.user_ns.update(to_main)
339 self.shell.user_ns.update(to_main)
340
340
341 def store_output(self, line_num):
341 def store_output(self, line_num):
342 """If database output logging is enabled, this saves all the
342 """If database output logging is enabled, this saves all the
343 outputs from the indicated prompt number to the database. It's
343 outputs from the indicated prompt number to the database. It's
344 called by run_cell after code has been executed."""
344 called by run_cell after code has been executed."""
345 if (not self.db_log_output) or not self.output_hist_reprs[line_num]:
345 if (not self.db_log_output) or not self.output_hist_reprs[line_num]:
346 return
346 return
347 output = json.dumps(self.output_hist_reprs[line_num])
347 output = json.dumps(self.output_hist_reprs[line_num])
348 db_row = (self.session_number, line_num, output)
348 db_row = (self.session_number, line_num, output)
349 if self.db_cache_size > 1:
349 if self.db_cache_size > 1:
350 self.db_output_cache.append(db_row)
350 self.db_output_cache.append(db_row)
351 else:
351 else:
352 with self.db:
352 with self.db:
353 self.db.execute("INSERT INTO output_history VALUES (?,?,?)", db_row)
353 self.db.execute("INSERT INTO output_history VALUES (?,?,?)", db_row)
354
354
355 def writeout_cache(self):
355 def writeout_cache(self):
356 #print(self.db_input_cache)
356 #print(self.db_input_cache)
357 with self.db:
357 with self.db:
358 self.db.executemany("INSERT INTO history VALUES (?, ?, ?, ?)",
358 self.db.executemany("INSERT INTO history VALUES (?, ?, ?, ?)",
359 self.db_input_cache)
359 self.db_input_cache)
360 self.db.executemany("INSERT INTO output_history VALUES (?, ?, ?)",
360 self.db.executemany("INSERT INTO output_history VALUES (?, ?, ?)",
361 self.db_output_cache)
361 self.db_output_cache)
362 self.db_input_cache = []
362 self.db_input_cache = []
363 self.db_output_cache = []
363 self.db_output_cache = []
364
364
365
365
366 # To match, e.g. ~5/8-~2/3
366 # To match, e.g. ~5/8-~2/3
367 range_re = re.compile(r"""
367 range_re = re.compile(r"""
368 ((?P<startsess>~?\d+)/)?
368 ((?P<startsess>~?\d+)/)?
369 (?P<start>\d+) # Only the start line num is compulsory
369 (?P<start>\d+) # Only the start line num is compulsory
370 ((?P<sep>[\-:])
370 ((?P<sep>[\-:])
371 ((?P<endsess>~?\d+)/)?
371 ((?P<endsess>~?\d+)/)?
372 (?P<end>\d+))?
372 (?P<end>\d+))?
373 """, re.VERBOSE)
373 """, re.VERBOSE)
374
374
375 def extract_hist_ranges(ranges_str):
375 def extract_hist_ranges(ranges_str):
376 """Turn a string of history ranges into 3-tuples of (session, start, stop).
376 """Turn a string of history ranges into 3-tuples of (session, start, stop).
377
377
378 Examples
378 Examples
379 --------
379 --------
380 list(extract_input_ranges("~8/5-~7/4 2"))
380 list(extract_input_ranges("~8/5-~7/4 2"))
381 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
381 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
382 """
382 """
383 for range_str in ranges_str.split():
383 for range_str in ranges_str.split():
384 rmatch = range_re.match(range_str)
384 rmatch = range_re.match(range_str)
385 if not rmatch:
385 if not rmatch:
386 continue
386 continue
387 start = int(rmatch.group("start"))
387 start = int(rmatch.group("start"))
388 end = rmatch.group("end")
388 end = rmatch.group("end")
389 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
389 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
390 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
390 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
391 end += 1
391 end += 1
392 startsess = rmatch.group("startsess") or "0"
392 startsess = rmatch.group("startsess") or "0"
393 endsess = rmatch.group("endsess") or startsess
393 endsess = rmatch.group("endsess") or startsess
394 startsess = int(startsess.replace("~","-"))
394 startsess = int(startsess.replace("~","-"))
395 endsess = int(endsess.replace("~","-"))
395 endsess = int(endsess.replace("~","-"))
396 assert endsess >= startsess
396 assert endsess >= startsess
397
397
398 if endsess == startsess:
398 if endsess == startsess:
399 yield (startsess, start, end)
399 yield (startsess, start, end)
400 continue
400 continue
401 # Multiple sessions in one range:
401 # Multiple sessions in one range:
402 yield (startsess, start, None)
402 yield (startsess, start, None)
403 for sess in range(startsess+1, endsess):
403 for sess in range(startsess+1, endsess):
404 yield (sess, 1, None)
404 yield (sess, 1, None)
405 yield (endsess, 1, end)
405 yield (endsess, 1, end)
406
406
407 def _format_lineno(session, line):
407 def _format_lineno(session, line):
408 """Helper function to format line numbers properly."""
408 """Helper function to format line numbers properly."""
409 if session == 0:
409 if session == 0:
410 return str(line)
410 return str(line)
411 return "%s#%s" % (session, line)
411 return "%s#%s" % (session, line)
412
412
413 @testdec.skip_doctest
413 @testdec.skip_doctest
414 def magic_history(self, parameter_s = ''):
414 def magic_history(self, parameter_s = ''):
415 """Print input history (_i<n> variables), with most recent last.
415 """Print input history (_i<n> variables), with most recent last.
416
416
417 %history -> print at most 40 inputs (some may be multi-line)\\
417 %history -> print at most 40 inputs (some may be multi-line)\\
418 %history n -> print at most n inputs\\
418 %history n -> print at most n inputs\\
419 %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\
419 %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\
420
420
421 By default, input history is printed without line numbers so it can be
421 By default, input history is printed without line numbers so it can be
422 directly pasted into an editor.
422 directly pasted into an editor.
423
423
424 With -n, each input's number <n> is shown, and is accessible as the
424 With -n, each input's number <n> is shown, and is accessible as the
425 automatically generated variable _i<n> as well as In[<n>]. Multi-line
425 automatically generated variable _i<n> as well as In[<n>]. Multi-line
426 statements are printed starting at a new line for easy copy/paste.
426 statements are printed starting at a new line for easy copy/paste.
427
427
428 Options:
428 Options:
429
429
430 -n: print line numbers for each input.
430 -n: print line numbers for each input.
431 This feature is only available if numbered prompts are in use.
431 This feature is only available if numbered prompts are in use.
432
432
433 -o: also print outputs for each input.
433 -o: also print outputs for each input.
434
434
435 -p: print classic '>>>' python prompts before each input. This is useful
435 -p: print classic '>>>' python prompts before each input. This is useful
436 for making documentation, and in conjunction with -o, for producing
436 for making documentation, and in conjunction with -o, for producing
437 doctest-ready output.
437 doctest-ready output.
438
438
439 -r: (default) print the 'raw' history, i.e. the actual commands you typed.
439 -r: (default) print the 'raw' history, i.e. the actual commands you typed.
440
440
441 -t: print the 'translated' history, as IPython understands it. IPython
441 -t: print the 'translated' history, as IPython understands it. IPython
442 filters your input and converts it all into valid Python source before
442 filters your input and converts it all into valid Python source before
443 executing it (things like magics or aliases are turned into function
443 executing it (things like magics or aliases are turned into function
444 calls, for example). With this option, you'll see the native history
444 calls, for example). With this option, you'll see the native history
445 instead of the user-entered version: '%cd /' will be seen as
445 instead of the user-entered version: '%cd /' will be seen as
446 'get_ipython().magic("%cd /")' instead of '%cd /'.
446 'get_ipython().magic("%cd /")' instead of '%cd /'.
447
447
448 -g: treat the arg as a pattern to grep for in (full) history.
448 -g: treat the arg as a pattern to grep for in (full) history.
449 This includes the saved history (almost all commands ever written).
449 This includes the saved history (almost all commands ever written).
450 Use '%hist -g' to show full saved history (may be very long).
450 Use '%hist -g' to show full saved history (may be very long).
451
451
452 -l: get the last n lines from all sessions. Specify n as a single arg, or
452 -l: get the last n lines from all sessions. Specify n as a single arg, or
453 the default is the last 10 lines.
453 the default is the last 10 lines.
454
454
455 -f FILENAME: instead of printing the output to the screen, redirect it to
455 -f FILENAME: instead of printing the output to the screen, redirect it to
456 the given file. The file is always overwritten, though IPython asks for
456 the given file. The file is always overwritten, though IPython asks for
457 confirmation first if it already exists.
457 confirmation first if it already exists.
458
458
459 Examples
459 Examples
460 --------
460 --------
461 ::
461 ::
462
462
463 In [6]: %hist -n 4 6
463 In [6]: %hist -n 4 6
464 4:a = 12
464 4:a = 12
465 5:print a**2
465 5:print a**2
466
466
467 """
467 """
468
468
469 if not self.shell.displayhook.do_full_cache:
469 if not self.shell.displayhook.do_full_cache:
470 print('This feature is only available if numbered prompts are in use.')
470 print('This feature is only available if numbered prompts are in use.')
471 return
471 return
472 opts,args = self.parse_options(parameter_s,'noprtglf:',mode='string')
472 opts,args = self.parse_options(parameter_s,'noprtglf:',mode='string')
473
473
474 # For brevity
474 # For brevity
475 history_manager = self.shell.history_manager
475 history_manager = self.shell.history_manager
476
476
477 def _format_lineno(session, line):
477 def _format_lineno(session, line):
478 """Helper function to format line numbers properly."""
478 """Helper function to format line numbers properly."""
479 if session in (0, history_manager.session_number):
479 if session in (0, history_manager.session_number):
480 return str(line)
480 return str(line)
481 return "%s/%s" % (session, line)
481 return "%s/%s" % (session, line)
482
482
483 # Check if output to specific file was requested.
483 # Check if output to specific file was requested.
484 try:
484 try:
485 outfname = opts['f']
485 outfname = opts['f']
486 except KeyError:
486 except KeyError:
487 outfile = IPython.utils.io.Term.cout # default
487 outfile = IPython.utils.io.Term.cout # default
488 # We don't want to close stdout at the end!
488 # We don't want to close stdout at the end!
489 close_at_end = False
489 close_at_end = False
490 else:
490 else:
491 if os.path.exists(outfname):
491 if os.path.exists(outfname):
492 if not ask_yes_no("File %r exists. Overwrite?" % outfname):
492 if not ask_yes_no("File %r exists. Overwrite?" % outfname):
493 print('Aborting.')
493 print('Aborting.')
494 return
494 return
495
495
496 outfile = open(outfname,'w')
496 outfile = open(outfname,'w')
497 close_at_end = True
497 close_at_end = True
498
498
499 print_nums = 'n' in opts
499 print_nums = 'n' in opts
500 get_output = 'o' in opts
500 get_output = 'o' in opts
501 pyprompts = 'p' in opts
501 pyprompts = 'p' in opts
502 # Raw history is the default
502 # Raw history is the default
503 raw = not('t' in opts)
503 raw = not('t' in opts)
504
504
505 default_length = 40
505 default_length = 40
506 pattern = None
506 pattern = None
507
507
508 if 'g' in opts: # Glob search
508 if 'g' in opts: # Glob search
509 pattern = "*" + args + "*" if args else "*"
509 pattern = "*" + args + "*" if args else "*"
510 hist = history_manager.get_hist_search(pattern, raw=raw,
510 hist = history_manager.get_hist_search(pattern, raw=raw,
511 output=get_output)
511 output=get_output)
512 elif 'l' in opts: # Get 'tail'
512 elif 'l' in opts: # Get 'tail'
513 try:
513 try:
514 n = int(args)
514 n = int(args)
515 except ValueError, IndexError:
515 except ValueError, IndexError:
516 n = 10
516 n = 10
517 hist = history_manager.get_hist_tail(n, raw=raw, output=get_output)
517 hist = history_manager.get_hist_tail(n, raw=raw, output=get_output)
518 else:
518 else:
519 if args: # Get history by ranges
519 if args: # Get history by ranges
520 hist = history_manager.get_hist_from_rangestr(args, raw, get_output)
520 hist = history_manager.get_hist_from_rangestr(args, raw, get_output)
521 else: # Just get history for the current session
521 else: # Just get history for the current session
522 hist = history_manager.get_history(raw=raw, output=get_output)
522 hist = history_manager.get_history(raw=raw, output=get_output)
523
523
524 # We could be displaying the entire history, so let's not try to pull it
524 # We could be displaying the entire history, so let's not try to pull it
525 # into a list in memory. Anything that needs more space will just misalign.
525 # into a list in memory. Anything that needs more space will just misalign.
526 width = 4
526 width = 4
527
527
528 for session, lineno, inline in hist:
528 for session, lineno, inline in hist:
529 # Print user history with tabs expanded to 4 spaces. The GUI clients
529 # Print user history with tabs expanded to 4 spaces. The GUI clients
530 # use hard tabs for easier usability in auto-indented code, but we want
530 # use hard tabs for easier usability in auto-indented code, but we want
531 # to produce PEP-8 compliant history for safe pasting into an editor.
531 # to produce PEP-8 compliant history for safe pasting into an editor.
532 if get_output:
532 if get_output:
533 inline, output = inline
533 inline, output = inline
534 inline = inline.expandtabs(4).rstrip()
534 inline = inline.expandtabs(4).rstrip()
535
535
536 multiline = "\n" in inline
536 multiline = "\n" in inline
537 line_sep = '\n' if multiline else ' '
537 line_sep = '\n' if multiline else ' '
538 if print_nums:
538 if print_nums:
539 print('%s:%s' % (_format_lineno(session, lineno).rjust(width),
539 print('%s:%s' % (_format_lineno(session, lineno).rjust(width),
540 line_sep), file=outfile, end='')
540 line_sep), file=outfile, end='')
541 if pyprompts:
541 if pyprompts:
542 print(">>> ", end="", file=outfile)
542 print(">>> ", end="", file=outfile)
543 if multiline:
543 if multiline:
544 inline = "\n... ".join(inline.splitlines()) + "\n..."
544 inline = "\n... ".join(inline.splitlines()) + "\n..."
545 print(inline, file=outfile)
545 print(inline, file=outfile)
546 if get_output and output:
546 if get_output and output:
547 print("\n".join(output), file=outfile)
547 print("\n".join(output), file=outfile)
548
548
549 if close_at_end:
549 if close_at_end:
550 outfile.close()
550 outfile.close()
551
551
552
552
553 def magic_rep(self, arg):
553 def magic_rep(self, arg):
554 r""" Repeat a command, or get command to input line for editing
554 r""" Repeat a command, or get command to input line for editing
555
555
556 - %rep (no arguments):
556 - %rep (no arguments):
557
557
558 Place a string version of last computation result (stored in the special '_'
558 Place a string version of last computation result (stored in the special '_'
559 variable) to the next input prompt. Allows you to create elaborate command
559 variable) to the next input prompt. Allows you to create elaborate command
560 lines without using copy-paste::
560 lines without using copy-paste::
561
561
562 In[1]: l = ["hei", "vaan"]
562 In[1]: l = ["hei", "vaan"]
563 In[2]: "".join(l)
563 In[2]: "".join(l)
564 Out[2]: heivaan
564 Out[2]: heivaan
565 In[3]: %rep
565 In[3]: %rep
566 In[4]: heivaan_ <== cursor blinking
566 In[4]: heivaan_ <== cursor blinking
567
567
568 %rep 45
568 %rep 45
569
569
570 Place history line 45 on the next input prompt. Use %hist to find
570 Place history line 45 on the next input prompt. Use %hist to find
571 out the number.
571 out the number.
572
572
573 %rep 1-4 6-7 3
573 %rep 1-4 6-7 3
574
574
575 Combine the specified lines into one cell, and place it on the next
575 Combine the specified lines into one cell, and place it on the next
576 input prompt. History slice syntax is the same as in %macro and %save.
576 input prompt. History slice syntax is the same as in %macro and %save.
577
577
578 %rep foo+bar
578 %rep foo+bar
579
579
580 If foo+bar can be evaluated in the user namespace, the result is
580 If foo+bar can be evaluated in the user namespace, the result is
581 placed at the next input prompt. Otherwise, the history is searched
581 placed at the next input prompt. Otherwise, the history is searched
582 for lines which contain that substring, and the most recent one is
582 for lines which contain that substring, and the most recent one is
583 placed at the next input prompt.
583 placed at the next input prompt.
584 """
584 """
585 if not arg: # Last output
585 if not arg: # Last output
586 self.set_next_input(str(self.shell.user_ns["_"]))
586 self.set_next_input(str(self.shell.user_ns["_"]))
587 return
587 return
588 # Get history range
588 # Get history range
589 histlines = self.history_manager.get_hist_from_rangestr(arg)
589 histlines = self.history_manager.get_hist_from_rangestr(arg)
590 cmd = "\n".join(x[2] for x in histlines)
590 cmd = "\n".join(x[2] for x in histlines)
591 if cmd:
591 if cmd:
592 self.set_next_input(cmd.rstrip())
592 self.set_next_input(cmd.rstrip())
593 return
593 return
594
594
595 try: # Variable in user namespace
595 try: # Variable in user namespace
596 cmd = str(eval(arg, self.shell.user_ns))
596 cmd = str(eval(arg, self.shell.user_ns))
597 except Exception: # Search for term in history
597 except Exception: # Search for term in history
598 histlines = self.history_manager.get_hist_search("*"+arg+"*")
598 histlines = self.history_manager.get_hist_search("*"+arg+"*")
599 for h in reversed([x[2] for x in histlines]):
599 for h in reversed([x[2] for x in histlines]):
600 if 'rep' in h:
600 if 'rep' in h:
601 continue
601 continue
602 self.set_next_input(h.rstrip())
602 self.set_next_input(h.rstrip())
603 return
603 return
604 else:
604 else:
605 self.set_next_input(cmd.rstrip())
605 self.set_next_input(cmd.rstrip())
606 print("Couldn't evaluate or find in history:", arg)
606 print("Couldn't evaluate or find in history:", arg)
607
607
608 def magic_rerun(self, parameter_s=''):
608 def magic_rerun(self, parameter_s=''):
609 """Re-run previous input
609 """Re-run previous input
610
610
611 By default, you can specify ranges of input history to be repeated
611 By default, you can specify ranges of input history to be repeated
612 (as with %hist). With no arguments, it will repeat the last line.
612 (as with %hist). With no arguments, it will repeat the last line.
613
613
614 Options:
614 Options:
615
615
616 -l <n> : Repeat the last n lines of input, not including the
616 -l <n> : Repeat the last n lines of input, not including the
617 current command.
617 current command.
618
618
619 -g foo : Repeat the most recent line which contains foo
619 -g foo : Repeat the most recent line which contains foo
620 """
620 """
621 opts, args = self.parse_options(parameter_s, 'l:g:', mode='string')
621 opts, args = self.parse_options(parameter_s, 'l:g:', mode='string')
622 if "l" in opts: # Last n lines
622 if "l" in opts: # Last n lines
623 n = int(opts['l']) + 1
623 n = int(opts['l']) + 1
624 hist = self.history_manager.get_hist_tail(n, raw=False)
624 hist = self.history_manager.get_hist_tail(n, raw=False)
625 elif "g" in opts: # Search
625 elif "g" in opts: # Search
626 p = "*"+opts['g']+"*"
626 p = "*"+opts['g']+"*"
627 hist = self.history_manager.get_hist_search(p, raw=False)
627 hist = self.history_manager.get_hist_search(p, raw=False)
628 hist = list(hist)[-2:]
628 hist = list(hist)[-2:]
629 elif args: # Specify history ranges
629 elif args: # Specify history ranges
630 hist = self.history_manager.get_hist_from_rangestr(args)
630 hist = self.history_manager.get_hist_from_rangestr(args)
631 else: # Last line
631 else: # Last line
632 hist = self.history_manager.get_hist_tail(2, raw=False)
632 hist = self.history_manager.get_hist_tail(2, raw=False)
633 hist = [x[2] for x in hist]
633 hist = [x[2] for x in hist]
634 if hist and parameter_s in hist[-1]:
634 if hist and parameter_s in hist[-1]:
635 hist = hist[:-1]
635 hist = hist[:-1]
636 if not hist:
636 if not hist:
637 print("No lines in history match specification")
637 print("No lines in history match specification")
638 return
638 return
639 histlines = "\n".join(hist)
639 histlines = "\n".join(hist)
640 print("=== Executing: ===")
640 print("=== Executing: ===")
641 print(histlines)
641 print(histlines)
642 print("=== Output: ===")
642 print("=== Output: ===")
643 self.run_source("\n".join(hist), symbol="exec")
643 self.run_source("\n".join(hist), symbol="exec")
644
644
645
645
646 def init_ipython(ip):
646 def init_ipython(ip):
647 ip.define_magic("rep", magic_rep)
647 ip.define_magic("rep", magic_rep)
648 ip.define_magic("recall", magic_rep)
648 ip.define_magic("recall", magic_rep)
649 ip.define_magic("rerun", magic_rerun)
649 ip.define_magic("rerun", magic_rerun)
650 ip.define_magic("r", magic_rerun)
650 ip.define_magic("r", magic_rerun)
651 ip.define_magic("hist",magic_history) # Alternative name
651 ip.define_magic("hist",magic_history) # Alternative name
652 ip.define_magic("history",magic_history)
652 ip.define_magic("history",magic_history)
653
653
654 # XXX - ipy_completers are in quarantine, need to be updated to new apis
654 # XXX - ipy_completers are in quarantine, need to be updated to new apis
655 #import ipy_completers
655 #import ipy_completers
656 #ipy_completers.quick_completer('%hist' ,'-g -t -r -n')
656 #ipy_completers.quick_completer('%hist' ,'-g -t -r -n')
General Comments 0
You need to be logged in to leave comments. Login now