path.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. # encoding: utf-8
  2. """
  3. Utilities for path handling.
  4. """
  5. # Copyright (c) IPython Development Team.
  6. # Distributed under the terms of the Modified BSD License.
  7. import os
  8. import sys
  9. import errno
  10. import shutil
  11. import random
  12. from . import py3compat
  13. fs_encoding = sys.getfilesystemencoding()
  14. def filefind(filename, path_dirs=None):
  15. """Find a file by looking through a sequence of paths.
  16. This iterates through a sequence of paths looking for a file and returns
  17. the full, absolute path of the first occurence of the file. If no set of
  18. path dirs is given, the filename is tested as is, after running through
  19. :func:`expandvars` and :func:`expanduser`. Thus a simple call::
  20. filefind('myfile.txt')
  21. will find the file in the current working dir, but::
  22. filefind('~/myfile.txt')
  23. Will find the file in the users home directory. This function does not
  24. automatically try any paths, such as the cwd or the user's home directory.
  25. Parameters
  26. ----------
  27. filename : str
  28. The filename to look for.
  29. path_dirs : str, None or sequence of str
  30. The sequence of paths to look for the file in. If None, the filename
  31. need to be absolute or be in the cwd. If a string, the string is
  32. put into a sequence and the searched. If a sequence, walk through
  33. each element and join with ``filename``, calling :func:`expandvars`
  34. and :func:`expanduser` before testing for existence.
  35. Returns
  36. -------
  37. Raises :exc:`IOError` or returns absolute path to file.
  38. """
  39. # If paths are quoted, abspath gets confused, strip them...
  40. filename = filename.strip('"').strip("'")
  41. # If the input is an absolute path, just check it exists
  42. if os.path.isabs(filename) and os.path.isfile(filename):
  43. return filename
  44. if path_dirs is None:
  45. path_dirs = ("",)
  46. elif isinstance(path_dirs, py3compat.string_types):
  47. path_dirs = (path_dirs,)
  48. for path in path_dirs:
  49. if path == '.': path = py3compat.getcwd()
  50. testname = expand_path(os.path.join(path, filename))
  51. if os.path.isfile(testname):
  52. return os.path.abspath(testname)
  53. raise IOError("File %r does not exist in any of the search paths: %r" %
  54. (filename, path_dirs) )
  55. def expand_path(s):
  56. """Expand $VARS and ~names in a string, like a shell
  57. :Examples:
  58. In [2]: os.environ['FOO']='test'
  59. In [3]: expand_path('variable FOO is $FOO')
  60. Out[3]: 'variable FOO is test'
  61. """
  62. # This is a pretty subtle hack. When expand user is given a UNC path
  63. # on Windows (\\server\share$\%username%), os.path.expandvars, removes
  64. # the $ to get (\\server\share\%username%). I think it considered $
  65. # alone an empty var. But, we need the $ to remains there (it indicates
  66. # a hidden share).
  67. if os.name=='nt':
  68. s = s.replace('$\\', 'IPYTHON_TEMP')
  69. s = os.path.expandvars(os.path.expanduser(s))
  70. if os.name=='nt':
  71. s = s.replace('IPYTHON_TEMP', '$\\')
  72. return s
  73. try:
  74. ENOLINK = errno.ENOLINK
  75. except AttributeError:
  76. ENOLINK = 1998
  77. def link(src, dst):
  78. """Hard links ``src`` to ``dst``, returning 0 or errno.
  79. Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
  80. supported by the operating system.
  81. """
  82. if not hasattr(os, "link"):
  83. return ENOLINK
  84. link_errno = 0
  85. try:
  86. os.link(src, dst)
  87. except OSError as e:
  88. link_errno = e.errno
  89. return link_errno
  90. def link_or_copy(src, dst):
  91. """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
  92. Attempts to maintain the semantics of ``shutil.copy``.
  93. Because ``os.link`` does not overwrite files, a unique temporary file
  94. will be used if the target already exists, then that file will be moved
  95. into place.
  96. """
  97. if os.path.isdir(dst):
  98. dst = os.path.join(dst, os.path.basename(src))
  99. link_errno = link(src, dst)
  100. if link_errno == errno.EEXIST:
  101. if os.stat(src).st_ino == os.stat(dst).st_ino:
  102. # dst is already a hard link to the correct file, so we don't need
  103. # to do anything else. If we try to link and rename the file
  104. # anyway, we get duplicate files - see http://bugs.python.org/issue21876
  105. return
  106. new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
  107. try:
  108. link_or_copy(src, new_dst)
  109. except:
  110. try:
  111. os.remove(new_dst)
  112. except OSError:
  113. pass
  114. raise
  115. os.rename(new_dst, dst)
  116. elif link_errno != 0:
  117. # Either link isn't supported, or the filesystem doesn't support
  118. # linking, or 'src' and 'dst' are on different filesystems.
  119. shutil.copy(src, dst)
  120. def ensure_dir_exists(path, mode=0o755):
  121. """ensure that a directory exists
  122. If it doesn't exist, try to create it and protect against a race condition
  123. if another process is doing the same.
  124. The default permissions are 755, which differ from os.makedirs default of 777.
  125. """
  126. if not os.path.exists(path):
  127. try:
  128. os.makedirs(path, mode=mode)
  129. except OSError as e:
  130. if e.errno != errno.EEXIST:
  131. raise
  132. elif not os.path.isdir(path):
  133. raise IOError("%r exists but is not a directory" % path)