##// END OF EJS Templates
add get_session_info to HistoryManager for querying session table...
MinRK -
Show More
@@ -1,801 +1,829 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 atexit
16 import atexit
17 import datetime
17 import datetime
18 import os
18 import os
19 import re
19 import re
20 import sqlite3
20 import sqlite3
21 import threading
21 import threading
22
22
23 # Our own packages
23 # Our own packages
24 from IPython.config.configurable import Configurable
24 from IPython.config.configurable import Configurable
25
25
26 from IPython.testing.skipdoctest import skip_doctest
26 from IPython.testing.skipdoctest import skip_doctest
27 from IPython.utils import io
27 from IPython.utils import io
28 from IPython.utils.traitlets import Bool, Dict, Instance, Int, List, Unicode
28 from IPython.utils.traitlets import Bool, Dict, Instance, Int, List, Unicode
29 from IPython.utils.warn import warn
29 from IPython.utils.warn import warn
30
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Classes and functions
32 # Classes and functions
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34
34
35 class HistoryManager(Configurable):
35 class HistoryManager(Configurable):
36 """A class to organize all history-related functionality in one place.
36 """A class to organize all history-related functionality in one place.
37 """
37 """
38 # Public interface
38 # Public interface
39
39
40 # An instance of the IPython shell we are attached to
40 # An instance of the IPython shell we are attached to
41 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
41 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
42 # Lists to hold processed and raw history. These start with a blank entry
42 # Lists to hold processed and raw history. These start with a blank entry
43 # so that we can index them starting from 1
43 # so that we can index them starting from 1
44 input_hist_parsed = List([""])
44 input_hist_parsed = List([""])
45 input_hist_raw = List([""])
45 input_hist_raw = List([""])
46 # A list of directories visited during session
46 # A list of directories visited during session
47 dir_hist = List()
47 dir_hist = List()
48 def _dir_hist_default(self):
48 def _dir_hist_default(self):
49 try:
49 try:
50 return [os.getcwdu()]
50 return [os.getcwdu()]
51 except OSError:
51 except OSError:
52 return []
52 return []
53
53
54 # A dict of output history, keyed with ints from the shell's
54 # A dict of output history, keyed with ints from the shell's
55 # execution count.
55 # execution count.
56 output_hist = Dict()
56 output_hist = Dict()
57 # The text/plain repr of outputs.
57 # The text/plain repr of outputs.
58 output_hist_reprs = Dict()
58 output_hist_reprs = Dict()
59
59
60 # String holding the path to the history file
60 # String holding the path to the history file
61 hist_file = Unicode(config=True)
61 hist_file = Unicode(config=True)
62
62
63 # The SQLite database
63 # The SQLite database
64 db = Instance(sqlite3.Connection)
64 db = Instance(sqlite3.Connection)
65 # The number of the current session in the history database
65 # The number of the current session in the history database
66 session_number = Int()
66 session_number = Int()
67 # Should we log output to the database? (default no)
67 # Should we log output to the database? (default no)
68 db_log_output = Bool(False, config=True)
68 db_log_output = Bool(False, config=True)
69 # Write to database every x commands (higher values save disk access & power)
69 # Write to database every x commands (higher values save disk access & power)
70 # Values of 1 or less effectively disable caching.
70 # Values of 1 or less effectively disable caching.
71 db_cache_size = Int(0, config=True)
71 db_cache_size = Int(0, config=True)
72 # The input and output caches
72 # The input and output caches
73 db_input_cache = List()
73 db_input_cache = List()
74 db_output_cache = List()
74 db_output_cache = List()
75
75
76 # History saving in separate thread
76 # History saving in separate thread
77 save_thread = Instance('IPython.core.history.HistorySavingThread')
77 save_thread = Instance('IPython.core.history.HistorySavingThread')
78 # N.B. Event is a function returning an instance of _Event.
78 # N.B. Event is a function returning an instance of _Event.
79 save_flag = Instance(threading._Event)
79 save_flag = Instance(threading._Event)
80
80
81 # Private interface
81 # Private interface
82 # Variables used to store the three last inputs from the user. On each new
82 # Variables used to store the three last inputs from the user. On each new
83 # history update, we populate the user's namespace with these, shifted as
83 # history update, we populate the user's namespace with these, shifted as
84 # necessary.
84 # necessary.
85 _i00 = Unicode(u'')
85 _i00 = Unicode(u'')
86 _i = Unicode(u'')
86 _i = Unicode(u'')
87 _ii = Unicode(u'')
87 _ii = Unicode(u'')
88 _iii = Unicode(u'')
88 _iii = Unicode(u'')
89
89
90 # A regex matching all forms of the exit command, so that we don't store
90 # A regex matching all forms of the exit command, so that we don't store
91 # them in the history (it's annoying to rewind the first entry and land on
91 # them in the history (it's annoying to rewind the first entry and land on
92 # an exit call).
92 # an exit call).
93 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
93 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
94
94
95 def __init__(self, shell, config=None, **traits):
95 def __init__(self, shell, config=None, **traits):
96 """Create a new history manager associated with a shell instance.
96 """Create a new history manager associated with a shell instance.
97 """
97 """
98 # We need a pointer back to the shell for various tasks.
98 # We need a pointer back to the shell for various tasks.
99 super(HistoryManager, self).__init__(shell=shell, config=config,
99 super(HistoryManager, self).__init__(shell=shell, config=config,
100 **traits)
100 **traits)
101
101
102 if self.hist_file == u'':
102 if self.hist_file == u'':
103 # No one has set the hist_file, yet.
103 # No one has set the hist_file, yet.
104 histfname = 'history'
104 histfname = 'history'
105 self.hist_file = os.path.join(shell.profile_dir.location, histfname + '.sqlite')
105 self.hist_file = os.path.join(shell.profile_dir.location, histfname + '.sqlite')
106
106
107 try:
107 try:
108 self.init_db()
108 self.init_db()
109 except sqlite3.DatabaseError:
109 except sqlite3.DatabaseError:
110 if os.path.isfile(self.hist_file):
110 if os.path.isfile(self.hist_file):
111 # Try to move the file out of the way.
111 # Try to move the file out of the way.
112 newpath = os.path.join(self.shell.profile_dir.location, "hist-corrupt.sqlite")
112 newpath = os.path.join(self.shell.profile_dir.location, "hist-corrupt.sqlite")
113 os.rename(self.hist_file, newpath)
113 os.rename(self.hist_file, newpath)
114 print("ERROR! History file wasn't a valid SQLite database.",
114 print("ERROR! History file wasn't a valid SQLite database.",
115 "It was moved to %s" % newpath, "and a new file created.")
115 "It was moved to %s" % newpath, "and a new file created.")
116 self.init_db()
116 self.init_db()
117 else:
117 else:
118 # The hist_file is probably :memory: or something else.
118 # The hist_file is probably :memory: or something else.
119 raise
119 raise
120
120
121 self.save_flag = threading.Event()
121 self.save_flag = threading.Event()
122 self.db_input_cache_lock = threading.Lock()
122 self.db_input_cache_lock = threading.Lock()
123 self.db_output_cache_lock = threading.Lock()
123 self.db_output_cache_lock = threading.Lock()
124 self.save_thread = HistorySavingThread(self)
124 self.save_thread = HistorySavingThread(self)
125 self.save_thread.start()
125 self.save_thread.start()
126
126
127 self.new_session()
127 self.new_session()
128
128
129
129
130 def init_db(self):
130 def init_db(self):
131 """Connect to the database, and create tables if necessary."""
131 """Connect to the database, and create tables if necessary."""
132 self.db = sqlite3.connect(self.hist_file)
132 # use detect_types so that timestamps return datetime objects
133 self.db = sqlite3.connect(self.hist_file, detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
133 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
134 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
134 primary key autoincrement, start timestamp,
135 primary key autoincrement, start timestamp,
135 end timestamp, num_cmds integer, remark text)""")
136 end timestamp, num_cmds integer, remark text)""")
136 self.db.execute("""CREATE TABLE IF NOT EXISTS history
137 self.db.execute("""CREATE TABLE IF NOT EXISTS history
137 (session integer, line integer, source text, source_raw text,
138 (session integer, line integer, source text, source_raw text,
138 PRIMARY KEY (session, line))""")
139 PRIMARY KEY (session, line))""")
139 # Output history is optional, but ensure the table's there so it can be
140 # Output history is optional, but ensure the table's there so it can be
140 # enabled later.
141 # enabled later.
141 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
142 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
142 (session integer, line integer, output text,
143 (session integer, line integer, output text,
143 PRIMARY KEY (session, line))""")
144 PRIMARY KEY (session, line))""")
144 self.db.commit()
145 self.db.commit()
145
146
146 def new_session(self, conn=None):
147 def new_session(self, conn=None):
147 """Get a new session number."""
148 """Get a new session number."""
148 if conn is None:
149 if conn is None:
149 conn = self.db
150 conn = self.db
150
151
151 with conn:
152 with conn:
152 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
153 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
153 NULL, "") """, (datetime.datetime.now(),))
154 NULL, "") """, (datetime.datetime.now(),))
154 self.session_number = cur.lastrowid
155 self.session_number = cur.lastrowid
155
156
156 def end_session(self):
157 def end_session(self):
157 """Close the database session, filling in the end time and line count."""
158 """Close the database session, filling in the end time and line count."""
158 self.writeout_cache()
159 self.writeout_cache()
159 with self.db:
160 with self.db:
160 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
161 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
161 session==?""", (datetime.datetime.now(),
162 session==?""", (datetime.datetime.now(),
162 len(self.input_hist_parsed)-1, self.session_number))
163 len(self.input_hist_parsed)-1, self.session_number))
163 self.session_number = 0
164 self.session_number = 0
164
165
165 def name_session(self, name):
166 def name_session(self, name):
166 """Give the current session a name in the history database."""
167 """Give the current session a name in the history database."""
167 with self.db:
168 with self.db:
168 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
169 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
169 (name, self.session_number))
170 (name, self.session_number))
170
171
171 def reset(self, new_session=True):
172 def reset(self, new_session=True):
172 """Clear the session history, releasing all object references, and
173 """Clear the session history, releasing all object references, and
173 optionally open a new session."""
174 optionally open a new session."""
174 self.output_hist.clear()
175 self.output_hist.clear()
175 # The directory history can't be completely empty
176 # The directory history can't be completely empty
176 self.dir_hist[:] = [os.getcwdu()]
177 self.dir_hist[:] = [os.getcwdu()]
177
178
178 if new_session:
179 if new_session:
179 if self.session_number:
180 if self.session_number:
180 self.end_session()
181 self.end_session()
181 self.input_hist_parsed[:] = [""]
182 self.input_hist_parsed[:] = [""]
182 self.input_hist_raw[:] = [""]
183 self.input_hist_raw[:] = [""]
183 self.new_session()
184 self.new_session()
184
185
185 ## -------------------------------
186 ## -------------------------------
186 ## Methods for retrieving history:
187 ## Methods for retrieving history:
187 ## -------------------------------
188 ## -------------------------------
188 def _run_sql(self, sql, params, raw=True, output=False):
189 def _run_sql(self, sql, params, raw=True, output=False):
189 """Prepares and runs an SQL query for the history database.
190 """Prepares and runs an SQL query for the history database.
190
191
191 Parameters
192 Parameters
192 ----------
193 ----------
193 sql : str
194 sql : str
194 Any filtering expressions to go after SELECT ... FROM ...
195 Any filtering expressions to go after SELECT ... FROM ...
195 params : tuple
196 params : tuple
196 Parameters passed to the SQL query (to replace "?")
197 Parameters passed to the SQL query (to replace "?")
197 raw, output : bool
198 raw, output : bool
198 See :meth:`get_range`
199 See :meth:`get_range`
199
200
200 Returns
201 Returns
201 -------
202 -------
202 Tuples as :meth:`get_range`
203 Tuples as :meth:`get_range`
203 """
204 """
204 toget = 'source_raw' if raw else 'source'
205 toget = 'source_raw' if raw else 'source'
205 sqlfrom = "history"
206 sqlfrom = "history"
206 if output:
207 if output:
207 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
208 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
208 toget = "history.%s, output_history.output" % toget
209 toget = "history.%s, output_history.output" % toget
209 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
210 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
210 (toget, sqlfrom) + sql, params)
211 (toget, sqlfrom) + sql, params)
211 if output: # Regroup into 3-tuples, and parse JSON
212 if output: # Regroup into 3-tuples, and parse JSON
212 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
213 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
213 return cur
214 return cur
214
215
215
216
217 def get_session_info(self, session=0):
218 """get info about a session
219
220 Parameters
221 ----------
222
223 session : int
224 Session number to retrieve. The current session is 0, and negative
225 numbers count back from current session, so -1 is previous session.
226
227 Returns
228 -------
229
230 (session_id [int], start [datetime], end [datetime], num_cmds [int], remark [unicode])
231
232 Sessions that are running or did not exit cleanly will have `end=None`
233 and `num_cmds=None`.
234
235 """
236
237 if session <= 0:
238 session += self.session_number
239
240 query = "SELECT * from sessions where session == ?"
241 return self.db.execute(query, (session,)).fetchone()
242
243
216 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
244 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
217 """Get the last n lines from the history database.
245 """Get the last n lines from the history database.
218
246
219 Parameters
247 Parameters
220 ----------
248 ----------
221 n : int
249 n : int
222 The number of lines to get
250 The number of lines to get
223 raw, output : bool
251 raw, output : bool
224 See :meth:`get_range`
252 See :meth:`get_range`
225 include_latest : bool
253 include_latest : bool
226 If False (default), n+1 lines are fetched, and the latest one
254 If False (default), n+1 lines are fetched, and the latest one
227 is discarded. This is intended to be used where the function
255 is discarded. This is intended to be used where the function
228 is called by a user command, which it should not return.
256 is called by a user command, which it should not return.
229
257
230 Returns
258 Returns
231 -------
259 -------
232 Tuples as :meth:`get_range`
260 Tuples as :meth:`get_range`
233 """
261 """
234 self.writeout_cache()
262 self.writeout_cache()
235 if not include_latest:
263 if not include_latest:
236 n += 1
264 n += 1
237 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
265 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
238 (n,), raw=raw, output=output)
266 (n,), raw=raw, output=output)
239 if not include_latest:
267 if not include_latest:
240 return reversed(list(cur)[1:])
268 return reversed(list(cur)[1:])
241 return reversed(list(cur))
269 return reversed(list(cur))
242
270
243 def search(self, pattern="*", raw=True, search_raw=True,
271 def search(self, pattern="*", raw=True, search_raw=True,
244 output=False):
272 output=False):
245 """Search the database using unix glob-style matching (wildcards
273 """Search the database using unix glob-style matching (wildcards
246 * and ?).
274 * and ?).
247
275
248 Parameters
276 Parameters
249 ----------
277 ----------
250 pattern : str
278 pattern : str
251 The wildcarded pattern to match when searching
279 The wildcarded pattern to match when searching
252 search_raw : bool
280 search_raw : bool
253 If True, search the raw input, otherwise, the parsed input
281 If True, search the raw input, otherwise, the parsed input
254 raw, output : bool
282 raw, output : bool
255 See :meth:`get_range`
283 See :meth:`get_range`
256
284
257 Returns
285 Returns
258 -------
286 -------
259 Tuples as :meth:`get_range`
287 Tuples as :meth:`get_range`
260 """
288 """
261 tosearch = "source_raw" if search_raw else "source"
289 tosearch = "source_raw" if search_raw else "source"
262 if output:
290 if output:
263 tosearch = "history." + tosearch
291 tosearch = "history." + tosearch
264 self.writeout_cache()
292 self.writeout_cache()
265 return self._run_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
293 return self._run_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
266 raw=raw, output=output)
294 raw=raw, output=output)
267
295
268 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
296 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
269 """Get input and output history from the current session. Called by
297 """Get input and output history from the current session. Called by
270 get_range, and takes similar parameters."""
298 get_range, and takes similar parameters."""
271 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
299 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
272
300
273 n = len(input_hist)
301 n = len(input_hist)
274 if start < 0:
302 if start < 0:
275 start += n
303 start += n
276 if not stop:
304 if not stop:
277 stop = n
305 stop = n
278 elif stop < 0:
306 elif stop < 0:
279 stop += n
307 stop += n
280
308
281 for i in range(start, stop):
309 for i in range(start, stop):
282 if output:
310 if output:
283 line = (input_hist[i], self.output_hist_reprs.get(i))
311 line = (input_hist[i], self.output_hist_reprs.get(i))
284 else:
312 else:
285 line = input_hist[i]
313 line = input_hist[i]
286 yield (0, i, line)
314 yield (0, i, line)
287
315
288 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
316 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
289 """Retrieve input by session.
317 """Retrieve input by session.
290
318
291 Parameters
319 Parameters
292 ----------
320 ----------
293 session : int
321 session : int
294 Session number to retrieve. The current session is 0, and negative
322 Session number to retrieve. The current session is 0, and negative
295 numbers count back from current session, so -1 is previous session.
323 numbers count back from current session, so -1 is previous session.
296 start : int
324 start : int
297 First line to retrieve.
325 First line to retrieve.
298 stop : int
326 stop : int
299 End of line range (excluded from output itself). If None, retrieve
327 End of line range (excluded from output itself). If None, retrieve
300 to the end of the session.
328 to the end of the session.
301 raw : bool
329 raw : bool
302 If True, return untranslated input
330 If True, return untranslated input
303 output : bool
331 output : bool
304 If True, attempt to include output. This will be 'real' Python
332 If True, attempt to include output. This will be 'real' Python
305 objects for the current session, or text reprs from previous
333 objects for the current session, or text reprs from previous
306 sessions if db_log_output was enabled at the time. Where no output
334 sessions if db_log_output was enabled at the time. Where no output
307 is found, None is used.
335 is found, None is used.
308
336
309 Returns
337 Returns
310 -------
338 -------
311 An iterator over the desired lines. Each line is a 3-tuple, either
339 An iterator over the desired lines. Each line is a 3-tuple, either
312 (session, line, input) if output is False, or
340 (session, line, input) if output is False, or
313 (session, line, (input, output)) if output is True.
341 (session, line, (input, output)) if output is True.
314 """
342 """
315 if session == 0 or session==self.session_number: # Current session
343 if session == 0 or session==self.session_number: # Current session
316 return self._get_range_session(start, stop, raw, output)
344 return self._get_range_session(start, stop, raw, output)
317 if session < 0:
345 if session < 0:
318 session += self.session_number
346 session += self.session_number
319
347
320 if stop:
348 if stop:
321 lineclause = "line >= ? AND line < ?"
349 lineclause = "line >= ? AND line < ?"
322 params = (session, start, stop)
350 params = (session, start, stop)
323 else:
351 else:
324 lineclause = "line>=?"
352 lineclause = "line>=?"
325 params = (session, start)
353 params = (session, start)
326
354
327 return self._run_sql("WHERE session==? AND %s""" % lineclause,
355 return self._run_sql("WHERE session==? AND %s""" % lineclause,
328 params, raw=raw, output=output)
356 params, raw=raw, output=output)
329
357
330 def get_range_by_str(self, rangestr, raw=True, output=False):
358 def get_range_by_str(self, rangestr, raw=True, output=False):
331 """Get lines of history from a string of ranges, as used by magic
359 """Get lines of history from a string of ranges, as used by magic
332 commands %hist, %save, %macro, etc.
360 commands %hist, %save, %macro, etc.
333
361
334 Parameters
362 Parameters
335 ----------
363 ----------
336 rangestr : str
364 rangestr : str
337 A string specifying ranges, e.g. "5 ~2/1-4". See
365 A string specifying ranges, e.g. "5 ~2/1-4". See
338 :func:`magic_history` for full details.
366 :func:`magic_history` for full details.
339 raw, output : bool
367 raw, output : bool
340 As :meth:`get_range`
368 As :meth:`get_range`
341
369
342 Returns
370 Returns
343 -------
371 -------
344 Tuples as :meth:`get_range`
372 Tuples as :meth:`get_range`
345 """
373 """
346 for sess, s, e in extract_hist_ranges(rangestr):
374 for sess, s, e in extract_hist_ranges(rangestr):
347 for line in self.get_range(sess, s, e, raw=raw, output=output):
375 for line in self.get_range(sess, s, e, raw=raw, output=output):
348 yield line
376 yield line
349
377
350 ## ----------------------------
378 ## ----------------------------
351 ## Methods for storing history:
379 ## Methods for storing history:
352 ## ----------------------------
380 ## ----------------------------
353 def store_inputs(self, line_num, source, source_raw=None):
381 def store_inputs(self, line_num, source, source_raw=None):
354 """Store source and raw input in history and create input cache
382 """Store source and raw input in history and create input cache
355 variables _i*.
383 variables _i*.
356
384
357 Parameters
385 Parameters
358 ----------
386 ----------
359 line_num : int
387 line_num : int
360 The prompt number of this input.
388 The prompt number of this input.
361
389
362 source : str
390 source : str
363 Python input.
391 Python input.
364
392
365 source_raw : str, optional
393 source_raw : str, optional
366 If given, this is the raw input without any IPython transformations
394 If given, this is the raw input without any IPython transformations
367 applied to it. If not given, ``source`` is used.
395 applied to it. If not given, ``source`` is used.
368 """
396 """
369 if source_raw is None:
397 if source_raw is None:
370 source_raw = source
398 source_raw = source
371 source = source.rstrip('\n')
399 source = source.rstrip('\n')
372 source_raw = source_raw.rstrip('\n')
400 source_raw = source_raw.rstrip('\n')
373
401
374 # do not store exit/quit commands
402 # do not store exit/quit commands
375 if self._exit_re.match(source_raw.strip()):
403 if self._exit_re.match(source_raw.strip()):
376 return
404 return
377
405
378 self.input_hist_parsed.append(source)
406 self.input_hist_parsed.append(source)
379 self.input_hist_raw.append(source_raw)
407 self.input_hist_raw.append(source_raw)
380
408
381 with self.db_input_cache_lock:
409 with self.db_input_cache_lock:
382 self.db_input_cache.append((line_num, source, source_raw))
410 self.db_input_cache.append((line_num, source, source_raw))
383 # Trigger to flush cache and write to DB.
411 # Trigger to flush cache and write to DB.
384 if len(self.db_input_cache) >= self.db_cache_size:
412 if len(self.db_input_cache) >= self.db_cache_size:
385 self.save_flag.set()
413 self.save_flag.set()
386
414
387 # update the auto _i variables
415 # update the auto _i variables
388 self._iii = self._ii
416 self._iii = self._ii
389 self._ii = self._i
417 self._ii = self._i
390 self._i = self._i00
418 self._i = self._i00
391 self._i00 = source_raw
419 self._i00 = source_raw
392
420
393 # hackish access to user namespace to create _i1,_i2... dynamically
421 # hackish access to user namespace to create _i1,_i2... dynamically
394 new_i = '_i%s' % line_num
422 new_i = '_i%s' % line_num
395 to_main = {'_i': self._i,
423 to_main = {'_i': self._i,
396 '_ii': self._ii,
424 '_ii': self._ii,
397 '_iii': self._iii,
425 '_iii': self._iii,
398 new_i : self._i00 }
426 new_i : self._i00 }
399 self.shell.user_ns.update(to_main)
427 self.shell.user_ns.update(to_main)
400
428
401 def store_output(self, line_num):
429 def store_output(self, line_num):
402 """If database output logging is enabled, this saves all the
430 """If database output logging is enabled, this saves all the
403 outputs from the indicated prompt number to the database. It's
431 outputs from the indicated prompt number to the database. It's
404 called by run_cell after code has been executed.
432 called by run_cell after code has been executed.
405
433
406 Parameters
434 Parameters
407 ----------
435 ----------
408 line_num : int
436 line_num : int
409 The line number from which to save outputs
437 The line number from which to save outputs
410 """
438 """
411 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
439 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
412 return
440 return
413 output = self.output_hist_reprs[line_num]
441 output = self.output_hist_reprs[line_num]
414
442
415 with self.db_output_cache_lock:
443 with self.db_output_cache_lock:
416 self.db_output_cache.append((line_num, output))
444 self.db_output_cache.append((line_num, output))
417 if self.db_cache_size <= 1:
445 if self.db_cache_size <= 1:
418 self.save_flag.set()
446 self.save_flag.set()
419
447
420 def _writeout_input_cache(self, conn):
448 def _writeout_input_cache(self, conn):
421 with conn:
449 with conn:
422 for line in self.db_input_cache:
450 for line in self.db_input_cache:
423 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
451 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
424 (self.session_number,)+line)
452 (self.session_number,)+line)
425
453
426 def _writeout_output_cache(self, conn):
454 def _writeout_output_cache(self, conn):
427 with conn:
455 with conn:
428 for line in self.db_output_cache:
456 for line in self.db_output_cache:
429 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
457 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
430 (self.session_number,)+line)
458 (self.session_number,)+line)
431
459
432 def writeout_cache(self, conn=None):
460 def writeout_cache(self, conn=None):
433 """Write any entries in the cache to the database."""
461 """Write any entries in the cache to the database."""
434 if conn is None:
462 if conn is None:
435 conn = self.db
463 conn = self.db
436
464
437 with self.db_input_cache_lock:
465 with self.db_input_cache_lock:
438 try:
466 try:
439 self._writeout_input_cache(conn)
467 self._writeout_input_cache(conn)
440 except sqlite3.IntegrityError:
468 except sqlite3.IntegrityError:
441 self.new_session(conn)
469 self.new_session(conn)
442 print("ERROR! Session/line number was not unique in",
470 print("ERROR! Session/line number was not unique in",
443 "database. History logging moved to new session",
471 "database. History logging moved to new session",
444 self.session_number)
472 self.session_number)
445 try: # Try writing to the new session. If this fails, don't recurse
473 try: # Try writing to the new session. If this fails, don't recurse
446 self._writeout_input_cache(conn)
474 self._writeout_input_cache(conn)
447 except sqlite3.IntegrityError:
475 except sqlite3.IntegrityError:
448 pass
476 pass
449 finally:
477 finally:
450 self.db_input_cache = []
478 self.db_input_cache = []
451
479
452 with self.db_output_cache_lock:
480 with self.db_output_cache_lock:
453 try:
481 try:
454 self._writeout_output_cache(conn)
482 self._writeout_output_cache(conn)
455 except sqlite3.IntegrityError:
483 except sqlite3.IntegrityError:
456 print("!! Session/line number for output was not unique",
484 print("!! Session/line number for output was not unique",
457 "in database. Output will not be stored.")
485 "in database. Output will not be stored.")
458 finally:
486 finally:
459 self.db_output_cache = []
487 self.db_output_cache = []
460
488
461
489
462 class HistorySavingThread(threading.Thread):
490 class HistorySavingThread(threading.Thread):
463 """This thread takes care of writing history to the database, so that
491 """This thread takes care of writing history to the database, so that
464 the UI isn't held up while that happens.
492 the UI isn't held up while that happens.
465
493
466 It waits for the HistoryManager's save_flag to be set, then writes out
494 It waits for the HistoryManager's save_flag to be set, then writes out
467 the history cache. The main thread is responsible for setting the flag when
495 the history cache. The main thread is responsible for setting the flag when
468 the cache size reaches a defined threshold."""
496 the cache size reaches a defined threshold."""
469 daemon = True
497 daemon = True
470 stop_now = False
498 stop_now = False
471 def __init__(self, history_manager):
499 def __init__(self, history_manager):
472 super(HistorySavingThread, self).__init__()
500 super(HistorySavingThread, self).__init__()
473 self.history_manager = history_manager
501 self.history_manager = history_manager
474 atexit.register(self.stop)
502 atexit.register(self.stop)
475
503
476 def run(self):
504 def run(self):
477 # We need a separate db connection per thread:
505 # We need a separate db connection per thread:
478 try:
506 try:
479 self.db = sqlite3.connect(self.history_manager.hist_file)
507 self.db = sqlite3.connect(self.history_manager.hist_file)
480 while True:
508 while True:
481 self.history_manager.save_flag.wait()
509 self.history_manager.save_flag.wait()
482 if self.stop_now:
510 if self.stop_now:
483 return
511 return
484 self.history_manager.save_flag.clear()
512 self.history_manager.save_flag.clear()
485 self.history_manager.writeout_cache(self.db)
513 self.history_manager.writeout_cache(self.db)
486 except Exception as e:
514 except Exception as e:
487 print(("The history saving thread hit an unexpected error (%s)."
515 print(("The history saving thread hit an unexpected error (%s)."
488 "History will not be written to the database.") % repr(e))
516 "History will not be written to the database.") % repr(e))
489
517
490 def stop(self):
518 def stop(self):
491 """This can be called from the main thread to safely stop this thread.
519 """This can be called from the main thread to safely stop this thread.
492
520
493 Note that it does not attempt to write out remaining history before
521 Note that it does not attempt to write out remaining history before
494 exiting. That should be done by calling the HistoryManager's
522 exiting. That should be done by calling the HistoryManager's
495 end_session method."""
523 end_session method."""
496 self.stop_now = True
524 self.stop_now = True
497 self.history_manager.save_flag.set()
525 self.history_manager.save_flag.set()
498 self.join()
526 self.join()
499
527
500
528
501 # To match, e.g. ~5/8-~2/3
529 # To match, e.g. ~5/8-~2/3
502 range_re = re.compile(r"""
530 range_re = re.compile(r"""
503 ((?P<startsess>~?\d+)/)?
531 ((?P<startsess>~?\d+)/)?
504 (?P<start>\d+) # Only the start line num is compulsory
532 (?P<start>\d+) # Only the start line num is compulsory
505 ((?P<sep>[\-:])
533 ((?P<sep>[\-:])
506 ((?P<endsess>~?\d+)/)?
534 ((?P<endsess>~?\d+)/)?
507 (?P<end>\d+))?
535 (?P<end>\d+))?
508 $""", re.VERBOSE)
536 $""", re.VERBOSE)
509
537
510 def extract_hist_ranges(ranges_str):
538 def extract_hist_ranges(ranges_str):
511 """Turn a string of history ranges into 3-tuples of (session, start, stop).
539 """Turn a string of history ranges into 3-tuples of (session, start, stop).
512
540
513 Examples
541 Examples
514 --------
542 --------
515 list(extract_input_ranges("~8/5-~7/4 2"))
543 list(extract_input_ranges("~8/5-~7/4 2"))
516 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
544 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
517 """
545 """
518 for range_str in ranges_str.split():
546 for range_str in ranges_str.split():
519 rmatch = range_re.match(range_str)
547 rmatch = range_re.match(range_str)
520 if not rmatch:
548 if not rmatch:
521 continue
549 continue
522 start = int(rmatch.group("start"))
550 start = int(rmatch.group("start"))
523 end = rmatch.group("end")
551 end = rmatch.group("end")
524 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
552 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
525 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
553 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
526 end += 1
554 end += 1
527 startsess = rmatch.group("startsess") or "0"
555 startsess = rmatch.group("startsess") or "0"
528 endsess = rmatch.group("endsess") or startsess
556 endsess = rmatch.group("endsess") or startsess
529 startsess = int(startsess.replace("~","-"))
557 startsess = int(startsess.replace("~","-"))
530 endsess = int(endsess.replace("~","-"))
558 endsess = int(endsess.replace("~","-"))
531 assert endsess >= startsess
559 assert endsess >= startsess
532
560
533 if endsess == startsess:
561 if endsess == startsess:
534 yield (startsess, start, end)
562 yield (startsess, start, end)
535 continue
563 continue
536 # Multiple sessions in one range:
564 # Multiple sessions in one range:
537 yield (startsess, start, None)
565 yield (startsess, start, None)
538 for sess in range(startsess+1, endsess):
566 for sess in range(startsess+1, endsess):
539 yield (sess, 1, None)
567 yield (sess, 1, None)
540 yield (endsess, 1, end)
568 yield (endsess, 1, end)
541
569
542 def _format_lineno(session, line):
570 def _format_lineno(session, line):
543 """Helper function to format line numbers properly."""
571 """Helper function to format line numbers properly."""
544 if session == 0:
572 if session == 0:
545 return str(line)
573 return str(line)
546 return "%s#%s" % (session, line)
574 return "%s#%s" % (session, line)
547
575
548 @skip_doctest
576 @skip_doctest
549 def magic_history(self, parameter_s = ''):
577 def magic_history(self, parameter_s = ''):
550 """Print input history (_i<n> variables), with most recent last.
578 """Print input history (_i<n> variables), with most recent last.
551
579
552 %history -> print at most 40 inputs (some may be multi-line)\\
580 %history -> print at most 40 inputs (some may be multi-line)\\
553 %history n -> print at most n inputs\\
581 %history n -> print at most n inputs\\
554 %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\
582 %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\
555
583
556 By default, input history is printed without line numbers so it can be
584 By default, input history is printed without line numbers so it can be
557 directly pasted into an editor. Use -n to show them.
585 directly pasted into an editor. Use -n to show them.
558
586
559 Ranges of history can be indicated using the syntax:
587 Ranges of history can be indicated using the syntax:
560 4 : Line 4, current session
588 4 : Line 4, current session
561 4-6 : Lines 4-6, current session
589 4-6 : Lines 4-6, current session
562 243/1-5: Lines 1-5, session 243
590 243/1-5: Lines 1-5, session 243
563 ~2/7 : Line 7, session 2 before current
591 ~2/7 : Line 7, session 2 before current
564 ~8/1-~6/5 : From the first line of 8 sessions ago, to the fifth line
592 ~8/1-~6/5 : From the first line of 8 sessions ago, to the fifth line
565 of 6 sessions ago.
593 of 6 sessions ago.
566 Multiple ranges can be entered, separated by spaces
594 Multiple ranges can be entered, separated by spaces
567
595
568 The same syntax is used by %macro, %save, %edit, %rerun
596 The same syntax is used by %macro, %save, %edit, %rerun
569
597
570 Options:
598 Options:
571
599
572 -n: print line numbers for each input.
600 -n: print line numbers for each input.
573 This feature is only available if numbered prompts are in use.
601 This feature is only available if numbered prompts are in use.
574
602
575 -o: also print outputs for each input.
603 -o: also print outputs for each input.
576
604
577 -p: print classic '>>>' python prompts before each input. This is useful
605 -p: print classic '>>>' python prompts before each input. This is useful
578 for making documentation, and in conjunction with -o, for producing
606 for making documentation, and in conjunction with -o, for producing
579 doctest-ready output.
607 doctest-ready output.
580
608
581 -r: (default) print the 'raw' history, i.e. the actual commands you typed.
609 -r: (default) print the 'raw' history, i.e. the actual commands you typed.
582
610
583 -t: print the 'translated' history, as IPython understands it. IPython
611 -t: print the 'translated' history, as IPython understands it. IPython
584 filters your input and converts it all into valid Python source before
612 filters your input and converts it all into valid Python source before
585 executing it (things like magics or aliases are turned into function
613 executing it (things like magics or aliases are turned into function
586 calls, for example). With this option, you'll see the native history
614 calls, for example). With this option, you'll see the native history
587 instead of the user-entered version: '%cd /' will be seen as
615 instead of the user-entered version: '%cd /' will be seen as
588 'get_ipython().magic("%cd /")' instead of '%cd /'.
616 'get_ipython().magic("%cd /")' instead of '%cd /'.
589
617
590 -g: treat the arg as a pattern to grep for in (full) history.
618 -g: treat the arg as a pattern to grep for in (full) history.
591 This includes the saved history (almost all commands ever written).
619 This includes the saved history (almost all commands ever written).
592 Use '%hist -g' to show full saved history (may be very long).
620 Use '%hist -g' to show full saved history (may be very long).
593
621
594 -l: get the last n lines from all sessions. Specify n as a single arg, or
622 -l: get the last n lines from all sessions. Specify n as a single arg, or
595 the default is the last 10 lines.
623 the default is the last 10 lines.
596
624
597 -f FILENAME: instead of printing the output to the screen, redirect it to
625 -f FILENAME: instead of printing the output to the screen, redirect it to
598 the given file. The file is always overwritten, though IPython asks for
626 the given file. The file is always overwritten, though IPython asks for
599 confirmation first if it already exists.
627 confirmation first if it already exists.
600
628
601 Examples
629 Examples
602 --------
630 --------
603 ::
631 ::
604
632
605 In [6]: %hist -n 4 6
633 In [6]: %hist -n 4 6
606 4:a = 12
634 4:a = 12
607 5:print a**2
635 5:print a**2
608
636
609 """
637 """
610
638
611 if not self.shell.displayhook.do_full_cache:
639 if not self.shell.displayhook.do_full_cache:
612 print('This feature is only available if numbered prompts are in use.')
640 print('This feature is only available if numbered prompts are in use.')
613 return
641 return
614 opts,args = self.parse_options(parameter_s,'noprtglf:',mode='string')
642 opts,args = self.parse_options(parameter_s,'noprtglf:',mode='string')
615
643
616 # For brevity
644 # For brevity
617 history_manager = self.shell.history_manager
645 history_manager = self.shell.history_manager
618
646
619 def _format_lineno(session, line):
647 def _format_lineno(session, line):
620 """Helper function to format line numbers properly."""
648 """Helper function to format line numbers properly."""
621 if session in (0, history_manager.session_number):
649 if session in (0, history_manager.session_number):
622 return str(line)
650 return str(line)
623 return "%s/%s" % (session, line)
651 return "%s/%s" % (session, line)
624
652
625 # Check if output to specific file was requested.
653 # Check if output to specific file was requested.
626 try:
654 try:
627 outfname = opts['f']
655 outfname = opts['f']
628 except KeyError:
656 except KeyError:
629 outfile = io.stdout # default
657 outfile = io.stdout # default
630 # We don't want to close stdout at the end!
658 # We don't want to close stdout at the end!
631 close_at_end = False
659 close_at_end = False
632 else:
660 else:
633 if os.path.exists(outfname):
661 if os.path.exists(outfname):
634 if not io.ask_yes_no("File %r exists. Overwrite?" % outfname):
662 if not io.ask_yes_no("File %r exists. Overwrite?" % outfname):
635 print('Aborting.')
663 print('Aborting.')
636 return
664 return
637
665
638 outfile = open(outfname,'w')
666 outfile = open(outfname,'w')
639 close_at_end = True
667 close_at_end = True
640
668
641 print_nums = 'n' in opts
669 print_nums = 'n' in opts
642 get_output = 'o' in opts
670 get_output = 'o' in opts
643 pyprompts = 'p' in opts
671 pyprompts = 'p' in opts
644 # Raw history is the default
672 # Raw history is the default
645 raw = not('t' in opts)
673 raw = not('t' in opts)
646
674
647 default_length = 40
675 default_length = 40
648 pattern = None
676 pattern = None
649
677
650 if 'g' in opts: # Glob search
678 if 'g' in opts: # Glob search
651 pattern = "*" + args + "*" if args else "*"
679 pattern = "*" + args + "*" if args else "*"
652 hist = history_manager.search(pattern, raw=raw, output=get_output)
680 hist = history_manager.search(pattern, raw=raw, output=get_output)
653 print_nums = True
681 print_nums = True
654 elif 'l' in opts: # Get 'tail'
682 elif 'l' in opts: # Get 'tail'
655 try:
683 try:
656 n = int(args)
684 n = int(args)
657 except ValueError, IndexError:
685 except ValueError, IndexError:
658 n = 10
686 n = 10
659 hist = history_manager.get_tail(n, raw=raw, output=get_output)
687 hist = history_manager.get_tail(n, raw=raw, output=get_output)
660 else:
688 else:
661 if args: # Get history by ranges
689 if args: # Get history by ranges
662 hist = history_manager.get_range_by_str(args, raw, get_output)
690 hist = history_manager.get_range_by_str(args, raw, get_output)
663 else: # Just get history for the current session
691 else: # Just get history for the current session
664 hist = history_manager.get_range(raw=raw, output=get_output)
692 hist = history_manager.get_range(raw=raw, output=get_output)
665
693
666 # We could be displaying the entire history, so let's not try to pull it
694 # We could be displaying the entire history, so let's not try to pull it
667 # into a list in memory. Anything that needs more space will just misalign.
695 # into a list in memory. Anything that needs more space will just misalign.
668 width = 4
696 width = 4
669
697
670 for session, lineno, inline in hist:
698 for session, lineno, inline in hist:
671 # Print user history with tabs expanded to 4 spaces. The GUI clients
699 # Print user history with tabs expanded to 4 spaces. The GUI clients
672 # use hard tabs for easier usability in auto-indented code, but we want
700 # use hard tabs for easier usability in auto-indented code, but we want
673 # to produce PEP-8 compliant history for safe pasting into an editor.
701 # to produce PEP-8 compliant history for safe pasting into an editor.
674 if get_output:
702 if get_output:
675 inline, output = inline
703 inline, output = inline
676 inline = inline.expandtabs(4).rstrip()
704 inline = inline.expandtabs(4).rstrip()
677
705
678 multiline = "\n" in inline
706 multiline = "\n" in inline
679 line_sep = '\n' if multiline else ' '
707 line_sep = '\n' if multiline else ' '
680 if print_nums:
708 if print_nums:
681 print('%s:%s' % (_format_lineno(session, lineno).rjust(width),
709 print('%s:%s' % (_format_lineno(session, lineno).rjust(width),
682 line_sep), file=outfile, end='')
710 line_sep), file=outfile, end='')
683 if pyprompts:
711 if pyprompts:
684 print(">>> ", end="", file=outfile)
712 print(">>> ", end="", file=outfile)
685 if multiline:
713 if multiline:
686 inline = "\n... ".join(inline.splitlines()) + "\n..."
714 inline = "\n... ".join(inline.splitlines()) + "\n..."
687 print(inline, file=outfile)
715 print(inline, file=outfile)
688 if get_output and output:
716 if get_output and output:
689 print(output, file=outfile)
717 print(output, file=outfile)
690
718
691 if close_at_end:
719 if close_at_end:
692 outfile.close()
720 outfile.close()
693
721
694
722
695 def magic_rep(self, arg):
723 def magic_rep(self, arg):
696 r"""Repeat a command, or get command to input line for editing. %recall and
724 r"""Repeat a command, or get command to input line for editing. %recall and
697 %rep are equivalent.
725 %rep are equivalent.
698
726
699 - %recall (no arguments):
727 - %recall (no arguments):
700
728
701 Place a string version of last computation result (stored in the special '_'
729 Place a string version of last computation result (stored in the special '_'
702 variable) to the next input prompt. Allows you to create elaborate command
730 variable) to the next input prompt. Allows you to create elaborate command
703 lines without using copy-paste::
731 lines without using copy-paste::
704
732
705 In[1]: l = ["hei", "vaan"]
733 In[1]: l = ["hei", "vaan"]
706 In[2]: "".join(l)
734 In[2]: "".join(l)
707 Out[2]: heivaan
735 Out[2]: heivaan
708 In[3]: %rep
736 In[3]: %rep
709 In[4]: heivaan_ <== cursor blinking
737 In[4]: heivaan_ <== cursor blinking
710
738
711 %recall 45
739 %recall 45
712
740
713 Place history line 45 on the next input prompt. Use %hist to find
741 Place history line 45 on the next input prompt. Use %hist to find
714 out the number.
742 out the number.
715
743
716 %recall 1-4
744 %recall 1-4
717
745
718 Combine the specified lines into one cell, and place it on the next
746 Combine the specified lines into one cell, and place it on the next
719 input prompt. See %history for the slice syntax.
747 input prompt. See %history for the slice syntax.
720
748
721 %recall foo+bar
749 %recall foo+bar
722
750
723 If foo+bar can be evaluated in the user namespace, the result is
751 If foo+bar can be evaluated in the user namespace, the result is
724 placed at the next input prompt. Otherwise, the history is searched
752 placed at the next input prompt. Otherwise, the history is searched
725 for lines which contain that substring, and the most recent one is
753 for lines which contain that substring, and the most recent one is
726 placed at the next input prompt.
754 placed at the next input prompt.
727 """
755 """
728 if not arg: # Last output
756 if not arg: # Last output
729 self.set_next_input(str(self.shell.user_ns["_"]))
757 self.set_next_input(str(self.shell.user_ns["_"]))
730 return
758 return
731 # Get history range
759 # Get history range
732 histlines = self.history_manager.get_range_by_str(arg)
760 histlines = self.history_manager.get_range_by_str(arg)
733 cmd = "\n".join(x[2] for x in histlines)
761 cmd = "\n".join(x[2] for x in histlines)
734 if cmd:
762 if cmd:
735 self.set_next_input(cmd.rstrip())
763 self.set_next_input(cmd.rstrip())
736 return
764 return
737
765
738 try: # Variable in user namespace
766 try: # Variable in user namespace
739 cmd = str(eval(arg, self.shell.user_ns))
767 cmd = str(eval(arg, self.shell.user_ns))
740 except Exception: # Search for term in history
768 except Exception: # Search for term in history
741 histlines = self.history_manager.search("*"+arg+"*")
769 histlines = self.history_manager.search("*"+arg+"*")
742 for h in reversed([x[2] for x in histlines]):
770 for h in reversed([x[2] for x in histlines]):
743 if 'rep' in h:
771 if 'rep' in h:
744 continue
772 continue
745 self.set_next_input(h.rstrip())
773 self.set_next_input(h.rstrip())
746 return
774 return
747 else:
775 else:
748 self.set_next_input(cmd.rstrip())
776 self.set_next_input(cmd.rstrip())
749 print("Couldn't evaluate or find in history:", arg)
777 print("Couldn't evaluate or find in history:", arg)
750
778
751 def magic_rerun(self, parameter_s=''):
779 def magic_rerun(self, parameter_s=''):
752 """Re-run previous input
780 """Re-run previous input
753
781
754 By default, you can specify ranges of input history to be repeated
782 By default, you can specify ranges of input history to be repeated
755 (as with %history). With no arguments, it will repeat the last line.
783 (as with %history). With no arguments, it will repeat the last line.
756
784
757 Options:
785 Options:
758
786
759 -l <n> : Repeat the last n lines of input, not including the
787 -l <n> : Repeat the last n lines of input, not including the
760 current command.
788 current command.
761
789
762 -g foo : Repeat the most recent line which contains foo
790 -g foo : Repeat the most recent line which contains foo
763 """
791 """
764 opts, args = self.parse_options(parameter_s, 'l:g:', mode='string')
792 opts, args = self.parse_options(parameter_s, 'l:g:', mode='string')
765 if "l" in opts: # Last n lines
793 if "l" in opts: # Last n lines
766 n = int(opts['l'])
794 n = int(opts['l'])
767 hist = self.history_manager.get_tail(n)
795 hist = self.history_manager.get_tail(n)
768 elif "g" in opts: # Search
796 elif "g" in opts: # Search
769 p = "*"+opts['g']+"*"
797 p = "*"+opts['g']+"*"
770 hist = list(self.history_manager.search(p))
798 hist = list(self.history_manager.search(p))
771 for l in reversed(hist):
799 for l in reversed(hist):
772 if "rerun" not in l[2]:
800 if "rerun" not in l[2]:
773 hist = [l] # The last match which isn't a %rerun
801 hist = [l] # The last match which isn't a %rerun
774 break
802 break
775 else:
803 else:
776 hist = [] # No matches except %rerun
804 hist = [] # No matches except %rerun
777 elif args: # Specify history ranges
805 elif args: # Specify history ranges
778 hist = self.history_manager.get_range_by_str(args)
806 hist = self.history_manager.get_range_by_str(args)
779 else: # Last line
807 else: # Last line
780 hist = self.history_manager.get_tail(1)
808 hist = self.history_manager.get_tail(1)
781 hist = [x[2] for x in hist]
809 hist = [x[2] for x in hist]
782 if not hist:
810 if not hist:
783 print("No lines in history match specification")
811 print("No lines in history match specification")
784 return
812 return
785 histlines = "\n".join(hist)
813 histlines = "\n".join(hist)
786 print("=== Executing: ===")
814 print("=== Executing: ===")
787 print(histlines)
815 print(histlines)
788 print("=== Output: ===")
816 print("=== Output: ===")
789 self.run_cell("\n".join(hist), store_history=False)
817 self.run_cell("\n".join(hist), store_history=False)
790
818
791
819
792 def init_ipython(ip):
820 def init_ipython(ip):
793 ip.define_magic("rep", magic_rep)
821 ip.define_magic("rep", magic_rep)
794 ip.define_magic("recall", magic_rep)
822 ip.define_magic("recall", magic_rep)
795 ip.define_magic("rerun", magic_rerun)
823 ip.define_magic("rerun", magic_rerun)
796 ip.define_magic("hist",magic_history) # Alternative name
824 ip.define_magic("hist",magic_history) # Alternative name
797 ip.define_magic("history",magic_history)
825 ip.define_magic("history",magic_history)
798
826
799 # XXX - ipy_completers are in quarantine, need to be updated to new apis
827 # XXX - ipy_completers are in quarantine, need to be updated to new apis
800 #import ipy_completers
828 #import ipy_completers
801 #ipy_completers.quick_completer('%hist' ,'-g -t -r -n')
829 #ipy_completers.quick_completer('%hist' ,'-g -t -r -n')
@@ -1,109 +1,114 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Tests for the IPython tab-completion machinery.
2 """Tests for the IPython tab-completion machinery.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Module imports
5 # Module imports
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7
7
8 # stdlib
8 # stdlib
9 import os
9 import os
10 import sys
10 import sys
11 import unittest
11 import unittest
12
12 from datetime import datetime
13 # third party
13 # third party
14 import nose.tools as nt
14 import nose.tools as nt
15
15
16 # our own packages
16 # our own packages
17 from IPython.utils.tempdir import TemporaryDirectory
17 from IPython.utils.tempdir import TemporaryDirectory
18 from IPython.core.history import HistoryManager, extract_hist_ranges
18 from IPython.core.history import HistoryManager, extract_hist_ranges
19
19
20 def setUp():
20 def setUp():
21 nt.assert_equal(sys.getdefaultencoding(), "ascii")
21 nt.assert_equal(sys.getdefaultencoding(), "ascii")
22
22
23 def test_history():
23 def test_history():
24 ip = get_ipython()
24 ip = get_ipython()
25 with TemporaryDirectory() as tmpdir:
25 with TemporaryDirectory() as tmpdir:
26 hist_manager_ori = ip.history_manager
26 hist_manager_ori = ip.history_manager
27 hist_file = os.path.join(tmpdir, 'history.sqlite')
27 hist_file = os.path.join(tmpdir, 'history.sqlite')
28 try:
28 try:
29 ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file)
29 ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file)
30 hist = ['a=1', 'def f():\n test = 1\n return test', u"b='β‚¬Γ†ΒΎΓ·ΓŸ'"]
30 hist = ['a=1', 'def f():\n test = 1\n return test', u"b='β‚¬Γ†ΒΎΓ·ΓŸ'"]
31 for i, h in enumerate(hist, start=1):
31 for i, h in enumerate(hist, start=1):
32 ip.history_manager.store_inputs(i, h)
32 ip.history_manager.store_inputs(i, h)
33
33
34 ip.history_manager.db_log_output = True
34 ip.history_manager.db_log_output = True
35 # Doesn't match the input, but we'll just check it's stored.
35 # Doesn't match the input, but we'll just check it's stored.
36 ip.history_manager.output_hist_reprs[3] = "spam"
36 ip.history_manager.output_hist_reprs[3] = "spam"
37 ip.history_manager.store_output(3)
37 ip.history_manager.store_output(3)
38
38
39 nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist)
39 nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist)
40
40
41
41
42 # New session
42 # New session
43 ip.history_manager.reset()
43 ip.history_manager.reset()
44 newcmds = ["z=5","class X(object):\n pass", "k='p'"]
44 newcmds = ["z=5","class X(object):\n pass", "k='p'"]
45 for i, cmd in enumerate(newcmds, start=1):
45 for i, cmd in enumerate(newcmds, start=1):
46 ip.history_manager.store_inputs(i, cmd)
46 ip.history_manager.store_inputs(i, cmd)
47 gothist = ip.history_manager.get_range(start=1, stop=4)
47 gothist = ip.history_manager.get_range(start=1, stop=4)
48 nt.assert_equal(list(gothist), zip([0,0,0],[1,2,3], newcmds))
48 nt.assert_equal(list(gothist), zip([0,0,0],[1,2,3], newcmds))
49 # Previous session:
49 # Previous session:
50 gothist = ip.history_manager.get_range(-1, 1, 4)
50 gothist = ip.history_manager.get_range(-1, 1, 4)
51 nt.assert_equal(list(gothist), zip([1,1,1],[1,2,3], hist))
51 nt.assert_equal(list(gothist), zip([1,1,1],[1,2,3], hist))
52
52
53 # Check get_hist_tail
53 # Check get_hist_tail
54 gothist = ip.history_manager.get_tail(4, output=True,
54 gothist = ip.history_manager.get_tail(4, output=True,
55 include_latest=True)
55 include_latest=True)
56 expected = [(1, 3, (hist[-1], "spam")),
56 expected = [(1, 3, (hist[-1], "spam")),
57 (2, 1, (newcmds[0], None)),
57 (2, 1, (newcmds[0], None)),
58 (2, 2, (newcmds[1], None)),
58 (2, 2, (newcmds[1], None)),
59 (2, 3, (newcmds[2], None)),]
59 (2, 3, (newcmds[2], None)),]
60 nt.assert_equal(list(gothist), expected)
60 nt.assert_equal(list(gothist), expected)
61
61
62 gothist = ip.history_manager.get_tail(2)
62 gothist = ip.history_manager.get_tail(2)
63 expected = [(2, 1, newcmds[0]),
63 expected = [(2, 1, newcmds[0]),
64 (2, 2, newcmds[1])]
64 (2, 2, newcmds[1])]
65 nt.assert_equal(list(gothist), expected)
65 nt.assert_equal(list(gothist), expected)
66
66
67 # Check get_hist_search
67 # Check get_hist_search
68 gothist = ip.history_manager.search("*test*")
68 gothist = ip.history_manager.search("*test*")
69 nt.assert_equal(list(gothist), [(1,2,hist[1])] )
69 nt.assert_equal(list(gothist), [(1,2,hist[1])] )
70 gothist = ip.history_manager.search("b*", output=True)
70 gothist = ip.history_manager.search("b*", output=True)
71 nt.assert_equal(list(gothist), [(1,3,(hist[2],"spam"))] )
71 nt.assert_equal(list(gothist), [(1,3,(hist[2],"spam"))] )
72
72
73 # Cross testing: check that magic %save can get previous session.
73 # Cross testing: check that magic %save can get previous session.
74 testfilename = os.path.realpath(os.path.join(tmpdir, "test.py"))
74 testfilename = os.path.realpath(os.path.join(tmpdir, "test.py"))
75 ip.magic_save(testfilename + " ~1/1-3")
75 ip.magic_save(testfilename + " ~1/1-3")
76 testfile = open(testfilename, "r")
76 testfile = open(testfilename, "r")
77 nt.assert_equal(testfile.read().decode("utf-8"),
77 nt.assert_equal(testfile.read().decode("utf-8"),
78 "# coding: utf-8\n" + "\n".join(hist))
78 "# coding: utf-8\n" + "\n".join(hist))
79
79
80 # Duplicate line numbers - check that it doesn't crash, and
80 # Duplicate line numbers - check that it doesn't crash, and
81 # gets a new session
81 # gets a new session
82 ip.history_manager.store_inputs(1, "rogue")
82 ip.history_manager.store_inputs(1, "rogue")
83 ip.history_manager.writeout_cache()
83 ip.history_manager.writeout_cache()
84 nt.assert_equal(ip.history_manager.session_number, 3)
84 nt.assert_equal(ip.history_manager.session_number, 3)
85 finally:
85 finally:
86 # Restore history manager
86 # Restore history manager
87 ip.history_manager = hist_manager_ori
87 ip.history_manager = hist_manager_ori
88
88
89
89
90 def test_extract_hist_ranges():
90 def test_extract_hist_ranges():
91 instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5"
91 instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5"
92 expected = [(0, 1, 2), # 0 == current session
92 expected = [(0, 1, 2), # 0 == current session
93 (2, 3, 4),
93 (2, 3, 4),
94 (-4, 5, 7),
94 (-4, 5, 7),
95 (-4, 7, 10),
95 (-4, 7, 10),
96 (-9, 2, None), # None == to end
96 (-9, 2, None), # None == to end
97 (-8, 1, None),
97 (-8, 1, None),
98 (-7, 1, 6)]
98 (-7, 1, 6)]
99 actual = list(extract_hist_ranges(instr))
99 actual = list(extract_hist_ranges(instr))
100 nt.assert_equal(actual, expected)
100 nt.assert_equal(actual, expected)
101
101
102 def test_magic_rerun():
102 def test_magic_rerun():
103 """Simple test for %rerun (no args -> rerun last line)"""
103 """Simple test for %rerun (no args -> rerun last line)"""
104 ip = get_ipython()
104 ip = get_ipython()
105 ip.run_cell("a = 10")
105 ip.run_cell("a = 10")
106 ip.run_cell("a += 1")
106 ip.run_cell("a += 1")
107 nt.assert_equal(ip.user_ns["a"], 11)
107 nt.assert_equal(ip.user_ns["a"], 11)
108 ip.run_cell("%rerun")
108 ip.run_cell("%rerun")
109 nt.assert_equal(ip.user_ns["a"], 12)
109 nt.assert_equal(ip.user_ns["a"], 12)
110
111 def test_timestamp_type():
112 ip = get_ipython()
113 info = ip.history_manager.get_session_info()
114 nt.assert_true(isinstance(info[1], datetime))
General Comments 0
You need to be logged in to leave comments. Login now