##// END OF EJS Templates
Merge branch 'history-access'
Thomas Kluyver -
r4990:401962b8 merge
parent child Browse files
Show More
@@ -0,0 +1,38 b''
1 #!/usr/bin/env python
2 """Extract a session from the IPython input history.
3
4 Usage:
5 ipython-get-history.py sessionnumber [outputfile]
6
7 If outputfile is not given, the relevant history is written to stdout. If
8 outputfile has a .py extension, the translated history (without IPython's
9 special syntax) will be extracted.
10
11 Example:
12 ./ipython-get-history.py 57 record.ipy
13
14
15 This script is a simple demonstration of HistoryAccessor. It should be possible
16 to build much more flexible and powerful tools to browse and pull from the
17 history database.
18 """
19 import sys
20 import codecs
21
22 from IPython.core.history import HistoryAccessor
23
24 session_number = int(sys.argv[1])
25 if len(sys.argv) > 2:
26 dest = open(sys.argv[2], "w")
27 raw = not sys.argv[2].endswith('.py')
28 else:
29 dest = sys.stdout
30 raw = True
31 dest.write("# coding: utf-8\n")
32
33 # Profiles other than 'default' can be specified here with a profile= argument:
34 hist = HistoryAccessor()
35
36 for session, lineno, cell in hist.get_range(session=session_number, raw=raw):
37 # To use this in Python 3, remove the .encode() here:
38 dest.write(cell.encode('utf-8') + '\n')
@@ -1,835 +1,909 b''
1 1 """ History related magics and functionality """
2 2 #-----------------------------------------------------------------------------
3 3 # Copyright (C) 2010 The IPython Development Team.
4 4 #
5 5 # Distributed under the terms of the BSD License.
6 6 #
7 7 # The full license is in the file COPYING.txt, distributed with this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13 from __future__ import print_function
14 14
15 15 # Stdlib imports
16 16 import atexit
17 17 import datetime
18 18 import os
19 19 import re
20 20 import sqlite3
21 21 import threading
22 22
23 23 # Our own packages
24 24 from IPython.config.configurable import Configurable
25 25
26 26 from IPython.testing.skipdoctest import skip_doctest
27 27 from IPython.utils import io
28 from IPython.utils.path import locate_profile
28 29 from IPython.utils.traitlets import Bool, Dict, Instance, Int, CInt, List, Unicode
29 30 from IPython.utils.warn import warn
30 31
31 32 #-----------------------------------------------------------------------------
32 33 # Classes and functions
33 34 #-----------------------------------------------------------------------------
34 35
35 class HistoryManager(Configurable):
36 """A class to organize all history-related functionality in one place.
37 """
38 # Public interface
39
40 # An instance of the IPython shell we are attached to
41 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
42 # Lists to hold processed and raw history. These start with a blank entry
43 # so that we can index them starting from 1
44 input_hist_parsed = List([""])
45 input_hist_raw = List([""])
46 # A list of directories visited during session
47 dir_hist = List()
48 def _dir_hist_default(self):
49 try:
50 return [os.getcwdu()]
51 except OSError:
52 return []
53
54 # A dict of output history, keyed with ints from the shell's
55 # execution count.
56 output_hist = Dict()
57 # The text/plain repr of outputs.
58 output_hist_reprs = Dict()
59
36 class HistoryAccessor(Configurable):
37 """Access the history database without adding to it.
38
39 This is intended for use by standalone history tools. IPython shells use
40 HistoryManager, below, which is a subclass of this."""
60 41 # String holding the path to the history file
61 42 hist_file = Unicode(config=True)
62 43
63 44 # The SQLite database
64 45 db = Instance(sqlite3.Connection)
65 # The number of the current session in the history database
66 session_number = CInt()
67 # Should we log output to the database? (default no)
68 db_log_output = Bool(False, config=True)
69 # Write to database every x commands (higher values save disk access & power)
70 # Values of 1 or less effectively disable caching.
71 db_cache_size = Int(0, config=True)
72 # The input and output caches
73 db_input_cache = List()
74 db_output_cache = List()
75
76 # History saving in separate thread
77 save_thread = Instance('IPython.core.history.HistorySavingThread')
78 try: # Event is a function returning an instance of _Event...
79 save_flag = Instance(threading._Event)
80 except AttributeError: # ...until Python 3.3, when it's a class.
81 save_flag = Instance(threading.Event)
82
83 # Private interface
84 # Variables used to store the three last inputs from the user. On each new
85 # history update, we populate the user's namespace with these, shifted as
86 # necessary.
87 _i00 = Unicode(u'')
88 _i = Unicode(u'')
89 _ii = Unicode(u'')
90 _iii = Unicode(u'')
91
92 # A regex matching all forms of the exit command, so that we don't store
93 # them in the history (it's annoying to rewind the first entry and land on
94 # an exit call).
95 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
96
97 def __init__(self, shell, config=None, **traits):
98 """Create a new history manager associated with a shell instance.
46
47 def __init__(self, profile='default', hist_file=u'', shell=None, config=None, **traits):
48 """Create a new history accessor.
49
50 Parameters
51 ----------
52 profile : str
53 The name of the profile from which to open history.
54 hist_file : str
55 Path to an SQLite history database stored by IPython. If specified,
56 hist_file overrides profile.
57 shell :
58 InteractiveShell object, for use by HistoryManager subclass
59 config :
60 Config object. hist_file can also be set through this.
99 61 """
100 62 # We need a pointer back to the shell for various tasks.
101 super(HistoryManager, self).__init__(shell=shell, config=config,
102 **traits)
63 super(HistoryAccessor, self).__init__(shell=shell, config=config,
64 hist_file=hist_file, **traits)
103 65
104 66 if self.hist_file == u'':
105 67 # No one has set the hist_file, yet.
106 histfname = 'history'
107 self.hist_file = os.path.join(shell.profile_dir.location, histfname + '.sqlite')
68 self.hist_file = self._get_hist_file_name(profile)
108 69
109 70 try:
110 71 self.init_db()
111 72 except sqlite3.DatabaseError:
112 73 if os.path.isfile(self.hist_file):
113 74 # Try to move the file out of the way.
114 75 newpath = os.path.join(self.shell.profile_dir.location, "hist-corrupt.sqlite")
115 76 os.rename(self.hist_file, newpath)
116 77 print("ERROR! History file wasn't a valid SQLite database.",
117 78 "It was moved to %s" % newpath, "and a new file created.")
118 79 self.init_db()
119 80 else:
120 81 # The hist_file is probably :memory: or something else.
121 82 raise
122
123 self.save_flag = threading.Event()
124 self.db_input_cache_lock = threading.Lock()
125 self.db_output_cache_lock = threading.Lock()
126 self.save_thread = HistorySavingThread(self)
127 self.save_thread.start()
128
129 self.new_session()
130
131
83
84 def _get_hist_file_name(self, profile='default'):
85 """Find the history file for the given profile name.
86
87 This is overridden by the HistoryManager subclass, to use the shell's
88 active profile.
89
90 Parameters
91 ----------
92 profile : str
93 The name of a profile which has a history file.
94 """
95 return os.path.join(locate_profile(profile), 'history.sqlite')
96
132 97 def init_db(self):
133 98 """Connect to the database, and create tables if necessary."""
134 99 # use detect_types so that timestamps return datetime objects
135 100 self.db = sqlite3.connect(self.hist_file, detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
136 101 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
137 102 primary key autoincrement, start timestamp,
138 103 end timestamp, num_cmds integer, remark text)""")
139 104 self.db.execute("""CREATE TABLE IF NOT EXISTS history
140 105 (session integer, line integer, source text, source_raw text,
141 106 PRIMARY KEY (session, line))""")
142 107 # Output history is optional, but ensure the table's there so it can be
143 108 # enabled later.
144 109 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
145 110 (session integer, line integer, output text,
146 111 PRIMARY KEY (session, line))""")
147 112 self.db.commit()
148 113
149 def new_session(self, conn=None):
150 """Get a new session number."""
151 if conn is None:
152 conn = self.db
153
154 with conn:
155 # N.B. 'insert into' here is lower case because of a bug in the
156 # sqlite3 module that affects the Turkish locale. This should be
157 # fixed for Python 2.7.3 and 3.2.3, as well as 3.3 onwards.
158 # http://bugs.python.org/issue13099
159 cur = conn.execute("""insert into sessions VALUES (NULL, ?, NULL,
160 NULL, "") """, (datetime.datetime.now(),))
161 self.session_number = cur.lastrowid
162
163 def end_session(self):
164 """Close the database session, filling in the end time and line count."""
165 self.writeout_cache()
166 with self.db:
167 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
168 session==?""", (datetime.datetime.now(),
169 len(self.input_hist_parsed)-1, self.session_number))
170 self.session_number = 0
171
172 def name_session(self, name):
173 """Give the current session a name in the history database."""
174 with self.db:
175 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
176 (name, self.session_number))
177
178 def reset(self, new_session=True):
179 """Clear the session history, releasing all object references, and
180 optionally open a new session."""
181 self.output_hist.clear()
182 # The directory history can't be completely empty
183 self.dir_hist[:] = [os.getcwdu()]
184
185 if new_session:
186 if self.session_number:
187 self.end_session()
188 self.input_hist_parsed[:] = [""]
189 self.input_hist_raw[:] = [""]
190 self.new_session()
114 def writeout_cache(self):
115 """Overridden by HistoryManager to dump the cache before certain
116 database lookups."""
117 pass
191 118
192 119 ## -------------------------------
193 120 ## Methods for retrieving history:
194 121 ## -------------------------------
195 122 def _run_sql(self, sql, params, raw=True, output=False):
196 123 """Prepares and runs an SQL query for the history database.
197 124
198 125 Parameters
199 126 ----------
200 127 sql : str
201 128 Any filtering expressions to go after SELECT ... FROM ...
202 129 params : tuple
203 130 Parameters passed to the SQL query (to replace "?")
204 131 raw, output : bool
205 132 See :meth:`get_range`
206 133
207 134 Returns
208 135 -------
209 136 Tuples as :meth:`get_range`
210 137 """
211 138 toget = 'source_raw' if raw else 'source'
212 139 sqlfrom = "history"
213 140 if output:
214 141 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
215 142 toget = "history.%s, output_history.output" % toget
216 143 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
217 144 (toget, sqlfrom) + sql, params)
218 145 if output: # Regroup into 3-tuples, and parse JSON
219 146 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
220 147 return cur
221 148
222
223 149 def get_session_info(self, session=0):
224 150 """get info about a session
225 151
226 152 Parameters
227 153 ----------
228 154
229 155 session : int
230 156 Session number to retrieve. The current session is 0, and negative
231 157 numbers count back from current session, so -1 is previous session.
232 158
233 159 Returns
234 160 -------
235 161
236 162 (session_id [int], start [datetime], end [datetime], num_cmds [int], remark [unicode])
237 163
238 164 Sessions that are running or did not exit cleanly will have `end=None`
239 165 and `num_cmds=None`.
240 166
241 167 """
242 168
243 169 if session <= 0:
244 170 session += self.session_number
245 171
246 172 query = "SELECT * from sessions where session == ?"
247 173 return self.db.execute(query, (session,)).fetchone()
248 174
249
250 175 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
251 176 """Get the last n lines from the history database.
252 177
253 178 Parameters
254 179 ----------
255 180 n : int
256 181 The number of lines to get
257 182 raw, output : bool
258 183 See :meth:`get_range`
259 184 include_latest : bool
260 185 If False (default), n+1 lines are fetched, and the latest one
261 186 is discarded. This is intended to be used where the function
262 187 is called by a user command, which it should not return.
263 188
264 189 Returns
265 190 -------
266 191 Tuples as :meth:`get_range`
267 192 """
268 193 self.writeout_cache()
269 194 if not include_latest:
270 195 n += 1
271 196 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
272 197 (n,), raw=raw, output=output)
273 198 if not include_latest:
274 199 return reversed(list(cur)[1:])
275 200 return reversed(list(cur))
276 201
277 202 def search(self, pattern="*", raw=True, search_raw=True,
278 203 output=False):
279 204 """Search the database using unix glob-style matching (wildcards
280 205 * and ?).
281 206
282 207 Parameters
283 208 ----------
284 209 pattern : str
285 210 The wildcarded pattern to match when searching
286 211 search_raw : bool
287 212 If True, search the raw input, otherwise, the parsed input
288 213 raw, output : bool
289 214 See :meth:`get_range`
290 215
291 216 Returns
292 217 -------
293 218 Tuples as :meth:`get_range`
294 219 """
295 220 tosearch = "source_raw" if search_raw else "source"
296 221 if output:
297 222 tosearch = "history." + tosearch
298 223 self.writeout_cache()
299 224 return self._run_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
300 225 raw=raw, output=output)
301
302 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
303 """Get input and output history from the current session. Called by
304 get_range, and takes similar parameters."""
305 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
306
307 n = len(input_hist)
308 if start < 0:
309 start += n
310 if not stop or (stop > n):
311 stop = n
312 elif stop < 0:
313 stop += n
314
315 for i in range(start, stop):
316 if output:
317 line = (input_hist[i], self.output_hist_reprs.get(i))
318 else:
319 line = input_hist[i]
320 yield (0, i, line)
321
322 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
226
227 def get_range(self, session, start=1, stop=None, raw=True,output=False):
323 228 """Retrieve input by session.
324 229
325 230 Parameters
326 231 ----------
327 232 session : int
328 Session number to retrieve. The current session is 0, and negative
329 numbers count back from current session, so -1 is previous session.
233 Session number to retrieve.
330 234 start : int
331 235 First line to retrieve.
332 236 stop : int
333 237 End of line range (excluded from output itself). If None, retrieve
334 238 to the end of the session.
335 239 raw : bool
336 240 If True, return untranslated input
337 241 output : bool
338 242 If True, attempt to include output. This will be 'real' Python
339 243 objects for the current session, or text reprs from previous
340 244 sessions if db_log_output was enabled at the time. Where no output
341 245 is found, None is used.
342 246
343 247 Returns
344 248 -------
345 249 An iterator over the desired lines. Each line is a 3-tuple, either
346 250 (session, line, input) if output is False, or
347 251 (session, line, (input, output)) if output is True.
348 252 """
349 if session == 0 or session==self.session_number: # Current session
350 return self._get_range_session(start, stop, raw, output)
351 if session < 0:
352 session += self.session_number
353
354 253 if stop:
355 254 lineclause = "line >= ? AND line < ?"
356 255 params = (session, start, stop)
357 256 else:
358 257 lineclause = "line>=?"
359 258 params = (session, start)
360 259
361 260 return self._run_sql("WHERE session==? AND %s""" % lineclause,
362 261 params, raw=raw, output=output)
363 262
364 263 def get_range_by_str(self, rangestr, raw=True, output=False):
365 264 """Get lines of history from a string of ranges, as used by magic
366 265 commands %hist, %save, %macro, etc.
367 266
368 267 Parameters
369 268 ----------
370 269 rangestr : str
371 270 A string specifying ranges, e.g. "5 ~2/1-4". See
372 271 :func:`magic_history` for full details.
373 272 raw, output : bool
374 273 As :meth:`get_range`
375 274
376 275 Returns
377 276 -------
378 277 Tuples as :meth:`get_range`
379 278 """
380 279 for sess, s, e in extract_hist_ranges(rangestr):
381 280 for line in self.get_range(sess, s, e, raw=raw, output=output):
382 281 yield line
383 282
283
284 class HistoryManager(HistoryAccessor):
285 """A class to organize all history-related functionality in one place.
286 """
287 # Public interface
288
289 # An instance of the IPython shell we are attached to
290 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
291 # Lists to hold processed and raw history. These start with a blank entry
292 # so that we can index them starting from 1
293 input_hist_parsed = List([""])
294 input_hist_raw = List([""])
295 # A list of directories visited during session
296 dir_hist = List()
297 def _dir_hist_default(self):
298 try:
299 return [os.getcwdu()]
300 except OSError:
301 return []
302
303 # A dict of output history, keyed with ints from the shell's
304 # execution count.
305 output_hist = Dict()
306 # The text/plain repr of outputs.
307 output_hist_reprs = Dict()
308
309 # The number of the current session in the history database
310 session_number = CInt()
311 # Should we log output to the database? (default no)
312 db_log_output = Bool(False, config=True)
313 # Write to database every x commands (higher values save disk access & power)
314 # Values of 1 or less effectively disable caching.
315 db_cache_size = Int(0, config=True)
316 # The input and output caches
317 db_input_cache = List()
318 db_output_cache = List()
319
320 # History saving in separate thread
321 save_thread = Instance('IPython.core.history.HistorySavingThread')
322 try: # Event is a function returning an instance of _Event...
323 save_flag = Instance(threading._Event)
324 except AttributeError: # ...until Python 3.3, when it's a class.
325 save_flag = Instance(threading.Event)
326
327 # Private interface
328 # Variables used to store the three last inputs from the user. On each new
329 # history update, we populate the user's namespace with these, shifted as
330 # necessary.
331 _i00 = Unicode(u'')
332 _i = Unicode(u'')
333 _ii = Unicode(u'')
334 _iii = Unicode(u'')
335
336 # A regex matching all forms of the exit command, so that we don't store
337 # them in the history (it's annoying to rewind the first entry and land on
338 # an exit call).
339 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
340
341 def __init__(self, shell=None, config=None, **traits):
342 """Create a new history manager associated with a shell instance.
343 """
344 # We need a pointer back to the shell for various tasks.
345 super(HistoryManager, self).__init__(shell=shell, config=config,
346 **traits)
347 self.save_flag = threading.Event()
348 self.db_input_cache_lock = threading.Lock()
349 self.db_output_cache_lock = threading.Lock()
350 self.save_thread = HistorySavingThread(self)
351 self.save_thread.start()
352
353 self.new_session()
354
355 def _get_hist_file_name(self, profile=None):
356 """Get default history file name based on the Shell's profile.
357
358 The profile parameter is ignored, but must exist for compatibility with
359 the parent class."""
360 profile_dir = self.shell.profile_dir.location
361 return os.path.join(profile_dir, 'history.sqlite')
362
363 def new_session(self, conn=None):
364 """Get a new session number."""
365 if conn is None:
366 conn = self.db
367
368 with conn:
369 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
370 NULL, "") """, (datetime.datetime.now(),))
371 self.session_number = cur.lastrowid
372
373 def end_session(self):
374 """Close the database session, filling in the end time and line count."""
375 self.writeout_cache()
376 with self.db:
377 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
378 session==?""", (datetime.datetime.now(),
379 len(self.input_hist_parsed)-1, self.session_number))
380 self.session_number = 0
381
382 def name_session(self, name):
383 """Give the current session a name in the history database."""
384 with self.db:
385 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
386 (name, self.session_number))
387
388 def reset(self, new_session=True):
389 """Clear the session history, releasing all object references, and
390 optionally open a new session."""
391 self.output_hist.clear()
392 # The directory history can't be completely empty
393 self.dir_hist[:] = [os.getcwdu()]
394
395 if new_session:
396 if self.session_number:
397 self.end_session()
398 self.input_hist_parsed[:] = [""]
399 self.input_hist_raw[:] = [""]
400 self.new_session()
401
402 # ------------------------------
403 # Methods for retrieving history
404 # ------------------------------
405 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
406 """Get input and output history from the current session. Called by
407 get_range, and takes similar parameters."""
408 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
409
410 n = len(input_hist)
411 if start < 0:
412 start += n
413 if not stop or (stop > n):
414 stop = n
415 elif stop < 0:
416 stop += n
417
418 for i in range(start, stop):
419 if output:
420 line = (input_hist[i], self.output_hist_reprs.get(i))
421 else:
422 line = input_hist[i]
423 yield (0, i, line)
424
425 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
426 """Retrieve input by session.
427
428 Parameters
429 ----------
430 session : int
431 Session number to retrieve. The current session is 0, and negative
432 numbers count back from current session, so -1 is previous session.
433 start : int
434 First line to retrieve.
435 stop : int
436 End of line range (excluded from output itself). If None, retrieve
437 to the end of the session.
438 raw : bool
439 If True, return untranslated input
440 output : bool
441 If True, attempt to include output. This will be 'real' Python
442 objects for the current session, or text reprs from previous
443 sessions if db_log_output was enabled at the time. Where no output
444 is found, None is used.
445
446 Returns
447 -------
448 An iterator over the desired lines. Each line is a 3-tuple, either
449 (session, line, input) if output is False, or
450 (session, line, (input, output)) if output is True.
451 """
452 if session <= 0:
453 session += self.session_number
454 if session==self.session_number: # Current session
455 return self._get_range_session(start, stop, raw, output)
456 return super(HistoryManager, self).get_range(session, start, stop, raw, output)
457
384 458 ## ----------------------------
385 459 ## Methods for storing history:
386 460 ## ----------------------------
387 461 def store_inputs(self, line_num, source, source_raw=None):
388 462 """Store source and raw input in history and create input cache
389 463 variables _i*.
390 464
391 465 Parameters
392 466 ----------
393 467 line_num : int
394 468 The prompt number of this input.
395 469
396 470 source : str
397 471 Python input.
398 472
399 473 source_raw : str, optional
400 474 If given, this is the raw input without any IPython transformations
401 475 applied to it. If not given, ``source`` is used.
402 476 """
403 477 if source_raw is None:
404 478 source_raw = source
405 479 source = source.rstrip('\n')
406 480 source_raw = source_raw.rstrip('\n')
407 481
408 482 # do not store exit/quit commands
409 483 if self._exit_re.match(source_raw.strip()):
410 484 return
411 485
412 486 self.input_hist_parsed.append(source)
413 487 self.input_hist_raw.append(source_raw)
414 488
415 489 with self.db_input_cache_lock:
416 490 self.db_input_cache.append((line_num, source, source_raw))
417 491 # Trigger to flush cache and write to DB.
418 492 if len(self.db_input_cache) >= self.db_cache_size:
419 493 self.save_flag.set()
420 494
421 495 # update the auto _i variables
422 496 self._iii = self._ii
423 497 self._ii = self._i
424 498 self._i = self._i00
425 499 self._i00 = source_raw
426 500
427 501 # hackish access to user namespace to create _i1,_i2... dynamically
428 502 new_i = '_i%s' % line_num
429 503 to_main = {'_i': self._i,
430 504 '_ii': self._ii,
431 505 '_iii': self._iii,
432 506 new_i : self._i00 }
433 507 self.shell.user_ns.update(to_main)
434 508
435 509 def store_output(self, line_num):
436 510 """If database output logging is enabled, this saves all the
437 511 outputs from the indicated prompt number to the database. It's
438 512 called by run_cell after code has been executed.
439 513
440 514 Parameters
441 515 ----------
442 516 line_num : int
443 517 The line number from which to save outputs
444 518 """
445 519 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
446 520 return
447 521 output = self.output_hist_reprs[line_num]
448 522
449 523 with self.db_output_cache_lock:
450 524 self.db_output_cache.append((line_num, output))
451 525 if self.db_cache_size <= 1:
452 526 self.save_flag.set()
453 527
454 528 def _writeout_input_cache(self, conn):
455 529 with conn:
456 530 for line in self.db_input_cache:
457 531 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
458 532 (self.session_number,)+line)
459 533
460 534 def _writeout_output_cache(self, conn):
461 535 with conn:
462 536 for line in self.db_output_cache:
463 537 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
464 538 (self.session_number,)+line)
465 539
466 540 def writeout_cache(self, conn=None):
467 541 """Write any entries in the cache to the database."""
468 542 if conn is None:
469 543 conn = self.db
470 544
471 545 with self.db_input_cache_lock:
472 546 try:
473 547 self._writeout_input_cache(conn)
474 548 except sqlite3.IntegrityError:
475 549 self.new_session(conn)
476 550 print("ERROR! Session/line number was not unique in",
477 551 "database. History logging moved to new session",
478 552 self.session_number)
479 553 try: # Try writing to the new session. If this fails, don't recurse
480 554 self._writeout_input_cache(conn)
481 555 except sqlite3.IntegrityError:
482 556 pass
483 557 finally:
484 558 self.db_input_cache = []
485 559
486 560 with self.db_output_cache_lock:
487 561 try:
488 562 self._writeout_output_cache(conn)
489 563 except sqlite3.IntegrityError:
490 564 print("!! Session/line number for output was not unique",
491 565 "in database. Output will not be stored.")
492 566 finally:
493 567 self.db_output_cache = []
494 568
495 569
496 570 class HistorySavingThread(threading.Thread):
497 571 """This thread takes care of writing history to the database, so that
498 572 the UI isn't held up while that happens.
499 573
500 574 It waits for the HistoryManager's save_flag to be set, then writes out
501 575 the history cache. The main thread is responsible for setting the flag when
502 576 the cache size reaches a defined threshold."""
503 577 daemon = True
504 578 stop_now = False
505 579 def __init__(self, history_manager):
506 580 super(HistorySavingThread, self).__init__()
507 581 self.history_manager = history_manager
508 582 atexit.register(self.stop)
509 583
510 584 def run(self):
511 585 # We need a separate db connection per thread:
512 586 try:
513 587 self.db = sqlite3.connect(self.history_manager.hist_file)
514 588 while True:
515 589 self.history_manager.save_flag.wait()
516 590 if self.stop_now:
517 591 return
518 592 self.history_manager.save_flag.clear()
519 593 self.history_manager.writeout_cache(self.db)
520 594 except Exception as e:
521 595 print(("The history saving thread hit an unexpected error (%s)."
522 596 "History will not be written to the database.") % repr(e))
523 597
524 598 def stop(self):
525 599 """This can be called from the main thread to safely stop this thread.
526 600
527 601 Note that it does not attempt to write out remaining history before
528 602 exiting. That should be done by calling the HistoryManager's
529 603 end_session method."""
530 604 self.stop_now = True
531 605 self.history_manager.save_flag.set()
532 606 self.join()
533 607
534 608
535 609 # To match, e.g. ~5/8-~2/3
536 610 range_re = re.compile(r"""
537 611 ((?P<startsess>~?\d+)/)?
538 612 (?P<start>\d+) # Only the start line num is compulsory
539 613 ((?P<sep>[\-:])
540 614 ((?P<endsess>~?\d+)/)?
541 615 (?P<end>\d+))?
542 616 $""", re.VERBOSE)
543 617
544 618 def extract_hist_ranges(ranges_str):
545 619 """Turn a string of history ranges into 3-tuples of (session, start, stop).
546 620
547 621 Examples
548 622 --------
549 623 list(extract_input_ranges("~8/5-~7/4 2"))
550 624 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
551 625 """
552 626 for range_str in ranges_str.split():
553 627 rmatch = range_re.match(range_str)
554 628 if not rmatch:
555 629 continue
556 630 start = int(rmatch.group("start"))
557 631 end = rmatch.group("end")
558 632 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
559 633 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
560 634 end += 1
561 635 startsess = rmatch.group("startsess") or "0"
562 636 endsess = rmatch.group("endsess") or startsess
563 637 startsess = int(startsess.replace("~","-"))
564 638 endsess = int(endsess.replace("~","-"))
565 639 assert endsess >= startsess
566 640
567 641 if endsess == startsess:
568 642 yield (startsess, start, end)
569 643 continue
570 644 # Multiple sessions in one range:
571 645 yield (startsess, start, None)
572 646 for sess in range(startsess+1, endsess):
573 647 yield (sess, 1, None)
574 648 yield (endsess, 1, end)
575 649
576 650 def _format_lineno(session, line):
577 651 """Helper function to format line numbers properly."""
578 652 if session == 0:
579 653 return str(line)
580 654 return "%s#%s" % (session, line)
581 655
582 656 @skip_doctest
583 657 def magic_history(self, parameter_s = ''):
584 658 """Print input history (_i<n> variables), with most recent last.
585 659
586 660 %history -> print at most 40 inputs (some may be multi-line)\\
587 661 %history n -> print at most n inputs\\
588 662 %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\
589 663
590 664 By default, input history is printed without line numbers so it can be
591 665 directly pasted into an editor. Use -n to show them.
592 666
593 667 Ranges of history can be indicated using the syntax:
594 668 4 : Line 4, current session
595 669 4-6 : Lines 4-6, current session
596 670 243/1-5: Lines 1-5, session 243
597 671 ~2/7 : Line 7, session 2 before current
598 672 ~8/1-~6/5 : From the first line of 8 sessions ago, to the fifth line
599 673 of 6 sessions ago.
600 674 Multiple ranges can be entered, separated by spaces
601 675
602 676 The same syntax is used by %macro, %save, %edit, %rerun
603 677
604 678 Options:
605 679
606 680 -n: print line numbers for each input.
607 681 This feature is only available if numbered prompts are in use.
608 682
609 683 -o: also print outputs for each input.
610 684
611 685 -p: print classic '>>>' python prompts before each input. This is useful
612 686 for making documentation, and in conjunction with -o, for producing
613 687 doctest-ready output.
614 688
615 689 -r: (default) print the 'raw' history, i.e. the actual commands you typed.
616 690
617 691 -t: print the 'translated' history, as IPython understands it. IPython
618 692 filters your input and converts it all into valid Python source before
619 693 executing it (things like magics or aliases are turned into function
620 694 calls, for example). With this option, you'll see the native history
621 695 instead of the user-entered version: '%cd /' will be seen as
622 696 'get_ipython().magic("%cd /")' instead of '%cd /'.
623 697
624 698 -g: treat the arg as a pattern to grep for in (full) history.
625 699 This includes the saved history (almost all commands ever written).
626 700 Use '%hist -g' to show full saved history (may be very long).
627 701
628 702 -l: get the last n lines from all sessions. Specify n as a single arg, or
629 703 the default is the last 10 lines.
630 704
631 705 -f FILENAME: instead of printing the output to the screen, redirect it to
632 706 the given file. The file is always overwritten, though IPython asks for
633 707 confirmation first if it already exists.
634 708
635 709 Examples
636 710 --------
637 711 ::
638 712
639 713 In [6]: %hist -n 4 6
640 714 4:a = 12
641 715 5:print a**2
642 716
643 717 """
644 718
645 719 if not self.shell.displayhook.do_full_cache:
646 720 print('This feature is only available if numbered prompts are in use.')
647 721 return
648 722 opts,args = self.parse_options(parameter_s,'noprtglf:',mode='string')
649 723
650 724 # For brevity
651 725 history_manager = self.shell.history_manager
652 726
653 727 def _format_lineno(session, line):
654 728 """Helper function to format line numbers properly."""
655 729 if session in (0, history_manager.session_number):
656 730 return str(line)
657 731 return "%s/%s" % (session, line)
658 732
659 733 # Check if output to specific file was requested.
660 734 try:
661 735 outfname = opts['f']
662 736 except KeyError:
663 737 outfile = io.stdout # default
664 738 # We don't want to close stdout at the end!
665 739 close_at_end = False
666 740 else:
667 741 if os.path.exists(outfname):
668 742 if not io.ask_yes_no("File %r exists. Overwrite?" % outfname):
669 743 print('Aborting.')
670 744 return
671 745
672 746 outfile = open(outfname,'w')
673 747 close_at_end = True
674 748
675 749 print_nums = 'n' in opts
676 750 get_output = 'o' in opts
677 751 pyprompts = 'p' in opts
678 752 # Raw history is the default
679 753 raw = not('t' in opts)
680 754
681 755 default_length = 40
682 756 pattern = None
683 757
684 758 if 'g' in opts: # Glob search
685 759 pattern = "*" + args + "*" if args else "*"
686 760 hist = history_manager.search(pattern, raw=raw, output=get_output)
687 761 print_nums = True
688 762 elif 'l' in opts: # Get 'tail'
689 763 try:
690 764 n = int(args)
691 765 except ValueError, IndexError:
692 766 n = 10
693 767 hist = history_manager.get_tail(n, raw=raw, output=get_output)
694 768 else:
695 769 if args: # Get history by ranges
696 770 hist = history_manager.get_range_by_str(args, raw, get_output)
697 771 else: # Just get history for the current session
698 772 hist = history_manager.get_range(raw=raw, output=get_output)
699 773
700 774 # We could be displaying the entire history, so let's not try to pull it
701 775 # into a list in memory. Anything that needs more space will just misalign.
702 776 width = 4
703 777
704 778 for session, lineno, inline in hist:
705 779 # Print user history with tabs expanded to 4 spaces. The GUI clients
706 780 # use hard tabs for easier usability in auto-indented code, but we want
707 781 # to produce PEP-8 compliant history for safe pasting into an editor.
708 782 if get_output:
709 783 inline, output = inline
710 784 inline = inline.expandtabs(4).rstrip()
711 785
712 786 multiline = "\n" in inline
713 787 line_sep = '\n' if multiline else ' '
714 788 if print_nums:
715 789 print('%s:%s' % (_format_lineno(session, lineno).rjust(width),
716 790 line_sep), file=outfile, end='')
717 791 if pyprompts:
718 792 print(">>> ", end="", file=outfile)
719 793 if multiline:
720 794 inline = "\n... ".join(inline.splitlines()) + "\n..."
721 795 print(inline, file=outfile)
722 796 if get_output and output:
723 797 print(output, file=outfile)
724 798
725 799 if close_at_end:
726 800 outfile.close()
727 801
728 802
729 803 def magic_rep(self, arg):
730 804 r"""Repeat a command, or get command to input line for editing. %recall and
731 805 %rep are equivalent.
732 806
733 807 - %recall (no arguments):
734 808
735 809 Place a string version of last computation result (stored in the special '_'
736 810 variable) to the next input prompt. Allows you to create elaborate command
737 811 lines without using copy-paste::
738 812
739 813 In[1]: l = ["hei", "vaan"]
740 814 In[2]: "".join(l)
741 815 Out[2]: heivaan
742 816 In[3]: %rep
743 817 In[4]: heivaan_ <== cursor blinking
744 818
745 819 %recall 45
746 820
747 821 Place history line 45 on the next input prompt. Use %hist to find
748 822 out the number.
749 823
750 824 %recall 1-4
751 825
752 826 Combine the specified lines into one cell, and place it on the next
753 827 input prompt. See %history for the slice syntax.
754 828
755 829 %recall foo+bar
756 830
757 831 If foo+bar can be evaluated in the user namespace, the result is
758 832 placed at the next input prompt. Otherwise, the history is searched
759 833 for lines which contain that substring, and the most recent one is
760 834 placed at the next input prompt.
761 835 """
762 836 if not arg: # Last output
763 837 self.set_next_input(str(self.shell.user_ns["_"]))
764 838 return
765 839 # Get history range
766 840 histlines = self.history_manager.get_range_by_str(arg)
767 841 cmd = "\n".join(x[2] for x in histlines)
768 842 if cmd:
769 843 self.set_next_input(cmd.rstrip())
770 844 return
771 845
772 846 try: # Variable in user namespace
773 847 cmd = str(eval(arg, self.shell.user_ns))
774 848 except Exception: # Search for term in history
775 849 histlines = self.history_manager.search("*"+arg+"*")
776 850 for h in reversed([x[2] for x in histlines]):
777 851 if 'rep' in h:
778 852 continue
779 853 self.set_next_input(h.rstrip())
780 854 return
781 855 else:
782 856 self.set_next_input(cmd.rstrip())
783 857 print("Couldn't evaluate or find in history:", arg)
784 858
785 859 def magic_rerun(self, parameter_s=''):
786 860 """Re-run previous input
787 861
788 862 By default, you can specify ranges of input history to be repeated
789 863 (as with %history). With no arguments, it will repeat the last line.
790 864
791 865 Options:
792 866
793 867 -l <n> : Repeat the last n lines of input, not including the
794 868 current command.
795 869
796 870 -g foo : Repeat the most recent line which contains foo
797 871 """
798 872 opts, args = self.parse_options(parameter_s, 'l:g:', mode='string')
799 873 if "l" in opts: # Last n lines
800 874 n = int(opts['l'])
801 875 hist = self.history_manager.get_tail(n)
802 876 elif "g" in opts: # Search
803 877 p = "*"+opts['g']+"*"
804 878 hist = list(self.history_manager.search(p))
805 879 for l in reversed(hist):
806 880 if "rerun" not in l[2]:
807 881 hist = [l] # The last match which isn't a %rerun
808 882 break
809 883 else:
810 884 hist = [] # No matches except %rerun
811 885 elif args: # Specify history ranges
812 886 hist = self.history_manager.get_range_by_str(args)
813 887 else: # Last line
814 888 hist = self.history_manager.get_tail(1)
815 889 hist = [x[2] for x in hist]
816 890 if not hist:
817 891 print("No lines in history match specification")
818 892 return
819 893 histlines = "\n".join(hist)
820 894 print("=== Executing: ===")
821 895 print(histlines)
822 896 print("=== Output: ===")
823 897 self.run_cell("\n".join(hist), store_history=False)
824 898
825 899
826 900 def init_ipython(ip):
827 901 ip.define_magic("rep", magic_rep)
828 902 ip.define_magic("recall", magic_rep)
829 903 ip.define_magic("rerun", magic_rerun)
830 904 ip.define_magic("hist",magic_history) # Alternative name
831 905 ip.define_magic("history",magic_history)
832 906
833 907 # XXX - ipy_completers are in quarantine, need to be updated to new apis
834 908 #import ipy_completers
835 909 #ipy_completers.quick_completer('%hist' ,'-g -t -r -n')
@@ -1,118 +1,124 b''
1 1 # coding: utf-8
2 2 """Tests for the IPython tab-completion machinery.
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 # Module imports
6 6 #-----------------------------------------------------------------------------
7 7
8 8 # stdlib
9 9 import os
10 10 import sys
11 11 import unittest
12 12 from datetime import datetime
13 13 # third party
14 14 import nose.tools as nt
15 15
16 16 # our own packages
17 17 from IPython.utils.tempdir import TemporaryDirectory
18 18 from IPython.core.history import HistoryManager, extract_hist_ranges
19 19 from IPython.utils import py3compat
20 20
21 21 def setUp():
22 22 nt.assert_equal(sys.getdefaultencoding(), "utf-8" if py3compat.PY3 else "ascii")
23 23
24 24 def test_history():
25 25 ip = get_ipython()
26 26 with TemporaryDirectory() as tmpdir:
27 27 hist_manager_ori = ip.history_manager
28 28 hist_file = os.path.join(tmpdir, 'history.sqlite')
29 29 try:
30 30 ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file)
31 31 hist = ['a=1', 'def f():\n test = 1\n return test', u"b='β‚¬Γ†ΒΎΓ·ΓŸ'"]
32 32 for i, h in enumerate(hist, start=1):
33 33 ip.history_manager.store_inputs(i, h)
34 34
35 35 ip.history_manager.db_log_output = True
36 36 # Doesn't match the input, but we'll just check it's stored.
37 37 ip.history_manager.output_hist_reprs[3] = "spam"
38 38 ip.history_manager.store_output(3)
39 39
40 40 nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist)
41
42 # Detailed tests for _get_range_session
43 grs = ip.history_manager._get_range_session
44 nt.assert_equal(list(grs(start=2,stop=-1)), zip([0], [2], hist[1:-1]))
45 nt.assert_equal(list(grs(start=-2)), zip([0,0], [2,3], hist[-2:]))
46 nt.assert_equal(list(grs(output=True)), zip([0,0,0], [1,2,3], zip(hist, [None,None,'spam'])))
41 47
42 48 # Check whether specifying a range beyond the end of the current
43 49 # session results in an error (gh-804)
44 50 ip.magic('%hist 2-500')
45 51
46 52 # New session
47 53 ip.history_manager.reset()
48 54 newcmds = ["z=5","class X(object):\n pass", "k='p'"]
49 55 for i, cmd in enumerate(newcmds, start=1):
50 56 ip.history_manager.store_inputs(i, cmd)
51 57 gothist = ip.history_manager.get_range(start=1, stop=4)
52 58 nt.assert_equal(list(gothist), zip([0,0,0],[1,2,3], newcmds))
53 59 # Previous session:
54 60 gothist = ip.history_manager.get_range(-1, 1, 4)
55 61 nt.assert_equal(list(gothist), zip([1,1,1],[1,2,3], hist))
56 62
57 63 # Check get_hist_tail
58 64 gothist = ip.history_manager.get_tail(4, output=True,
59 65 include_latest=True)
60 66 expected = [(1, 3, (hist[-1], "spam")),
61 67 (2, 1, (newcmds[0], None)),
62 68 (2, 2, (newcmds[1], None)),
63 69 (2, 3, (newcmds[2], None)),]
64 70 nt.assert_equal(list(gothist), expected)
65 71
66 72 gothist = ip.history_manager.get_tail(2)
67 73 expected = [(2, 1, newcmds[0]),
68 74 (2, 2, newcmds[1])]
69 75 nt.assert_equal(list(gothist), expected)
70 76
71 77 # Check get_hist_search
72 78 gothist = ip.history_manager.search("*test*")
73 79 nt.assert_equal(list(gothist), [(1,2,hist[1])] )
74 80 gothist = ip.history_manager.search("b*", output=True)
75 81 nt.assert_equal(list(gothist), [(1,3,(hist[2],"spam"))] )
76 82
77 83 # Cross testing: check that magic %save can get previous session.
78 84 testfilename = os.path.realpath(os.path.join(tmpdir, "test.py"))
79 85 ip.magic_save(testfilename + " ~1/1-3")
80 86 with py3compat.open(testfilename) as testfile:
81 87 nt.assert_equal(testfile.read(),
82 88 u"# coding: utf-8\n" + u"\n".join(hist))
83 89
84 90 # Duplicate line numbers - check that it doesn't crash, and
85 91 # gets a new session
86 92 ip.history_manager.store_inputs(1, "rogue")
87 93 ip.history_manager.writeout_cache()
88 94 nt.assert_equal(ip.history_manager.session_number, 3)
89 95 finally:
90 96 # Restore history manager
91 97 ip.history_manager = hist_manager_ori
92 98
93 99
94 100 def test_extract_hist_ranges():
95 101 instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5"
96 102 expected = [(0, 1, 2), # 0 == current session
97 103 (2, 3, 4),
98 104 (-4, 5, 7),
99 105 (-4, 7, 10),
100 106 (-9, 2, None), # None == to end
101 107 (-8, 1, None),
102 108 (-7, 1, 6)]
103 109 actual = list(extract_hist_ranges(instr))
104 110 nt.assert_equal(actual, expected)
105 111
106 112 def test_magic_rerun():
107 113 """Simple test for %rerun (no args -> rerun last line)"""
108 114 ip = get_ipython()
109 115 ip.run_cell("a = 10")
110 116 ip.run_cell("a += 1")
111 117 nt.assert_equal(ip.user_ns["a"], 11)
112 118 ip.run_cell("%rerun")
113 119 nt.assert_equal(ip.user_ns["a"], 12)
114 120
115 121 def test_timestamp_type():
116 122 ip = get_ipython()
117 123 info = ip.history_manager.get_session_info()
118 124 nt.assert_true(isinstance(info[1], datetime))
@@ -1,509 +1,521 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for path handling.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2008-2009 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 import os
18 18 import sys
19 19 import tempfile
20 20 import warnings
21 21 from hashlib import md5
22 22
23 23 import IPython
24 24 from IPython.utils.process import system
25 25 from IPython.utils.importstring import import_item
26 26 from IPython.utils import py3compat
27 27
28 28 #-----------------------------------------------------------------------------
29 29 # Code
30 30 #-----------------------------------------------------------------------------
31 31
32 32 fs_encoding = sys.getfilesystemencoding()
33 33
34 34 def _get_long_path_name(path):
35 35 """Dummy no-op."""
36 36 return path
37 37
38 38 def _writable_dir(path):
39 39 """Whether `path` is a directory, to which the user has write access."""
40 40 return os.path.isdir(path) and os.access(path, os.W_OK)
41 41
42 42 if sys.platform == 'win32':
43 43 def _get_long_path_name(path):
44 44 """Get a long path name (expand ~) on Windows using ctypes.
45 45
46 46 Examples
47 47 --------
48 48
49 49 >>> get_long_path_name('c:\\docume~1')
50 50 u'c:\\\\Documents and Settings'
51 51
52 52 """
53 53 try:
54 54 import ctypes
55 55 except ImportError:
56 56 raise ImportError('you need to have ctypes installed for this to work')
57 57 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
58 58 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
59 59 ctypes.c_uint ]
60 60
61 61 buf = ctypes.create_unicode_buffer(260)
62 62 rv = _GetLongPathName(path, buf, 260)
63 63 if rv == 0 or rv > 260:
64 64 return path
65 65 else:
66 66 return buf.value
67 67
68 68
69 69 def get_long_path_name(path):
70 70 """Expand a path into its long form.
71 71
72 72 On Windows this expands any ~ in the paths. On other platforms, it is
73 73 a null operation.
74 74 """
75 75 return _get_long_path_name(path)
76 76
77 77
78 78 def unquote_filename(name, win32=(sys.platform=='win32')):
79 79 """ On Windows, remove leading and trailing quotes from filenames.
80 80 """
81 81 if win32:
82 82 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
83 83 name = name[1:-1]
84 84 return name
85 85
86 86
87 87 def get_py_filename(name, force_win32=None):
88 88 """Return a valid python filename in the current directory.
89 89
90 90 If the given name is not a file, it adds '.py' and searches again.
91 91 Raises IOError with an informative message if the file isn't found.
92 92
93 93 On Windows, apply Windows semantics to the filename. In particular, remove
94 94 any quoting that has been applied to it. This option can be forced for
95 95 testing purposes.
96 96 """
97 97
98 98 name = os.path.expanduser(name)
99 99 if force_win32 is None:
100 100 win32 = (sys.platform == 'win32')
101 101 else:
102 102 win32 = force_win32
103 103 name = unquote_filename(name, win32=win32)
104 104 if not os.path.isfile(name) and not name.endswith('.py'):
105 105 name += '.py'
106 106 if os.path.isfile(name):
107 107 return name
108 108 else:
109 109 raise IOError,'File `%s` not found.' % name
110 110
111 111
112 112 def filefind(filename, path_dirs=None):
113 113 """Find a file by looking through a sequence of paths.
114 114
115 115 This iterates through a sequence of paths looking for a file and returns
116 116 the full, absolute path of the first occurence of the file. If no set of
117 117 path dirs is given, the filename is tested as is, after running through
118 118 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
119 119
120 120 filefind('myfile.txt')
121 121
122 122 will find the file in the current working dir, but::
123 123
124 124 filefind('~/myfile.txt')
125 125
126 126 Will find the file in the users home directory. This function does not
127 127 automatically try any paths, such as the cwd or the user's home directory.
128 128
129 129 Parameters
130 130 ----------
131 131 filename : str
132 132 The filename to look for.
133 133 path_dirs : str, None or sequence of str
134 134 The sequence of paths to look for the file in. If None, the filename
135 135 need to be absolute or be in the cwd. If a string, the string is
136 136 put into a sequence and the searched. If a sequence, walk through
137 137 each element and join with ``filename``, calling :func:`expandvars`
138 138 and :func:`expanduser` before testing for existence.
139 139
140 140 Returns
141 141 -------
142 142 Raises :exc:`IOError` or returns absolute path to file.
143 143 """
144 144
145 145 # If paths are quoted, abspath gets confused, strip them...
146 146 filename = filename.strip('"').strip("'")
147 147 # If the input is an absolute path, just check it exists
148 148 if os.path.isabs(filename) and os.path.isfile(filename):
149 149 return filename
150 150
151 151 if path_dirs is None:
152 152 path_dirs = ("",)
153 153 elif isinstance(path_dirs, basestring):
154 154 path_dirs = (path_dirs,)
155 155
156 156 for path in path_dirs:
157 157 if path == '.': path = os.getcwdu()
158 158 testname = expand_path(os.path.join(path, filename))
159 159 if os.path.isfile(testname):
160 160 return os.path.abspath(testname)
161 161
162 162 raise IOError("File %r does not exist in any of the search paths: %r" %
163 163 (filename, path_dirs) )
164 164
165 165
166 166 class HomeDirError(Exception):
167 167 pass
168 168
169 169
170 170 def get_home_dir():
171 171 """Return the closest possible equivalent to a 'home' directory.
172 172
173 173 * On POSIX, we try $HOME.
174 174 * On Windows we try:
175 175 - %HOMESHARE%
176 176 - %HOMEDRIVE\%HOMEPATH%
177 177 - %USERPROFILE%
178 178 - Registry hack for My Documents
179 179 - %HOME%: rare, but some people with unix-like setups may have defined it
180 180 * On Dos C:\
181 181
182 182 Currently only Posix and NT are implemented, a HomeDirError exception is
183 183 raised for all other OSes.
184 184 """
185 185
186 186 env = os.environ
187 187
188 188 # first, check py2exe distribution root directory for _ipython.
189 189 # This overrides all. Normally does not exist.
190 190
191 191 if hasattr(sys, "frozen"): #Is frozen by py2exe
192 192 if '\\library.zip\\' in IPython.__file__.lower():#libraries compressed to zip-file
193 193 root, rest = IPython.__file__.lower().split('library.zip')
194 194 else:
195 195 root=os.path.join(os.path.split(IPython.__file__)[0],"../../")
196 196 root=os.path.abspath(root).rstrip('\\')
197 197 if _writable_dir(os.path.join(root, '_ipython')):
198 198 os.environ["IPYKITROOT"] = root
199 199 return py3compat.cast_unicode(root, fs_encoding)
200 200
201 201 if os.name == 'posix':
202 202 # Linux, Unix, AIX, OS X
203 203 try:
204 204 homedir = env['HOME']
205 205 except KeyError:
206 206 # Last-ditch attempt at finding a suitable $HOME, on systems where
207 207 # it may not be defined in the environment but the system shell
208 208 # still knows it - reported once as:
209 209 # https://github.com/ipython/ipython/issues/154
210 210 from subprocess import Popen, PIPE
211 211 homedir = Popen('echo $HOME', shell=True,
212 212 stdout=PIPE).communicate()[0].strip()
213 213 if homedir:
214 214 return py3compat.cast_unicode(homedir, fs_encoding)
215 215 else:
216 216 raise HomeDirError('Undefined $HOME, IPython cannot proceed.')
217 217 else:
218 218 return py3compat.cast_unicode(homedir, fs_encoding)
219 219 elif os.name == 'nt':
220 220 # Now for win9x, XP, Vista, 7?
221 221 # For some strange reason all of these return 'nt' for os.name.
222 222 # First look for a network home directory. This will return the UNC
223 223 # path (\\server\\Users\%username%) not the mapped path (Z:\). This
224 224 # is needed when running IPython on cluster where all paths have to
225 225 # be UNC.
226 226 try:
227 227 homedir = env['HOMESHARE']
228 228 except KeyError:
229 229 pass
230 230 else:
231 231 if _writable_dir(homedir):
232 232 return py3compat.cast_unicode(homedir, fs_encoding)
233 233
234 234 # Now look for a local home directory
235 235 try:
236 236 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
237 237 except KeyError:
238 238 pass
239 239 else:
240 240 if _writable_dir(homedir):
241 241 return py3compat.cast_unicode(homedir, fs_encoding)
242 242
243 243 # Now the users profile directory
244 244 try:
245 245 homedir = os.path.join(env['USERPROFILE'])
246 246 except KeyError:
247 247 pass
248 248 else:
249 249 if _writable_dir(homedir):
250 250 return py3compat.cast_unicode(homedir, fs_encoding)
251 251
252 252 # Use the registry to get the 'My Documents' folder.
253 253 try:
254 254 import _winreg as wreg
255 255 key = wreg.OpenKey(
256 256 wreg.HKEY_CURRENT_USER,
257 257 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
258 258 )
259 259 homedir = wreg.QueryValueEx(key,'Personal')[0]
260 260 key.Close()
261 261 except:
262 262 pass
263 263 else:
264 264 if _writable_dir(homedir):
265 265 return py3compat.cast_unicode(homedir, fs_encoding)
266 266
267 267 # A user with a lot of unix tools in win32 may have defined $HOME.
268 268 # Try this as a last ditch option.
269 269 try:
270 270 homedir = env['HOME']
271 271 except KeyError:
272 272 pass
273 273 else:
274 274 if _writable_dir(homedir):
275 275 return py3compat.cast_unicode(homedir, fs_encoding)
276 276
277 277 # If all else fails, raise HomeDirError
278 278 raise HomeDirError('No valid home directory could be found')
279 279 elif os.name == 'dos':
280 280 # Desperate, may do absurd things in classic MacOS. May work under DOS.
281 281 return u'C:\\'
282 282 else:
283 283 raise HomeDirError('No valid home directory could be found for your OS')
284 284
285 285 def get_xdg_dir():
286 286 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
287 287
288 288 This is only for posix (Linux,Unix,OS X, etc) systems.
289 289 """
290 290
291 291 env = os.environ
292 292
293 293 if os.name == 'posix':
294 294 # Linux, Unix, AIX, OS X
295 295 # use ~/.config if not set OR empty
296 296 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
297 297 if xdg and _writable_dir(xdg):
298 298 return py3compat.cast_unicode(xdg, fs_encoding)
299 299
300 300 return None
301 301
302 302
303 303 def get_ipython_dir():
304 304 """Get the IPython directory for this platform and user.
305 305
306 306 This uses the logic in `get_home_dir` to find the home directory
307 307 and then adds .ipython to the end of the path.
308 308 """
309 309
310 310 env = os.environ
311 311 pjoin = os.path.join
312 312
313 313
314 314 ipdir_def = '.ipython'
315 315 xdg_def = 'ipython'
316 316
317 317 home_dir = get_home_dir()
318 318 xdg_dir = get_xdg_dir()
319 319 # import pdb; pdb.set_trace() # dbg
320 320 ipdir = env.get('IPYTHON_DIR', env.get('IPYTHONDIR', None))
321 321 if ipdir is None:
322 322 # not set explicitly, use XDG_CONFIG_HOME or HOME
323 323 home_ipdir = pjoin(home_dir, ipdir_def)
324 324 if xdg_dir:
325 325 # use XDG, as long as the user isn't already
326 326 # using $HOME/.ipython and *not* XDG/ipython
327 327
328 328 xdg_ipdir = pjoin(xdg_dir, xdg_def)
329 329
330 330 if _writable_dir(xdg_ipdir) or not _writable_dir(home_ipdir):
331 331 ipdir = xdg_ipdir
332 332
333 333 if ipdir is None:
334 334 # not using XDG
335 335 ipdir = home_ipdir
336 336
337 337 ipdir = os.path.normpath(os.path.expanduser(ipdir))
338 338
339 339 if os.path.exists(ipdir) and not _writable_dir(ipdir):
340 340 # ipdir exists, but is not writable
341 341 warnings.warn("IPython dir '%s' is not a writable location,"
342 342 " using a temp directory."%ipdir)
343 343 ipdir = tempfile.mkdtemp()
344 344 elif not os.path.exists(ipdir):
345 345 parent = ipdir.rsplit(os.path.sep, 1)[0]
346 346 if not _writable_dir(parent):
347 347 # ipdir does not exist and parent isn't writable
348 348 warnings.warn("IPython parent '%s' is not a writable location,"
349 349 " using a temp directory."%parent)
350 350 ipdir = tempfile.mkdtemp()
351 351
352 352 return py3compat.cast_unicode(ipdir, fs_encoding)
353 353
354 354
355 355 def get_ipython_package_dir():
356 356 """Get the base directory where IPython itself is installed."""
357 357 ipdir = os.path.dirname(IPython.__file__)
358 358 return py3compat.cast_unicode(ipdir, fs_encoding)
359 359
360 360
361 361 def get_ipython_module_path(module_str):
362 362 """Find the path to an IPython module in this version of IPython.
363 363
364 364 This will always find the version of the module that is in this importable
365 365 IPython package. This will always return the path to the ``.py``
366 366 version of the module.
367 367 """
368 368 if module_str == 'IPython':
369 369 return os.path.join(get_ipython_package_dir(), '__init__.py')
370 370 mod = import_item(module_str)
371 371 the_path = mod.__file__.replace('.pyc', '.py')
372 372 the_path = the_path.replace('.pyo', '.py')
373 373 return py3compat.cast_unicode(the_path, fs_encoding)
374 374
375 def locate_profile(profile='default'):
376 """Find the path to the folder associated with a given profile.
377
378 I.e. find $IPYTHON_DIR/profile_whatever.
379 """
380 from IPython.core.profiledir import ProfileDir, ProfileDirError
381 try:
382 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
383 except ProfileDirError:
384 # IOError makes more sense when people are expecting a path
385 raise IOError("Couldn't find profile %r" % profile)
386 return pd.location
375 387
376 388 def expand_path(s):
377 389 """Expand $VARS and ~names in a string, like a shell
378 390
379 391 :Examples:
380 392
381 393 In [2]: os.environ['FOO']='test'
382 394
383 395 In [3]: expand_path('variable FOO is $FOO')
384 396 Out[3]: 'variable FOO is test'
385 397 """
386 398 # This is a pretty subtle hack. When expand user is given a UNC path
387 399 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
388 400 # the $ to get (\\server\share\%username%). I think it considered $
389 401 # alone an empty var. But, we need the $ to remains there (it indicates
390 402 # a hidden share).
391 403 if os.name=='nt':
392 404 s = s.replace('$\\', 'IPYTHON_TEMP')
393 405 s = os.path.expandvars(os.path.expanduser(s))
394 406 if os.name=='nt':
395 407 s = s.replace('IPYTHON_TEMP', '$\\')
396 408 return s
397 409
398 410
399 411 def target_outdated(target,deps):
400 412 """Determine whether a target is out of date.
401 413
402 414 target_outdated(target,deps) -> 1/0
403 415
404 416 deps: list of filenames which MUST exist.
405 417 target: single filename which may or may not exist.
406 418
407 419 If target doesn't exist or is older than any file listed in deps, return
408 420 true, otherwise return false.
409 421 """
410 422 try:
411 423 target_time = os.path.getmtime(target)
412 424 except os.error:
413 425 return 1
414 426 for dep in deps:
415 427 dep_time = os.path.getmtime(dep)
416 428 if dep_time > target_time:
417 429 #print "For target",target,"Dep failed:",dep # dbg
418 430 #print "times (dep,tar):",dep_time,target_time # dbg
419 431 return 1
420 432 return 0
421 433
422 434
423 435 def target_update(target,deps,cmd):
424 436 """Update a target with a given command given a list of dependencies.
425 437
426 438 target_update(target,deps,cmd) -> runs cmd if target is outdated.
427 439
428 440 This is just a wrapper around target_outdated() which calls the given
429 441 command if target is outdated."""
430 442
431 443 if target_outdated(target,deps):
432 444 system(cmd)
433 445
434 446 def filehash(path):
435 447 """Make an MD5 hash of a file, ignoring any differences in line
436 448 ending characters."""
437 449 with open(path, "rU") as f:
438 450 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
439 451
440 452 # If the config is unmodified from the default, we'll just delete it.
441 453 # These are consistent for 0.10.x, thankfully. We're not going to worry about
442 454 # older versions.
443 455 old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3',
444 456 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'}
445 457
446 458 def check_for_old_config(ipython_dir=None):
447 459 """Check for old config files, and present a warning if they exist.
448 460
449 461 A link to the docs of the new config is included in the message.
450 462
451 463 This should mitigate confusion with the transition to the new
452 464 config system in 0.11.
453 465 """
454 466 if ipython_dir is None:
455 467 ipython_dir = get_ipython_dir()
456 468
457 469 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
458 470 warned = False
459 471 for cfg in old_configs:
460 472 f = os.path.join(ipython_dir, cfg)
461 473 if os.path.exists(f):
462 474 if filehash(f) == old_config_md5.get(cfg, ''):
463 475 os.unlink(f)
464 476 else:
465 477 warnings.warn("Found old IPython config file %r (modified by user)"%f)
466 478 warned = True
467 479
468 480 if warned:
469 481 warnings.warn("""
470 482 The IPython configuration system has changed as of 0.11, and these files will
471 483 be ignored. See http://ipython.github.com/ipython-doc/dev/config for details
472 484 of the new config system.
473 485 To start configuring IPython, do `ipython profile create`, and edit
474 486 `ipython_config.py` in <ipython_dir>/profile_default.
475 487 If you need to leave the old config files in place for an older version of
476 488 IPython and want to suppress this warning message, set
477 489 `c.InteractiveShellApp.ignore_old_config=True` in the new config.""")
478 490
479 491 def get_security_file(filename, profile='default'):
480 492 """Return the absolute path of a security file given by filename and profile
481 493
482 494 This allows users and developers to find security files without
483 495 knowledge of the IPython directory structure. The search path
484 496 will be ['.', profile.security_dir]
485 497
486 498 Parameters
487 499 ----------
488 500
489 501 filename : str
490 502 The file to be found. If it is passed as an absolute path, it will
491 503 simply be returned.
492 504 profile : str [default: 'default']
493 505 The name of the profile to search. Leaving this unspecified
494 506 The file to be found. If it is passed as an absolute path, fname will
495 507 simply be returned.
496 508
497 509 Returns
498 510 -------
499 511 Raises :exc:`IOError` if file not found or returns absolute path to file.
500 512 """
501 513 # import here, because profiledir also imports from utils.path
502 514 from IPython.core.profiledir import ProfileDir
503 515 try:
504 516 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
505 517 except Exception:
506 518 # will raise ProfileDirError if no such profile
507 519 raise IOError("Profile %r not found")
508 520 return filefind(filename, ['.', pd.security_dir])
509 521
General Comments 0
You need to be logged in to leave comments. Login now