##// END OF EJS Templates
Remove old config files on startup if they match the default (i.e. were never modified by the user)
Thomas Kluyver -
Show More
@@ -1,433 +1,450 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 from hashlib import md5
19 20
20 21 import IPython
21 22 from IPython.utils import warn
22 23 from IPython.utils.process import system
23 24 from IPython.utils.importstring import import_item
24 25
25 26 #-----------------------------------------------------------------------------
26 27 # Code
27 28 #-----------------------------------------------------------------------------
28 29
29 30 fs_encoding = sys.getfilesystemencoding()
30 31
31 32 def _cast_unicode(s, enc=None):
32 33 """Turn 8-bit strings into unicode."""
33 34 if isinstance(s, bytes):
34 35 enc = enc or sys.getdefaultencoding()
35 36 return s.decode(enc)
36 37 return s
37 38
38 39
39 40 def _get_long_path_name(path):
40 41 """Dummy no-op."""
41 42 return path
42 43
43 44 if sys.platform == 'win32':
44 45 def _get_long_path_name(path):
45 46 """Get a long path name (expand ~) on Windows using ctypes.
46 47
47 48 Examples
48 49 --------
49 50
50 51 >>> get_long_path_name('c:\\docume~1')
51 52 u'c:\\\\Documents and Settings'
52 53
53 54 """
54 55 try:
55 56 import ctypes
56 57 except ImportError:
57 58 raise ImportError('you need to have ctypes installed for this to work')
58 59 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
59 60 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
60 61 ctypes.c_uint ]
61 62
62 63 buf = ctypes.create_unicode_buffer(260)
63 64 rv = _GetLongPathName(path, buf, 260)
64 65 if rv == 0 or rv > 260:
65 66 return path
66 67 else:
67 68 return buf.value
68 69
69 70
70 71 def get_long_path_name(path):
71 72 """Expand a path into its long form.
72 73
73 74 On Windows this expands any ~ in the paths. On other platforms, it is
74 75 a null operation.
75 76 """
76 77 return _get_long_path_name(path)
77 78
78 79
79 80 def get_py_filename(name):
80 81 """Return a valid python filename in the current directory.
81 82
82 83 If the given name is not a file, it adds '.py' and searches again.
83 84 Raises IOError with an informative message if the file isn't found."""
84 85
85 86 name = os.path.expanduser(name)
86 87 if not os.path.isfile(name) and not name.endswith('.py'):
87 88 name += '.py'
88 89 if os.path.isfile(name):
89 90 return name
90 91 else:
91 92 raise IOError,'File `%s` not found.' % name
92 93
93 94
94 95 def filefind(filename, path_dirs=None):
95 96 """Find a file by looking through a sequence of paths.
96 97
97 98 This iterates through a sequence of paths looking for a file and returns
98 99 the full, absolute path of the first occurence of the file. If no set of
99 100 path dirs is given, the filename is tested as is, after running through
100 101 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
101 102
102 103 filefind('myfile.txt')
103 104
104 105 will find the file in the current working dir, but::
105 106
106 107 filefind('~/myfile.txt')
107 108
108 109 Will find the file in the users home directory. This function does not
109 110 automatically try any paths, such as the cwd or the user's home directory.
110 111
111 112 Parameters
112 113 ----------
113 114 filename : str
114 115 The filename to look for.
115 116 path_dirs : str, None or sequence of str
116 117 The sequence of paths to look for the file in. If None, the filename
117 118 need to be absolute or be in the cwd. If a string, the string is
118 119 put into a sequence and the searched. If a sequence, walk through
119 120 each element and join with ``filename``, calling :func:`expandvars`
120 121 and :func:`expanduser` before testing for existence.
121 122
122 123 Returns
123 124 -------
124 125 Raises :exc:`IOError` or returns absolute path to file.
125 126 """
126 127
127 128 # If paths are quoted, abspath gets confused, strip them...
128 129 filename = filename.strip('"').strip("'")
129 130 # If the input is an absolute path, just check it exists
130 131 if os.path.isabs(filename) and os.path.isfile(filename):
131 132 return filename
132 133
133 134 if path_dirs is None:
134 135 path_dirs = ("",)
135 136 elif isinstance(path_dirs, basestring):
136 137 path_dirs = (path_dirs,)
137 138
138 139 for path in path_dirs:
139 140 if path == '.': path = os.getcwd()
140 141 testname = expand_path(os.path.join(path, filename))
141 142 if os.path.isfile(testname):
142 143 return os.path.abspath(testname)
143 144
144 145 raise IOError("File %r does not exist in any of the search paths: %r" %
145 146 (filename, path_dirs) )
146 147
147 148
148 149 class HomeDirError(Exception):
149 150 pass
150 151
151 152
152 153 def get_home_dir():
153 154 """Return the closest possible equivalent to a 'home' directory.
154 155
155 156 * On POSIX, we try $HOME.
156 157 * On Windows we try:
157 158 - %HOMESHARE%
158 159 - %HOMEDRIVE\%HOMEPATH%
159 160 - %USERPROFILE%
160 161 - Registry hack for My Documents
161 162 - %HOME%: rare, but some people with unix-like setups may have defined it
162 163 * On Dos C:\
163 164
164 165 Currently only Posix and NT are implemented, a HomeDirError exception is
165 166 raised for all other OSes.
166 167 """
167 168
168 169 isdir = os.path.isdir
169 170 env = os.environ
170 171
171 172 # first, check py2exe distribution root directory for _ipython.
172 173 # This overrides all. Normally does not exist.
173 174
174 175 if hasattr(sys, "frozen"): #Is frozen by py2exe
175 176 if '\\library.zip\\' in IPython.__file__.lower():#libraries compressed to zip-file
176 177 root, rest = IPython.__file__.lower().split('library.zip')
177 178 else:
178 179 root=os.path.join(os.path.split(IPython.__file__)[0],"../../")
179 180 root=os.path.abspath(root).rstrip('\\')
180 181 if isdir(os.path.join(root, '_ipython')):
181 182 os.environ["IPYKITROOT"] = root
182 183 return _cast_unicode(root, fs_encoding)
183 184
184 185 if os.name == 'posix':
185 186 # Linux, Unix, AIX, OS X
186 187 try:
187 188 homedir = env['HOME']
188 189 except KeyError:
189 190 # Last-ditch attempt at finding a suitable $HOME, on systems where
190 191 # it may not be defined in the environment but the system shell
191 192 # still knows it - reported once as:
192 193 # https://github.com/ipython/ipython/issues/154
193 194 from subprocess import Popen, PIPE
194 195 homedir = Popen('echo $HOME', shell=True,
195 196 stdout=PIPE).communicate()[0].strip()
196 197 if homedir:
197 198 return _cast_unicode(homedir, fs_encoding)
198 199 else:
199 200 raise HomeDirError('Undefined $HOME, IPython cannot proceed.')
200 201 else:
201 202 return _cast_unicode(homedir, fs_encoding)
202 203 elif os.name == 'nt':
203 204 # Now for win9x, XP, Vista, 7?
204 205 # For some strange reason all of these return 'nt' for os.name.
205 206 # First look for a network home directory. This will return the UNC
206 207 # path (\\server\\Users\%username%) not the mapped path (Z:\). This
207 208 # is needed when running IPython on cluster where all paths have to
208 209 # be UNC.
209 210 try:
210 211 homedir = env['HOMESHARE']
211 212 except KeyError:
212 213 pass
213 214 else:
214 215 if isdir(homedir):
215 216 return _cast_unicode(homedir, fs_encoding)
216 217
217 218 # Now look for a local home directory
218 219 try:
219 220 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
220 221 except KeyError:
221 222 pass
222 223 else:
223 224 if isdir(homedir):
224 225 return _cast_unicode(homedir, fs_encoding)
225 226
226 227 # Now the users profile directory
227 228 try:
228 229 homedir = os.path.join(env['USERPROFILE'])
229 230 except KeyError:
230 231 pass
231 232 else:
232 233 if isdir(homedir):
233 234 return _cast_unicode(homedir, fs_encoding)
234 235
235 236 # Use the registry to get the 'My Documents' folder.
236 237 try:
237 238 import _winreg as wreg
238 239 key = wreg.OpenKey(
239 240 wreg.HKEY_CURRENT_USER,
240 241 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
241 242 )
242 243 homedir = wreg.QueryValueEx(key,'Personal')[0]
243 244 key.Close()
244 245 except:
245 246 pass
246 247 else:
247 248 if isdir(homedir):
248 249 return _cast_unicode(homedir, fs_encoding)
249 250
250 251 # A user with a lot of unix tools in win32 may have defined $HOME.
251 252 # Try this as a last ditch option.
252 253 try:
253 254 homedir = env['HOME']
254 255 except KeyError:
255 256 pass
256 257 else:
257 258 if isdir(homedir):
258 259 return _cast_unicode(homedir, fs_encoding)
259 260
260 261 # If all else fails, raise HomeDirError
261 262 raise HomeDirError('No valid home directory could be found')
262 263 elif os.name == 'dos':
263 264 # Desperate, may do absurd things in classic MacOS. May work under DOS.
264 265 return u'C:\\'
265 266 else:
266 267 raise HomeDirError('No valid home directory could be found for your OS')
267 268
268 269 def get_xdg_dir():
269 270 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
270 271
271 272 This is only for posix (Linux,Unix,OS X, etc) systems.
272 273 """
273 274
274 275 isdir = os.path.isdir
275 276 env = os.environ
276 277
277 278 if os.name == 'posix':
278 279 # Linux, Unix, AIX, OS X
279 280 # use ~/.config if not set OR empty
280 281 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
281 282 if xdg and isdir(xdg):
282 283 return _cast_unicode(xdg, fs_encoding)
283 284
284 285 return None
285 286
286 287
287 288 def get_ipython_dir():
288 289 """Get the IPython directory for this platform and user.
289 290
290 291 This uses the logic in `get_home_dir` to find the home directory
291 292 and the adds .ipython to the end of the path.
292 293 """
293 294
294 295 env = os.environ
295 296 pjoin = os.path.join
296 297 exists = os.path.exists
297 298
298 299 ipdir_def = '.ipython'
299 300 xdg_def = 'ipython'
300 301
301 302 home_dir = get_home_dir()
302 303 xdg_dir = get_xdg_dir()
303 304 # import pdb; pdb.set_trace() # dbg
304 305 ipdir = env.get('IPYTHON_DIR', env.get('IPYTHONDIR', None))
305 306 if ipdir is None:
306 307 # not set explicitly, use XDG_CONFIG_HOME or HOME
307 308 home_ipdir = pjoin(home_dir, ipdir_def)
308 309 if xdg_dir:
309 310 # use XDG, as long as the user isn't already
310 311 # using $HOME/.ipython and *not* XDG/ipython
311 312
312 313 xdg_ipdir = pjoin(xdg_dir, xdg_def)
313 314
314 315 if exists(xdg_ipdir) or not exists(home_ipdir):
315 316 ipdir = xdg_ipdir
316 317
317 318 if ipdir is None:
318 319 # not using XDG
319 320 ipdir = home_ipdir
320 321
321 322 ipdir = os.path.normpath(os.path.expanduser(ipdir))
322 323
323 324 return _cast_unicode(ipdir, fs_encoding)
324 325
325 326
326 327 def get_ipython_package_dir():
327 328 """Get the base directory where IPython itself is installed."""
328 329 ipdir = os.path.dirname(IPython.__file__)
329 330 return _cast_unicode(ipdir, fs_encoding)
330 331
331 332
332 333 def get_ipython_module_path(module_str):
333 334 """Find the path to an IPython module in this version of IPython.
334 335
335 336 This will always find the version of the module that is in this importable
336 337 IPython package. This will always return the path to the ``.py``
337 338 version of the module.
338 339 """
339 340 if module_str == 'IPython':
340 341 return os.path.join(get_ipython_package_dir(), '__init__.py')
341 342 mod = import_item(module_str)
342 343 the_path = mod.__file__.replace('.pyc', '.py')
343 344 the_path = the_path.replace('.pyo', '.py')
344 345 return _cast_unicode(the_path, fs_encoding)
345 346
346 347
347 348 def expand_path(s):
348 349 """Expand $VARS and ~names in a string, like a shell
349 350
350 351 :Examples:
351 352
352 353 In [2]: os.environ['FOO']='test'
353 354
354 355 In [3]: expand_path('variable FOO is $FOO')
355 356 Out[3]: 'variable FOO is test'
356 357 """
357 358 # This is a pretty subtle hack. When expand user is given a UNC path
358 359 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
359 360 # the $ to get (\\server\share\%username%). I think it considered $
360 361 # alone an empty var. But, we need the $ to remains there (it indicates
361 362 # a hidden share).
362 363 if os.name=='nt':
363 364 s = s.replace('$\\', 'IPYTHON_TEMP')
364 365 s = os.path.expandvars(os.path.expanduser(s))
365 366 if os.name=='nt':
366 367 s = s.replace('IPYTHON_TEMP', '$\\')
367 368 return s
368 369
369 370
370 371 def target_outdated(target,deps):
371 372 """Determine whether a target is out of date.
372 373
373 374 target_outdated(target,deps) -> 1/0
374 375
375 376 deps: list of filenames which MUST exist.
376 377 target: single filename which may or may not exist.
377 378
378 379 If target doesn't exist or is older than any file listed in deps, return
379 380 true, otherwise return false.
380 381 """
381 382 try:
382 383 target_time = os.path.getmtime(target)
383 384 except os.error:
384 385 return 1
385 386 for dep in deps:
386 387 dep_time = os.path.getmtime(dep)
387 388 if dep_time > target_time:
388 389 #print "For target",target,"Dep failed:",dep # dbg
389 390 #print "times (dep,tar):",dep_time,target_time # dbg
390 391 return 1
391 392 return 0
392 393
393 394
394 395 def target_update(target,deps,cmd):
395 396 """Update a target with a given command given a list of dependencies.
396 397
397 398 target_update(target,deps,cmd) -> runs cmd if target is outdated.
398 399
399 400 This is just a wrapper around target_outdated() which calls the given
400 401 command if target is outdated."""
401 402
402 403 if target_outdated(target,deps):
403 404 system(cmd)
404 405
406 def filehash(path):
407 """Make an MD5 hash of a file, ignoring any differences in line
408 ending characters."""
409 with open(path, "rU") as f:
410 return md5(f.read()).hexdigest()
411
412 # If the config is unmodified from the default, we'll just delete it.
413 # These are consistent for 0.10.x, thankfully. We're not going to worry about
414 # older versions.
415 old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3',
416 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'}
417
405 418 def check_for_old_config(ipython_dir=None):
406 419 """Check for old config files, and present a warning if they exist.
407 420
408 421 A link to the docs of the new config is included in the message.
409 422
410 423 This should mitigate confusion with the transition to the new
411 424 config system in 0.11.
412 425 """
413 426 if ipython_dir is None:
414 427 ipython_dir = get_ipython_dir()
415 428
416 429 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
417 430 warned = False
418 431 for cfg in old_configs:
419 432 f = os.path.join(ipython_dir, cfg)
420 433 if os.path.exists(f):
421 warned = True
422 warn.warn("Found old IPython config file %r"%f)
434 if filehash(f) == old_config_md5[cfg]:
435 os.unlink(f)
436 warn.info("Removed unmodified old IPython config file %r"%f)
437 else:
438 warned = True
439 warn.warn("Found old IPython config file %r (modified by user)"%f)
423 440
424 441 if warned:
425 442 warn.warn("""
426 443 The IPython configuration system has changed as of 0.11, and these files
427 444 will be ignored. See http://ipython.github.com/ipython-doc/dev/config for
428 445 details on the new config system. To start configuring IPython, do
429 446 `ipython profile create`, and edit `ipython_config.py` in
430 447 <ipython_dir>/profile_default, adding
431 448 `c.InteractiveShellApp.ignore_old_config=True`""")
432 449
433 450
General Comments 0
You need to be logged in to leave comments. Login now