You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

307 lines
8.9 KiB

4 years ago
  1. """Notebook related utilities"""
  2. # Copyright (c) Jupyter Development Team.
  3. # Distributed under the terms of the Modified BSD License.
  4. from __future__ import print_function
  5. import ctypes
  6. import errno
  7. import os
  8. import stat
  9. import sys
  10. from distutils.version import LooseVersion
  11. try:
  12. from urllib.parse import quote, unquote, urlparse
  13. except ImportError:
  14. from urllib import quote, unquote
  15. from urlparse import urlparse
  16. from ipython_genutils import py3compat
  17. # UF_HIDDEN is a stat flag not defined in the stat module.
  18. # It is used by BSD to indicate hidden files.
  19. UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768)
  20. def exists(path):
  21. """Replacement for `os.path.exists` which works for host mapped volumes
  22. on Windows containers
  23. """
  24. try:
  25. os.lstat(path)
  26. except OSError:
  27. return False
  28. return True
  29. def url_path_join(*pieces):
  30. """Join components of url into a relative url
  31. Use to prevent double slash when joining subpath. This will leave the
  32. initial and final / in place
  33. """
  34. initial = pieces[0].startswith('/')
  35. final = pieces[-1].endswith('/')
  36. stripped = [s.strip('/') for s in pieces]
  37. result = '/'.join(s for s in stripped if s)
  38. if initial: result = '/' + result
  39. if final: result = result + '/'
  40. if result == '//': result = '/'
  41. return result
  42. def url_is_absolute(url):
  43. """Determine whether a given URL is absolute"""
  44. return urlparse(url).path.startswith("/")
  45. def path2url(path):
  46. """Convert a local file path to a URL"""
  47. pieces = [ quote(p) for p in path.split(os.sep) ]
  48. # preserve trailing /
  49. if pieces[-1] == '':
  50. pieces[-1] = '/'
  51. url = url_path_join(*pieces)
  52. return url
  53. def url2path(url):
  54. """Convert a URL to a local file path"""
  55. pieces = [ unquote(p) for p in url.split('/') ]
  56. path = os.path.join(*pieces)
  57. return path
  58. def url_escape(path):
  59. """Escape special characters in a URL path
  60. Turns '/foo bar/' into '/foo%20bar/'
  61. """
  62. parts = py3compat.unicode_to_str(path, encoding='utf8').split('/')
  63. return u'/'.join([quote(p) for p in parts])
  64. def url_unescape(path):
  65. """Unescape special characters in a URL path
  66. Turns '/foo%20bar/' into '/foo bar/'
  67. """
  68. return u'/'.join([
  69. py3compat.str_to_unicode(unquote(p), encoding='utf8')
  70. for p in py3compat.unicode_to_str(path, encoding='utf8').split('/')
  71. ])
  72. def is_file_hidden_win(abs_path, stat_res=None):
  73. """Is a file hidden?
  74. This only checks the file itself; it should be called in combination with
  75. checking the directory containing the file.
  76. Use is_hidden() instead to check the file and its parent directories.
  77. Parameters
  78. ----------
  79. abs_path : unicode
  80. The absolute path to check.
  81. stat_res : os.stat_result, optional
  82. Ignored on Windows, exists for compatibility with POSIX version of the
  83. function.
  84. """
  85. if os.path.basename(abs_path).startswith('.'):
  86. return True
  87. win32_FILE_ATTRIBUTE_HIDDEN = 0x02
  88. try:
  89. attrs = ctypes.windll.kernel32.GetFileAttributesW(
  90. py3compat.cast_unicode(abs_path)
  91. )
  92. except AttributeError:
  93. pass
  94. else:
  95. if attrs > 0 and attrs & win32_FILE_ATTRIBUTE_HIDDEN:
  96. return True
  97. return False
  98. def is_file_hidden_posix(abs_path, stat_res=None):
  99. """Is a file hidden?
  100. This only checks the file itself; it should be called in combination with
  101. checking the directory containing the file.
  102. Use is_hidden() instead to check the file and its parent directories.
  103. Parameters
  104. ----------
  105. abs_path : unicode
  106. The absolute path to check.
  107. stat_res : os.stat_result, optional
  108. The result of calling stat() on abs_path. If not passed, this function
  109. will call stat() internally.
  110. """
  111. if os.path.basename(abs_path).startswith('.'):
  112. return True
  113. if stat_res is None or stat.S_ISLNK(stat_res.st_mode):
  114. try:
  115. stat_res = os.stat(abs_path)
  116. except OSError as e:
  117. if e.errno == errno.ENOENT:
  118. return False
  119. raise
  120. # check that dirs can be listed
  121. if stat.S_ISDIR(stat_res.st_mode):
  122. # use x-access, not actual listing, in case of slow/large listings
  123. if not os.access(abs_path, os.X_OK | os.R_OK):
  124. return True
  125. # check UF_HIDDEN
  126. if getattr(stat_res, 'st_flags', 0) & UF_HIDDEN:
  127. return True
  128. return False
  129. if sys.platform == 'win32':
  130. is_file_hidden = is_file_hidden_win
  131. else:
  132. is_file_hidden = is_file_hidden_posix
  133. def is_hidden(abs_path, abs_root=''):
  134. """Is a file hidden or contained in a hidden directory?
  135. This will start with the rightmost path element and work backwards to the
  136. given root to see if a path is hidden or in a hidden directory. Hidden is
  137. determined by either name starting with '.' or the UF_HIDDEN flag as
  138. reported by stat.
  139. If abs_path is the same directory as abs_root, it will be visible even if
  140. that is a hidden folder. This only checks the visibility of files
  141. and directories *within* abs_root.
  142. Parameters
  143. ----------
  144. abs_path : unicode
  145. The absolute path to check for hidden directories.
  146. abs_root : unicode
  147. The absolute path of the root directory in which hidden directories
  148. should be checked for.
  149. """
  150. if os.path.normpath(abs_path) == os.path.normpath(abs_root):
  151. return False
  152. if is_file_hidden(abs_path):
  153. return True
  154. if not abs_root:
  155. abs_root = abs_path.split(os.sep, 1)[0] + os.sep
  156. inside_root = abs_path[len(abs_root):]
  157. if any(part.startswith('.') for part in inside_root.split(os.sep)):
  158. return True
  159. # check UF_HIDDEN on any location up to root.
  160. # is_file_hidden() already checked the file, so start from its parent dir
  161. path = os.path.dirname(abs_path)
  162. while path and path.startswith(abs_root) and path != abs_root:
  163. if not exists(path):
  164. path = os.path.dirname(path)
  165. continue
  166. try:
  167. # may fail on Windows junctions
  168. st = os.lstat(path)
  169. except OSError:
  170. return True
  171. if getattr(st, 'st_flags', 0) & UF_HIDDEN:
  172. return True
  173. path = os.path.dirname(path)
  174. return False
  175. def samefile_simple(path, other_path):
  176. """
  177. Fill in for os.path.samefile when it is unavailable (Windows+py2).
  178. Do a case-insensitive string comparison in this case
  179. plus comparing the full stat result (including times)
  180. because Windows + py2 doesn't support the stat fields
  181. needed for identifying if it's the same file (st_ino, st_dev).
  182. Only to be used if os.path.samefile is not available.
  183. Parameters
  184. -----------
  185. path: String representing a path to a file
  186. other_path: String representing a path to another file
  187. Returns
  188. -----------
  189. same: Boolean that is True if both path and other path are the same
  190. """
  191. path_stat = os.stat(path)
  192. other_path_stat = os.stat(other_path)
  193. return (path.lower() == other_path.lower()
  194. and path_stat == other_path_stat)
  195. def to_os_path(path, root=''):
  196. """Convert an API path to a filesystem path
  197. If given, root will be prepended to the path.
  198. root must be a filesystem path already.
  199. """
  200. parts = path.strip('/').split('/')
  201. parts = [p for p in parts if p != ''] # remove duplicate splits
  202. path = os.path.join(root, *parts)
  203. return path
  204. def to_api_path(os_path, root=''):
  205. """Convert a filesystem path to an API path
  206. If given, root will be removed from the path.
  207. root must be a filesystem path already.
  208. """
  209. if os_path.startswith(root):
  210. os_path = os_path[len(root):]
  211. parts = os_path.strip(os.path.sep).split(os.path.sep)
  212. parts = [p for p in parts if p != ''] # remove duplicate splits
  213. path = '/'.join(parts)
  214. return path
  215. def check_version(v, check):
  216. """check version string v >= check
  217. If dev/prerelease tags result in TypeError for string-number comparison,
  218. it is assumed that the dependency is satisfied.
  219. Users on dev branches are responsible for keeping their own packages up to date.
  220. """
  221. try:
  222. return LooseVersion(v) >= LooseVersion(check)
  223. except TypeError:
  224. return True
  225. # Copy of IPython.utils.process.check_pid:
  226. def _check_pid_win32(pid):
  227. import ctypes
  228. # OpenProcess returns 0 if no such process (of ours) exists
  229. # positive int otherwise
  230. return bool(ctypes.windll.kernel32.OpenProcess(1,0,pid))
  231. def _check_pid_posix(pid):
  232. """Copy of IPython.utils.process.check_pid"""
  233. try:
  234. os.kill(pid, 0)
  235. except OSError as err:
  236. if err.errno == errno.ESRCH:
  237. return False
  238. elif err.errno == errno.EPERM:
  239. # Don't have permission to signal the process - probably means it exists
  240. return True
  241. raise
  242. else:
  243. return True
  244. if sys.platform == 'win32':
  245. check_pid = _check_pid_win32
  246. else:
  247. check_pid = _check_pid_posix