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.

162 lines
4.7 KiB

4 years ago
  1. import time
  2. import os
  3. import sys
  4. import hashlib
  5. import gc
  6. import shutil
  7. import platform
  8. import errno
  9. import logging
  10. try:
  11. import cPickle as pickle
  12. except:
  13. import pickle
  14. from parso._compatibility import FileNotFoundError
  15. LOG = logging.getLogger(__name__)
  16. _PICKLE_VERSION = 30
  17. """
  18. Version number (integer) for file system cache.
  19. Increment this number when there are any incompatible changes in
  20. the parser tree classes. For example, the following changes
  21. are regarded as incompatible.
  22. - A class name is changed.
  23. - A class is moved to another module.
  24. - A __slot__ of a class is changed.
  25. """
  26. _VERSION_TAG = '%s-%s%s-%s' % (
  27. platform.python_implementation(),
  28. sys.version_info[0],
  29. sys.version_info[1],
  30. _PICKLE_VERSION
  31. )
  32. """
  33. Short name for distinguish Python implementations and versions.
  34. It's like `sys.implementation.cache_tag` but for Python < 3.3
  35. we generate something similar. See:
  36. http://docs.python.org/3/library/sys.html#sys.implementation
  37. """
  38. def _get_default_cache_path():
  39. if platform.system().lower() == 'windows':
  40. dir_ = os.path.join(os.getenv('LOCALAPPDATA') or '~', 'Parso', 'Parso')
  41. elif platform.system().lower() == 'darwin':
  42. dir_ = os.path.join('~', 'Library', 'Caches', 'Parso')
  43. else:
  44. dir_ = os.path.join(os.getenv('XDG_CACHE_HOME') or '~/.cache', 'parso')
  45. return os.path.expanduser(dir_)
  46. _default_cache_path = _get_default_cache_path()
  47. """
  48. The path where the cache is stored.
  49. On Linux, this defaults to ``~/.cache/parso/``, on OS X to
  50. ``~/Library/Caches/Parso/`` and on Windows to ``%LOCALAPPDATA%\\Parso\\Parso\\``.
  51. On Linux, if environment variable ``$XDG_CACHE_HOME`` is set,
  52. ``$XDG_CACHE_HOME/parso`` is used instead of the default one.
  53. """
  54. parser_cache = {}
  55. class _NodeCacheItem(object):
  56. def __init__(self, node, lines, change_time=None):
  57. self.node = node
  58. self.lines = lines
  59. if change_time is None:
  60. change_time = time.time()
  61. self.change_time = change_time
  62. def load_module(hashed_grammar, path, cache_path=None):
  63. """
  64. Returns a module or None, if it fails.
  65. """
  66. try:
  67. p_time = os.path.getmtime(path)
  68. except FileNotFoundError:
  69. return None
  70. try:
  71. module_cache_item = parser_cache[hashed_grammar][path]
  72. if p_time <= module_cache_item.change_time:
  73. return module_cache_item.node
  74. except KeyError:
  75. return _load_from_file_system(hashed_grammar, path, p_time, cache_path=cache_path)
  76. def _load_from_file_system(hashed_grammar, path, p_time, cache_path=None):
  77. cache_path = _get_hashed_path(hashed_grammar, path, cache_path=cache_path)
  78. try:
  79. try:
  80. if p_time > os.path.getmtime(cache_path):
  81. # Cache is outdated
  82. return None
  83. except OSError as e:
  84. if e.errno == errno.ENOENT:
  85. # In Python 2 instead of an IOError here we get an OSError.
  86. raise FileNotFoundError
  87. else:
  88. raise
  89. with open(cache_path, 'rb') as f:
  90. gc.disable()
  91. try:
  92. module_cache_item = pickle.load(f)
  93. finally:
  94. gc.enable()
  95. except FileNotFoundError:
  96. return None
  97. else:
  98. parser_cache.setdefault(hashed_grammar, {})[path] = module_cache_item
  99. LOG.debug('pickle loaded: %s', path)
  100. return module_cache_item.node
  101. def save_module(hashed_grammar, path, module, lines, pickling=True, cache_path=None):
  102. try:
  103. p_time = None if path is None else os.path.getmtime(path)
  104. except OSError:
  105. p_time = None
  106. pickling = False
  107. item = _NodeCacheItem(module, lines, p_time)
  108. parser_cache.setdefault(hashed_grammar, {})[path] = item
  109. if pickling and path is not None:
  110. _save_to_file_system(hashed_grammar, path, item, cache_path=cache_path)
  111. def _save_to_file_system(hashed_grammar, path, item, cache_path=None):
  112. with open(_get_hashed_path(hashed_grammar, path, cache_path=cache_path), 'wb') as f:
  113. pickle.dump(item, f, pickle.HIGHEST_PROTOCOL)
  114. def clear_cache(cache_path=None):
  115. if cache_path is None:
  116. cache_path = _default_cache_path
  117. shutil.rmtree(cache_path)
  118. parser_cache.clear()
  119. def _get_hashed_path(hashed_grammar, path, cache_path=None):
  120. directory = _get_cache_directory_path(cache_path=cache_path)
  121. file_hash = hashlib.sha256(path.encode("utf-8")).hexdigest()
  122. return os.path.join(directory, '%s-%s.pkl' % (hashed_grammar, file_hash))
  123. def _get_cache_directory_path(cache_path=None):
  124. if cache_path is None:
  125. cache_path = _default_cache_path
  126. directory = os.path.join(cache_path, _VERSION_TAG)
  127. if not os.path.exists(directory):
  128. os.makedirs(directory)
  129. return directory