project.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. import cPickle as pickle
  2. import os
  3. import shutil
  4. import sys
  5. import warnings
  6. import rope.base.fscommands
  7. from rope.base import exceptions, taskhandle, prefs, history, pycore, utils
  8. from rope.base.resourceobserver import *
  9. from rope.base.resources import File, Folder, _ResourceMatcher
  10. class _Project(object):
  11. def __init__(self, fscommands):
  12. self.observers = []
  13. self.fscommands = fscommands
  14. self.prefs = prefs.Prefs()
  15. self.data_files = _DataFiles(self)
  16. def get_resource(self, resource_name):
  17. """Get a resource in a project.
  18. `resource_name` is the path of a resource in a project. It is
  19. the path of a resource relative to project root. Project root
  20. folder address is an empty string. If the resource does not
  21. exist a `exceptions.ResourceNotFound` exception would be
  22. raised. Use `get_file()` and `get_folder()` when you need to
  23. get nonexistent `Resource`\s.
  24. """
  25. path = self._get_resource_path(resource_name)
  26. if not os.path.exists(path):
  27. raise exceptions.ResourceNotFoundError(
  28. 'Resource <%s> does not exist' % resource_name)
  29. elif os.path.isfile(path):
  30. return File(self, resource_name)
  31. elif os.path.isdir(path):
  32. return Folder(self, resource_name)
  33. else:
  34. raise exceptions.ResourceNotFoundError('Unknown resource '
  35. + resource_name)
  36. def validate(self, folder):
  37. """Validate files and folders contained in this folder
  38. It validates all of the files and folders contained in this
  39. folder if some observers are interested in them.
  40. """
  41. for observer in list(self.observers):
  42. observer.validate(folder)
  43. def add_observer(self, observer):
  44. """Register a `ResourceObserver`
  45. See `FilteredResourceObserver`.
  46. """
  47. self.observers.append(observer)
  48. def remove_observer(self, observer):
  49. """Remove a registered `ResourceObserver`"""
  50. if observer in self.observers:
  51. self.observers.remove(observer)
  52. def do(self, changes, task_handle=taskhandle.NullTaskHandle()):
  53. """Apply the changes in a `ChangeSet`
  54. Most of the time you call this function for committing the
  55. changes for a refactoring.
  56. """
  57. self.history.do(changes, task_handle=task_handle)
  58. def get_pycore(self):
  59. return self.pycore
  60. def get_file(self, path):
  61. """Get the file with `path` (it may not exist)"""
  62. return File(self, path)
  63. def get_folder(self, path):
  64. """Get the folder with `path` (it may not exist)"""
  65. return Folder(self, path)
  66. def is_ignored(self, resource):
  67. return False
  68. def get_prefs(self):
  69. return self.prefs
  70. def _get_resource_path(self, name):
  71. pass
  72. @property
  73. @utils.saveit
  74. def history(self):
  75. return history.History(self)
  76. @property
  77. @utils.saveit
  78. def pycore(self):
  79. return pycore.PyCore(self)
  80. def close(self):
  81. warnings.warn('Cannot close a NoProject',
  82. DeprecationWarning, stacklevel=2)
  83. ropefolder = None
  84. class Project(_Project):
  85. """A Project containing files and folders"""
  86. def __init__(self, projectroot, fscommands=None,
  87. ropefolder='.ropeproject', **prefs):
  88. """A rope project
  89. :parameters:
  90. - `projectroot`: The address of the root folder of the project
  91. - `fscommands`: Implements the file system operations used
  92. by rope; have a look at `rope.base.fscommands`
  93. - `ropefolder`: The name of the folder in which rope stores
  94. project configurations and data. Pass `None` for not using
  95. such a folder at all.
  96. - `prefs`: Specify project preferences. These values
  97. overwrite config file preferences.
  98. """
  99. if projectroot != '/':
  100. projectroot = _realpath(projectroot).rstrip('/\\')
  101. self._address = projectroot
  102. self._ropefolder_name = ropefolder
  103. if not os.path.exists(self._address):
  104. os.mkdir(self._address)
  105. elif not os.path.isdir(self._address):
  106. raise exceptions.RopeError('Project root exists and'
  107. ' is not a directory')
  108. if fscommands is None:
  109. fscommands = rope.base.fscommands.create_fscommands(self._address)
  110. super(Project, self).__init__(fscommands)
  111. self.ignored = _ResourceMatcher()
  112. self.file_list = _FileListCacher(self)
  113. self.prefs.add_callback('ignored_resources', self.ignored.set_patterns)
  114. if ropefolder is not None:
  115. self.prefs['ignored_resources'] = [ropefolder]
  116. self._init_prefs(prefs)
  117. def get_files(self):
  118. return self.file_list.get_files()
  119. def _get_resource_path(self, name):
  120. return os.path.join(self._address, *name.split('/'))
  121. def _init_ropefolder(self):
  122. if self.ropefolder is not None:
  123. if not self.ropefolder.exists():
  124. self._create_recursively(self.ropefolder)
  125. if not self.ropefolder.has_child('config.py'):
  126. config = self.ropefolder.create_file('config.py')
  127. config.write(self._default_config())
  128. def _create_recursively(self, folder):
  129. if folder.parent != self.root and not folder.parent.exists():
  130. self._create_recursively(folder.parent)
  131. folder.create()
  132. def _init_prefs(self, prefs):
  133. run_globals = {}
  134. if self.ropefolder is not None:
  135. config = self.get_file(self.ropefolder.path + '/config.py')
  136. run_globals.update({'__name__': '__main__',
  137. '__builtins__': __builtins__,
  138. '__file__': config.real_path})
  139. if config.exists():
  140. config = self.ropefolder.get_child('config.py')
  141. execfile(config.real_path, run_globals)
  142. else:
  143. exec(self._default_config(), run_globals)
  144. if 'set_prefs' in run_globals:
  145. run_globals['set_prefs'](self.prefs)
  146. for key, value in prefs.items():
  147. self.prefs[key] = value
  148. self._init_other_parts()
  149. self._init_ropefolder()
  150. if 'project_opened' in run_globals:
  151. run_globals['project_opened'](self)
  152. def _default_config(self):
  153. import rope.base.default_config
  154. import inspect
  155. return inspect.getsource(rope.base.default_config)
  156. def _init_other_parts(self):
  157. # Forcing the creation of `self.pycore` to register observers
  158. self.pycore
  159. def is_ignored(self, resource):
  160. return self.ignored.does_match(resource)
  161. def sync(self):
  162. """Closes project open resources"""
  163. self.close()
  164. def close(self):
  165. """Closes project open resources"""
  166. self.data_files.write()
  167. def set(self, key, value):
  168. """Set the `key` preference to `value`"""
  169. self.prefs.set(key, value)
  170. @property
  171. def ropefolder(self):
  172. if self._ropefolder_name is not None:
  173. return self.get_folder(self._ropefolder_name)
  174. def validate(self, folder=None):
  175. if folder is None:
  176. folder = self.root
  177. super(Project, self).validate(folder)
  178. root = property(lambda self: self.get_resource(''))
  179. address = property(lambda self: self._address)
  180. class NoProject(_Project):
  181. """A null object for holding out of project files.
  182. This class is singleton use `get_no_project` global function
  183. """
  184. def __init__(self):
  185. fscommands = rope.base.fscommands.FileSystemCommands()
  186. super(NoProject, self).__init__(fscommands)
  187. def _get_resource_path(self, name):
  188. real_name = name.replace('/', os.path.sep)
  189. return _realpath(real_name)
  190. def get_resource(self, name):
  191. universal_name = _realpath(name).replace(os.path.sep, '/')
  192. return super(NoProject, self).get_resource(universal_name)
  193. def get_files(self):
  194. return []
  195. _no_project = None
  196. def get_no_project():
  197. if NoProject._no_project is None:
  198. NoProject._no_project = NoProject()
  199. return NoProject._no_project
  200. class _FileListCacher(object):
  201. def __init__(self, project):
  202. self.project = project
  203. self.files = None
  204. rawobserver = ResourceObserver(
  205. self._changed, self._invalid, self._invalid,
  206. self._invalid, self._invalid)
  207. self.project.add_observer(rawobserver)
  208. def get_files(self):
  209. if self.files is None:
  210. self.files = set()
  211. self._add_files(self.project.root)
  212. return self.files
  213. def _add_files(self, folder):
  214. for child in folder.get_children():
  215. if child.is_folder():
  216. self._add_files(child)
  217. elif not self.project.is_ignored(child):
  218. self.files.add(child)
  219. def _changed(self, resource):
  220. if resource.is_folder():
  221. self.files = None
  222. def _invalid(self, resource, new_resource=None):
  223. self.files = None
  224. class _DataFiles(object):
  225. def __init__(self, project):
  226. self.project = project
  227. self.hooks = []
  228. def read_data(self, name, compress=False, import_=False):
  229. if self.project.ropefolder is None:
  230. return None
  231. compress = compress and self._can_compress()
  232. opener = self._get_opener(compress)
  233. file = self._get_file(name, compress)
  234. if not compress and import_:
  235. self._import_old_files(name)
  236. if file.exists():
  237. input = opener(file.real_path, 'rb')
  238. try:
  239. result = []
  240. try:
  241. while True:
  242. result.append(pickle.load(input))
  243. except EOFError:
  244. pass
  245. if len(result) == 1:
  246. return result[0]
  247. if len(result) > 1:
  248. return result
  249. finally:
  250. input.close()
  251. def write_data(self, name, data, compress=False):
  252. if self.project.ropefolder is not None:
  253. compress = compress and self._can_compress()
  254. file = self._get_file(name, compress)
  255. opener = self._get_opener(compress)
  256. output = opener(file.real_path, 'wb')
  257. try:
  258. pickle.dump(data, output, 2)
  259. finally:
  260. output.close()
  261. def add_write_hook(self, hook):
  262. self.hooks.append(hook)
  263. def write(self):
  264. for hook in self.hooks:
  265. hook()
  266. def _can_compress(self):
  267. try:
  268. import gzip
  269. return True
  270. except ImportError:
  271. return False
  272. def _import_old_files(self, name):
  273. old = self._get_file(name + '.pickle', False)
  274. new = self._get_file(name, False)
  275. if old.exists() and not new.exists():
  276. shutil.move(old.real_path, new.real_path)
  277. def _get_opener(self, compress):
  278. if compress:
  279. try:
  280. import gzip
  281. return gzip.open
  282. except ImportError:
  283. pass
  284. return open
  285. def _get_file(self, name, compress):
  286. path = self.project.ropefolder.path + '/' + name
  287. if compress:
  288. path += '.gz'
  289. return self.project.get_file(path)
  290. def _realpath(path):
  291. """Return the real path of `path`
  292. Is equivalent to ``realpath(abspath(expanduser(path)))``.
  293. """
  294. path = path or ''
  295. # there is a bug in cygwin for os.path.abspath() for abs paths
  296. if sys.platform == 'cygwin':
  297. if path[1:3] == ':\\':
  298. return path
  299. return os.path.abspath(os.path.expanduser(path))
  300. return os.path.realpath(os.path.abspath(os.path.expanduser(path)))