##// END OF EJS Templates
lazy load pygments lib so we can use VCS without it installed
marcink -
r3876:c5dd289c beta
parent child Browse files
Show More
@@ -1,618 +1,616 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 vcs.nodes
3 vcs.nodes
4 ~~~~~~~~~
4 ~~~~~~~~~
5
5
6 Module holding everything related to vcs nodes.
6 Module holding everything related to vcs nodes.
7
7
8 :created_on: Apr 8, 2010
8 :created_on: Apr 8, 2010
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 """
10 """
11 import os
11 import os
12 import stat
12 import stat
13 import posixpath
13 import posixpath
14 import mimetypes
14 import mimetypes
15
15
16 from pygments import lexers
17
18 from rhodecode.lib.vcs.backends.base import EmptyChangeset
16 from rhodecode.lib.vcs.backends.base import EmptyChangeset
19 from rhodecode.lib.vcs.exceptions import NodeError, RemovedFileNodeError
17 from rhodecode.lib.vcs.exceptions import NodeError, RemovedFileNodeError
20 from rhodecode.lib.vcs.utils.lazy import LazyProperty
18 from rhodecode.lib.vcs.utils.lazy import LazyProperty
21 from rhodecode.lib.vcs.utils import safe_unicode
19 from rhodecode.lib.vcs.utils import safe_unicode
22
20
23
21
24 class NodeKind:
22 class NodeKind:
25 SUBMODULE = -1
23 SUBMODULE = -1
26 DIR = 1
24 DIR = 1
27 FILE = 2
25 FILE = 2
28
26
29
27
30 class NodeState:
28 class NodeState:
31 ADDED = u'added'
29 ADDED = u'added'
32 CHANGED = u'changed'
30 CHANGED = u'changed'
33 NOT_CHANGED = u'not changed'
31 NOT_CHANGED = u'not changed'
34 REMOVED = u'removed'
32 REMOVED = u'removed'
35
33
36
34
37 class NodeGeneratorBase(object):
35 class NodeGeneratorBase(object):
38 """
36 """
39 Base class for removed added and changed filenodes, it's a lazy generator
37 Base class for removed added and changed filenodes, it's a lazy generator
40 class that will create filenodes only on iteration or call
38 class that will create filenodes only on iteration or call
41
39
42 The len method doesn't need to create filenodes at all
40 The len method doesn't need to create filenodes at all
43 """
41 """
44
42
45 def __init__(self, current_paths, cs):
43 def __init__(self, current_paths, cs):
46 self.cs = cs
44 self.cs = cs
47 self.current_paths = current_paths
45 self.current_paths = current_paths
48
46
49 def __call__(self):
47 def __call__(self):
50 return [n for n in self]
48 return [n for n in self]
51
49
52 def __getslice__(self, i, j):
50 def __getslice__(self, i, j):
53 for p in self.current_paths[i:j]:
51 for p in self.current_paths[i:j]:
54 yield self.cs.get_node(p)
52 yield self.cs.get_node(p)
55
53
56 def __len__(self):
54 def __len__(self):
57 return len(self.current_paths)
55 return len(self.current_paths)
58
56
59 def __iter__(self):
57 def __iter__(self):
60 for p in self.current_paths:
58 for p in self.current_paths:
61 yield self.cs.get_node(p)
59 yield self.cs.get_node(p)
62
60
63
61
64 class AddedFileNodesGenerator(NodeGeneratorBase):
62 class AddedFileNodesGenerator(NodeGeneratorBase):
65 """
63 """
66 Class holding Added files for current changeset
64 Class holding Added files for current changeset
67 """
65 """
68 pass
66 pass
69
67
70
68
71 class ChangedFileNodesGenerator(NodeGeneratorBase):
69 class ChangedFileNodesGenerator(NodeGeneratorBase):
72 """
70 """
73 Class holding Changed files for current changeset
71 Class holding Changed files for current changeset
74 """
72 """
75 pass
73 pass
76
74
77
75
78 class RemovedFileNodesGenerator(NodeGeneratorBase):
76 class RemovedFileNodesGenerator(NodeGeneratorBase):
79 """
77 """
80 Class holding removed files for current changeset
78 Class holding removed files for current changeset
81 """
79 """
82 def __iter__(self):
80 def __iter__(self):
83 for p in self.current_paths:
81 for p in self.current_paths:
84 yield RemovedFileNode(path=p)
82 yield RemovedFileNode(path=p)
85
83
86 def __getslice__(self, i, j):
84 def __getslice__(self, i, j):
87 for p in self.current_paths[i:j]:
85 for p in self.current_paths[i:j]:
88 yield RemovedFileNode(path=p)
86 yield RemovedFileNode(path=p)
89
87
90
88
91 class Node(object):
89 class Node(object):
92 """
90 """
93 Simplest class representing file or directory on repository. SCM backends
91 Simplest class representing file or directory on repository. SCM backends
94 should use ``FileNode`` and ``DirNode`` subclasses rather than ``Node``
92 should use ``FileNode`` and ``DirNode`` subclasses rather than ``Node``
95 directly.
93 directly.
96
94
97 Node's ``path`` cannot start with slash as we operate on *relative* paths
95 Node's ``path`` cannot start with slash as we operate on *relative* paths
98 only. Moreover, every single node is identified by the ``path`` attribute,
96 only. Moreover, every single node is identified by the ``path`` attribute,
99 so it cannot end with slash, too. Otherwise, path could lead to mistakes.
97 so it cannot end with slash, too. Otherwise, path could lead to mistakes.
100 """
98 """
101
99
102 def __init__(self, path, kind):
100 def __init__(self, path, kind):
103 if path.startswith('/'):
101 if path.startswith('/'):
104 raise NodeError("Cannot initialize Node objects with slash at "
102 raise NodeError("Cannot initialize Node objects with slash at "
105 "the beginning as only relative paths are supported")
103 "the beginning as only relative paths are supported")
106 self.path = path.rstrip('/')
104 self.path = path.rstrip('/')
107 if path == '' and kind != NodeKind.DIR:
105 if path == '' and kind != NodeKind.DIR:
108 raise NodeError("Only DirNode and its subclasses may be "
106 raise NodeError("Only DirNode and its subclasses may be "
109 "initialized with empty path")
107 "initialized with empty path")
110 self.kind = kind
108 self.kind = kind
111 #self.dirs, self.files = [], []
109 #self.dirs, self.files = [], []
112 if self.is_root() and not self.is_dir():
110 if self.is_root() and not self.is_dir():
113 raise NodeError("Root node cannot be FILE kind")
111 raise NodeError("Root node cannot be FILE kind")
114
112
115 @LazyProperty
113 @LazyProperty
116 def parent(self):
114 def parent(self):
117 parent_path = self.get_parent_path()
115 parent_path = self.get_parent_path()
118 if parent_path:
116 if parent_path:
119 if self.changeset:
117 if self.changeset:
120 return self.changeset.get_node(parent_path)
118 return self.changeset.get_node(parent_path)
121 return DirNode(parent_path)
119 return DirNode(parent_path)
122 return None
120 return None
123
121
124 @LazyProperty
122 @LazyProperty
125 def unicode_path(self):
123 def unicode_path(self):
126 return safe_unicode(self.path)
124 return safe_unicode(self.path)
127
125
128 @LazyProperty
126 @LazyProperty
129 def name(self):
127 def name(self):
130 """
128 """
131 Returns name of the node so if its path
129 Returns name of the node so if its path
132 then only last part is returned.
130 then only last part is returned.
133 """
131 """
134 return safe_unicode(self.path.rstrip('/').split('/')[-1])
132 return safe_unicode(self.path.rstrip('/').split('/')[-1])
135
133
136 def _get_kind(self):
134 def _get_kind(self):
137 return self._kind
135 return self._kind
138
136
139 def _set_kind(self, kind):
137 def _set_kind(self, kind):
140 if hasattr(self, '_kind'):
138 if hasattr(self, '_kind'):
141 raise NodeError("Cannot change node's kind")
139 raise NodeError("Cannot change node's kind")
142 else:
140 else:
143 self._kind = kind
141 self._kind = kind
144 # Post setter check (path's trailing slash)
142 # Post setter check (path's trailing slash)
145 if self.path.endswith('/'):
143 if self.path.endswith('/'):
146 raise NodeError("Node's path cannot end with slash")
144 raise NodeError("Node's path cannot end with slash")
147
145
148 kind = property(_get_kind, _set_kind)
146 kind = property(_get_kind, _set_kind)
149
147
150 def __cmp__(self, other):
148 def __cmp__(self, other):
151 """
149 """
152 Comparator using name of the node, needed for quick list sorting.
150 Comparator using name of the node, needed for quick list sorting.
153 """
151 """
154 kind_cmp = cmp(self.kind, other.kind)
152 kind_cmp = cmp(self.kind, other.kind)
155 if kind_cmp:
153 if kind_cmp:
156 return kind_cmp
154 return kind_cmp
157 return cmp(self.name, other.name)
155 return cmp(self.name, other.name)
158
156
159 def __eq__(self, other):
157 def __eq__(self, other):
160 for attr in ['name', 'path', 'kind']:
158 for attr in ['name', 'path', 'kind']:
161 if getattr(self, attr) != getattr(other, attr):
159 if getattr(self, attr) != getattr(other, attr):
162 return False
160 return False
163 if self.is_file():
161 if self.is_file():
164 if self.content != other.content:
162 if self.content != other.content:
165 return False
163 return False
166 else:
164 else:
167 # For DirNode's check without entering each dir
165 # For DirNode's check without entering each dir
168 self_nodes_paths = list(sorted(n.path for n in self.nodes))
166 self_nodes_paths = list(sorted(n.path for n in self.nodes))
169 other_nodes_paths = list(sorted(n.path for n in self.nodes))
167 other_nodes_paths = list(sorted(n.path for n in self.nodes))
170 if self_nodes_paths != other_nodes_paths:
168 if self_nodes_paths != other_nodes_paths:
171 return False
169 return False
172 return True
170 return True
173
171
174 def __nq__(self, other):
172 def __nq__(self, other):
175 return not self.__eq__(other)
173 return not self.__eq__(other)
176
174
177 def __repr__(self):
175 def __repr__(self):
178 return '<%s %r>' % (self.__class__.__name__, self.path)
176 return '<%s %r>' % (self.__class__.__name__, self.path)
179
177
180 def __str__(self):
178 def __str__(self):
181 return self.__repr__()
179 return self.__repr__()
182
180
183 def __unicode__(self):
181 def __unicode__(self):
184 return self.name
182 return self.name
185
183
186 def get_parent_path(self):
184 def get_parent_path(self):
187 """
185 """
188 Returns node's parent path or empty string if node is root.
186 Returns node's parent path or empty string if node is root.
189 """
187 """
190 if self.is_root():
188 if self.is_root():
191 return ''
189 return ''
192 return posixpath.dirname(self.path.rstrip('/')) + '/'
190 return posixpath.dirname(self.path.rstrip('/')) + '/'
193
191
194 def is_file(self):
192 def is_file(self):
195 """
193 """
196 Returns ``True`` if node's kind is ``NodeKind.FILE``, ``False``
194 Returns ``True`` if node's kind is ``NodeKind.FILE``, ``False``
197 otherwise.
195 otherwise.
198 """
196 """
199 return self.kind == NodeKind.FILE
197 return self.kind == NodeKind.FILE
200
198
201 def is_dir(self):
199 def is_dir(self):
202 """
200 """
203 Returns ``True`` if node's kind is ``NodeKind.DIR``, ``False``
201 Returns ``True`` if node's kind is ``NodeKind.DIR``, ``False``
204 otherwise.
202 otherwise.
205 """
203 """
206 return self.kind == NodeKind.DIR
204 return self.kind == NodeKind.DIR
207
205
208 def is_root(self):
206 def is_root(self):
209 """
207 """
210 Returns ``True`` if node is a root node and ``False`` otherwise.
208 Returns ``True`` if node is a root node and ``False`` otherwise.
211 """
209 """
212 return self.kind == NodeKind.DIR and self.path == ''
210 return self.kind == NodeKind.DIR and self.path == ''
213
211
214 def is_submodule(self):
212 def is_submodule(self):
215 """
213 """
216 Returns ``True`` if node's kind is ``NodeKind.SUBMODULE``, ``False``
214 Returns ``True`` if node's kind is ``NodeKind.SUBMODULE``, ``False``
217 otherwise.
215 otherwise.
218 """
216 """
219 return self.kind == NodeKind.SUBMODULE
217 return self.kind == NodeKind.SUBMODULE
220
218
221 @LazyProperty
219 @LazyProperty
222 def added(self):
220 def added(self):
223 return self.state is NodeState.ADDED
221 return self.state is NodeState.ADDED
224
222
225 @LazyProperty
223 @LazyProperty
226 def changed(self):
224 def changed(self):
227 return self.state is NodeState.CHANGED
225 return self.state is NodeState.CHANGED
228
226
229 @LazyProperty
227 @LazyProperty
230 def not_changed(self):
228 def not_changed(self):
231 return self.state is NodeState.NOT_CHANGED
229 return self.state is NodeState.NOT_CHANGED
232
230
233 @LazyProperty
231 @LazyProperty
234 def removed(self):
232 def removed(self):
235 return self.state is NodeState.REMOVED
233 return self.state is NodeState.REMOVED
236
234
237
235
238 class FileNode(Node):
236 class FileNode(Node):
239 """
237 """
240 Class representing file nodes.
238 Class representing file nodes.
241
239
242 :attribute: path: path to the node, relative to repostiory's root
240 :attribute: path: path to the node, relative to repostiory's root
243 :attribute: content: if given arbitrary sets content of the file
241 :attribute: content: if given arbitrary sets content of the file
244 :attribute: changeset: if given, first time content is accessed, callback
242 :attribute: changeset: if given, first time content is accessed, callback
245 :attribute: mode: octal stat mode for a node. Default is 0100644.
243 :attribute: mode: octal stat mode for a node. Default is 0100644.
246 """
244 """
247
245
248 def __init__(self, path, content=None, changeset=None, mode=None):
246 def __init__(self, path, content=None, changeset=None, mode=None):
249 """
247 """
250 Only one of ``content`` and ``changeset`` may be given. Passing both
248 Only one of ``content`` and ``changeset`` may be given. Passing both
251 would raise ``NodeError`` exception.
249 would raise ``NodeError`` exception.
252
250
253 :param path: relative path to the node
251 :param path: relative path to the node
254 :param content: content may be passed to constructor
252 :param content: content may be passed to constructor
255 :param changeset: if given, will use it to lazily fetch content
253 :param changeset: if given, will use it to lazily fetch content
256 :param mode: octal representation of ST_MODE (i.e. 0100644)
254 :param mode: octal representation of ST_MODE (i.e. 0100644)
257 """
255 """
258
256
259 if content and changeset:
257 if content and changeset:
260 raise NodeError("Cannot use both content and changeset")
258 raise NodeError("Cannot use both content and changeset")
261 super(FileNode, self).__init__(path, kind=NodeKind.FILE)
259 super(FileNode, self).__init__(path, kind=NodeKind.FILE)
262 self.changeset = changeset
260 self.changeset = changeset
263 self._content = content
261 self._content = content
264 self._mode = mode or 0100644
262 self._mode = mode or 0100644
265
263
266 @LazyProperty
264 @LazyProperty
267 def mode(self):
265 def mode(self):
268 """
266 """
269 Returns lazily mode of the FileNode. If ``changeset`` is not set, would
267 Returns lazily mode of the FileNode. If ``changeset`` is not set, would
270 use value given at initialization or 0100644 (default).
268 use value given at initialization or 0100644 (default).
271 """
269 """
272 if self.changeset:
270 if self.changeset:
273 mode = self.changeset.get_file_mode(self.path)
271 mode = self.changeset.get_file_mode(self.path)
274 else:
272 else:
275 mode = self._mode
273 mode = self._mode
276 return mode
274 return mode
277
275
278 def _get_content(self):
276 def _get_content(self):
279 if self.changeset:
277 if self.changeset:
280 content = self.changeset.get_file_content(self.path)
278 content = self.changeset.get_file_content(self.path)
281 else:
279 else:
282 content = self._content
280 content = self._content
283 return content
281 return content
284
282
285 @property
283 @property
286 def content(self):
284 def content(self):
287 """
285 """
288 Returns lazily content of the FileNode. If possible, would try to
286 Returns lazily content of the FileNode. If possible, would try to
289 decode content from UTF-8.
287 decode content from UTF-8.
290 """
288 """
291 content = self._get_content()
289 content = self._get_content()
292
290
293 if bool(content and '\0' in content):
291 if bool(content and '\0' in content):
294 return content
292 return content
295 return safe_unicode(content)
293 return safe_unicode(content)
296
294
297 @LazyProperty
295 @LazyProperty
298 def size(self):
296 def size(self):
299 if self.changeset:
297 if self.changeset:
300 return self.changeset.get_file_size(self.path)
298 return self.changeset.get_file_size(self.path)
301 raise NodeError("Cannot retrieve size of the file without related "
299 raise NodeError("Cannot retrieve size of the file without related "
302 "changeset attribute")
300 "changeset attribute")
303
301
304 @LazyProperty
302 @LazyProperty
305 def message(self):
303 def message(self):
306 if self.changeset:
304 if self.changeset:
307 return self.last_changeset.message
305 return self.last_changeset.message
308 raise NodeError("Cannot retrieve message of the file without related "
306 raise NodeError("Cannot retrieve message of the file without related "
309 "changeset attribute")
307 "changeset attribute")
310
308
311 @LazyProperty
309 @LazyProperty
312 def last_changeset(self):
310 def last_changeset(self):
313 if self.changeset:
311 if self.changeset:
314 return self.changeset.get_file_changeset(self.path)
312 return self.changeset.get_file_changeset(self.path)
315 raise NodeError("Cannot retrieve last changeset of the file without "
313 raise NodeError("Cannot retrieve last changeset of the file without "
316 "related changeset attribute")
314 "related changeset attribute")
317
315
318 def get_mimetype(self):
316 def get_mimetype(self):
319 """
317 """
320 Mimetype is calculated based on the file's content. If ``_mimetype``
318 Mimetype is calculated based on the file's content. If ``_mimetype``
321 attribute is available, it will be returned (backends which store
319 attribute is available, it will be returned (backends which store
322 mimetypes or can easily recognize them, should set this private
320 mimetypes or can easily recognize them, should set this private
323 attribute to indicate that type should *NOT* be calculated).
321 attribute to indicate that type should *NOT* be calculated).
324 """
322 """
325 if hasattr(self, '_mimetype'):
323 if hasattr(self, '_mimetype'):
326 if (isinstance(self._mimetype, (tuple, list,)) and
324 if (isinstance(self._mimetype, (tuple, list,)) and
327 len(self._mimetype) == 2):
325 len(self._mimetype) == 2):
328 return self._mimetype
326 return self._mimetype
329 else:
327 else:
330 raise NodeError('given _mimetype attribute must be an 2 '
328 raise NodeError('given _mimetype attribute must be an 2 '
331 'element list or tuple')
329 'element list or tuple')
332
330
333 mtype, encoding = mimetypes.guess_type(self.name)
331 mtype, encoding = mimetypes.guess_type(self.name)
334
332
335 if mtype is None:
333 if mtype is None:
336 if self.is_binary:
334 if self.is_binary:
337 mtype = 'application/octet-stream'
335 mtype = 'application/octet-stream'
338 encoding = None
336 encoding = None
339 else:
337 else:
340 mtype = 'text/plain'
338 mtype = 'text/plain'
341 encoding = None
339 encoding = None
342 return mtype, encoding
340 return mtype, encoding
343
341
344 @LazyProperty
342 @LazyProperty
345 def mimetype(self):
343 def mimetype(self):
346 """
344 """
347 Wrapper around full mimetype info. It returns only type of fetched
345 Wrapper around full mimetype info. It returns only type of fetched
348 mimetype without the encoding part. use get_mimetype function to fetch
346 mimetype without the encoding part. use get_mimetype function to fetch
349 full set of (type,encoding)
347 full set of (type,encoding)
350 """
348 """
351 return self.get_mimetype()[0]
349 return self.get_mimetype()[0]
352
350
353 @LazyProperty
351 @LazyProperty
354 def mimetype_main(self):
352 def mimetype_main(self):
355 return self.mimetype.split('/')[0]
353 return self.mimetype.split('/')[0]
356
354
357 @LazyProperty
355 @LazyProperty
358 def lexer(self):
356 def lexer(self):
359 """
357 """
360 Returns pygment's lexer class. Would try to guess lexer taking file's
358 Returns pygment's lexer class. Would try to guess lexer taking file's
361 content, name and mimetype.
359 content, name and mimetype.
362 """
360 """
363
361 from pygments import lexers
364 try:
362 try:
365 lexer = lexers.guess_lexer_for_filename(self.name, self.content, stripnl=False)
363 lexer = lexers.guess_lexer_for_filename(self.name, self.content, stripnl=False)
366 except lexers.ClassNotFound:
364 except lexers.ClassNotFound:
367 lexer = lexers.TextLexer(stripnl=False)
365 lexer = lexers.TextLexer(stripnl=False)
368 # returns first alias
366 # returns first alias
369 return lexer
367 return lexer
370
368
371 @LazyProperty
369 @LazyProperty
372 def lexer_alias(self):
370 def lexer_alias(self):
373 """
371 """
374 Returns first alias of the lexer guessed for this file.
372 Returns first alias of the lexer guessed for this file.
375 """
373 """
376 return self.lexer.aliases[0]
374 return self.lexer.aliases[0]
377
375
378 @LazyProperty
376 @LazyProperty
379 def history(self):
377 def history(self):
380 """
378 """
381 Returns a list of changeset for this file in which the file was changed
379 Returns a list of changeset for this file in which the file was changed
382 """
380 """
383 if self.changeset is None:
381 if self.changeset is None:
384 raise NodeError('Unable to get changeset for this FileNode')
382 raise NodeError('Unable to get changeset for this FileNode')
385 return self.changeset.get_file_history(self.path)
383 return self.changeset.get_file_history(self.path)
386
384
387 @LazyProperty
385 @LazyProperty
388 def annotate(self):
386 def annotate(self):
389 """
387 """
390 Returns a list of three element tuples with lineno,changeset and line
388 Returns a list of three element tuples with lineno,changeset and line
391 """
389 """
392 if self.changeset is None:
390 if self.changeset is None:
393 raise NodeError('Unable to get changeset for this FileNode')
391 raise NodeError('Unable to get changeset for this FileNode')
394 return self.changeset.get_file_annotate(self.path)
392 return self.changeset.get_file_annotate(self.path)
395
393
396 @LazyProperty
394 @LazyProperty
397 def state(self):
395 def state(self):
398 if not self.changeset:
396 if not self.changeset:
399 raise NodeError("Cannot check state of the node if it's not "
397 raise NodeError("Cannot check state of the node if it's not "
400 "linked with changeset")
398 "linked with changeset")
401 elif self.path in (node.path for node in self.changeset.added):
399 elif self.path in (node.path for node in self.changeset.added):
402 return NodeState.ADDED
400 return NodeState.ADDED
403 elif self.path in (node.path for node in self.changeset.changed):
401 elif self.path in (node.path for node in self.changeset.changed):
404 return NodeState.CHANGED
402 return NodeState.CHANGED
405 else:
403 else:
406 return NodeState.NOT_CHANGED
404 return NodeState.NOT_CHANGED
407
405
408 @property
406 @property
409 def is_binary(self):
407 def is_binary(self):
410 """
408 """
411 Returns True if file has binary content.
409 Returns True if file has binary content.
412 """
410 """
413 _bin = '\0' in self._get_content()
411 _bin = '\0' in self._get_content()
414 return _bin
412 return _bin
415
413
416 @LazyProperty
414 @LazyProperty
417 def extension(self):
415 def extension(self):
418 """Returns filenode extension"""
416 """Returns filenode extension"""
419 return self.name.split('.')[-1]
417 return self.name.split('.')[-1]
420
418
421 @property
419 @property
422 def is_executable(self):
420 def is_executable(self):
423 """
421 """
424 Returns ``True`` if file has executable flag turned on.
422 Returns ``True`` if file has executable flag turned on.
425 """
423 """
426 return bool(self.mode & stat.S_IXUSR)
424 return bool(self.mode & stat.S_IXUSR)
427
425
428 def __repr__(self):
426 def __repr__(self):
429 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
427 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
430 getattr(self.changeset, 'short_id', ''))
428 getattr(self.changeset, 'short_id', ''))
431
429
432
430
433 class RemovedFileNode(FileNode):
431 class RemovedFileNode(FileNode):
434 """
432 """
435 Dummy FileNode class - trying to access any public attribute except path,
433 Dummy FileNode class - trying to access any public attribute except path,
436 name, kind or state (or methods/attributes checking those two) would raise
434 name, kind or state (or methods/attributes checking those two) would raise
437 RemovedFileNodeError.
435 RemovedFileNodeError.
438 """
436 """
439 ALLOWED_ATTRIBUTES = [
437 ALLOWED_ATTRIBUTES = [
440 'name', 'path', 'state', 'is_root', 'is_file', 'is_dir', 'kind',
438 'name', 'path', 'state', 'is_root', 'is_file', 'is_dir', 'kind',
441 'added', 'changed', 'not_changed', 'removed'
439 'added', 'changed', 'not_changed', 'removed'
442 ]
440 ]
443
441
444 def __init__(self, path):
442 def __init__(self, path):
445 """
443 """
446 :param path: relative path to the node
444 :param path: relative path to the node
447 """
445 """
448 super(RemovedFileNode, self).__init__(path=path)
446 super(RemovedFileNode, self).__init__(path=path)
449
447
450 def __getattribute__(self, attr):
448 def __getattribute__(self, attr):
451 if attr.startswith('_') or attr in RemovedFileNode.ALLOWED_ATTRIBUTES:
449 if attr.startswith('_') or attr in RemovedFileNode.ALLOWED_ATTRIBUTES:
452 return super(RemovedFileNode, self).__getattribute__(attr)
450 return super(RemovedFileNode, self).__getattribute__(attr)
453 raise RemovedFileNodeError("Cannot access attribute %s on "
451 raise RemovedFileNodeError("Cannot access attribute %s on "
454 "RemovedFileNode" % attr)
452 "RemovedFileNode" % attr)
455
453
456 @LazyProperty
454 @LazyProperty
457 def state(self):
455 def state(self):
458 return NodeState.REMOVED
456 return NodeState.REMOVED
459
457
460
458
461 class DirNode(Node):
459 class DirNode(Node):
462 """
460 """
463 DirNode stores list of files and directories within this node.
461 DirNode stores list of files and directories within this node.
464 Nodes may be used standalone but within repository context they
462 Nodes may be used standalone but within repository context they
465 lazily fetch data within same repositorty's changeset.
463 lazily fetch data within same repositorty's changeset.
466 """
464 """
467
465
468 def __init__(self, path, nodes=(), changeset=None):
466 def __init__(self, path, nodes=(), changeset=None):
469 """
467 """
470 Only one of ``nodes`` and ``changeset`` may be given. Passing both
468 Only one of ``nodes`` and ``changeset`` may be given. Passing both
471 would raise ``NodeError`` exception.
469 would raise ``NodeError`` exception.
472
470
473 :param path: relative path to the node
471 :param path: relative path to the node
474 :param nodes: content may be passed to constructor
472 :param nodes: content may be passed to constructor
475 :param changeset: if given, will use it to lazily fetch content
473 :param changeset: if given, will use it to lazily fetch content
476 :param size: always 0 for ``DirNode``
474 :param size: always 0 for ``DirNode``
477 """
475 """
478 if nodes and changeset:
476 if nodes and changeset:
479 raise NodeError("Cannot use both nodes and changeset")
477 raise NodeError("Cannot use both nodes and changeset")
480 super(DirNode, self).__init__(path, NodeKind.DIR)
478 super(DirNode, self).__init__(path, NodeKind.DIR)
481 self.changeset = changeset
479 self.changeset = changeset
482 self._nodes = nodes
480 self._nodes = nodes
483
481
484 @LazyProperty
482 @LazyProperty
485 def content(self):
483 def content(self):
486 raise NodeError("%s represents a dir and has no ``content`` attribute"
484 raise NodeError("%s represents a dir and has no ``content`` attribute"
487 % self)
485 % self)
488
486
489 @LazyProperty
487 @LazyProperty
490 def nodes(self):
488 def nodes(self):
491 if self.changeset:
489 if self.changeset:
492 nodes = self.changeset.get_nodes(self.path)
490 nodes = self.changeset.get_nodes(self.path)
493 else:
491 else:
494 nodes = self._nodes
492 nodes = self._nodes
495 self._nodes_dict = dict((node.path, node) for node in nodes)
493 self._nodes_dict = dict((node.path, node) for node in nodes)
496 return sorted(nodes)
494 return sorted(nodes)
497
495
498 @LazyProperty
496 @LazyProperty
499 def files(self):
497 def files(self):
500 return sorted((node for node in self.nodes if node.is_file()))
498 return sorted((node for node in self.nodes if node.is_file()))
501
499
502 @LazyProperty
500 @LazyProperty
503 def dirs(self):
501 def dirs(self):
504 return sorted((node for node in self.nodes if node.is_dir()))
502 return sorted((node for node in self.nodes if node.is_dir()))
505
503
506 def __iter__(self):
504 def __iter__(self):
507 for node in self.nodes:
505 for node in self.nodes:
508 yield node
506 yield node
509
507
510 def get_node(self, path):
508 def get_node(self, path):
511 """
509 """
512 Returns node from within this particular ``DirNode``, so it is now
510 Returns node from within this particular ``DirNode``, so it is now
513 allowed to fetch, i.e. node located at 'docs/api/index.rst' from node
511 allowed to fetch, i.e. node located at 'docs/api/index.rst' from node
514 'docs'. In order to access deeper nodes one must fetch nodes between
512 'docs'. In order to access deeper nodes one must fetch nodes between
515 them first - this would work::
513 them first - this would work::
516
514
517 docs = root.get_node('docs')
515 docs = root.get_node('docs')
518 docs.get_node('api').get_node('index.rst')
516 docs.get_node('api').get_node('index.rst')
519
517
520 :param: path - relative to the current node
518 :param: path - relative to the current node
521
519
522 .. note::
520 .. note::
523 To access lazily (as in example above) node have to be initialized
521 To access lazily (as in example above) node have to be initialized
524 with related changeset object - without it node is out of
522 with related changeset object - without it node is out of
525 context and may know nothing about anything else than nearest
523 context and may know nothing about anything else than nearest
526 (located at same level) nodes.
524 (located at same level) nodes.
527 """
525 """
528 try:
526 try:
529 path = path.rstrip('/')
527 path = path.rstrip('/')
530 if path == '':
528 if path == '':
531 raise NodeError("Cannot retrieve node without path")
529 raise NodeError("Cannot retrieve node without path")
532 self.nodes # access nodes first in order to set _nodes_dict
530 self.nodes # access nodes first in order to set _nodes_dict
533 paths = path.split('/')
531 paths = path.split('/')
534 if len(paths) == 1:
532 if len(paths) == 1:
535 if not self.is_root():
533 if not self.is_root():
536 path = '/'.join((self.path, paths[0]))
534 path = '/'.join((self.path, paths[0]))
537 else:
535 else:
538 path = paths[0]
536 path = paths[0]
539 return self._nodes_dict[path]
537 return self._nodes_dict[path]
540 elif len(paths) > 1:
538 elif len(paths) > 1:
541 if self.changeset is None:
539 if self.changeset is None:
542 raise NodeError("Cannot access deeper "
540 raise NodeError("Cannot access deeper "
543 "nodes without changeset")
541 "nodes without changeset")
544 else:
542 else:
545 path1, path2 = paths[0], '/'.join(paths[1:])
543 path1, path2 = paths[0], '/'.join(paths[1:])
546 return self.get_node(path1).get_node(path2)
544 return self.get_node(path1).get_node(path2)
547 else:
545 else:
548 raise KeyError
546 raise KeyError
549 except KeyError:
547 except KeyError:
550 raise NodeError("Node does not exist at %s" % path)
548 raise NodeError("Node does not exist at %s" % path)
551
549
552 @LazyProperty
550 @LazyProperty
553 def state(self):
551 def state(self):
554 raise NodeError("Cannot access state of DirNode")
552 raise NodeError("Cannot access state of DirNode")
555
553
556 @LazyProperty
554 @LazyProperty
557 def size(self):
555 def size(self):
558 size = 0
556 size = 0
559 for root, dirs, files in self.changeset.walk(self.path):
557 for root, dirs, files in self.changeset.walk(self.path):
560 for f in files:
558 for f in files:
561 size += f.size
559 size += f.size
562
560
563 return size
561 return size
564
562
565 def __repr__(self):
563 def __repr__(self):
566 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
564 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
567 getattr(self.changeset, 'short_id', ''))
565 getattr(self.changeset, 'short_id', ''))
568
566
569
567
570 class RootNode(DirNode):
568 class RootNode(DirNode):
571 """
569 """
572 DirNode being the root node of the repository.
570 DirNode being the root node of the repository.
573 """
571 """
574
572
575 def __init__(self, nodes=(), changeset=None):
573 def __init__(self, nodes=(), changeset=None):
576 super(RootNode, self).__init__(path='', nodes=nodes,
574 super(RootNode, self).__init__(path='', nodes=nodes,
577 changeset=changeset)
575 changeset=changeset)
578
576
579 def __repr__(self):
577 def __repr__(self):
580 return '<%s>' % self.__class__.__name__
578 return '<%s>' % self.__class__.__name__
581
579
582
580
583 class SubModuleNode(Node):
581 class SubModuleNode(Node):
584 """
582 """
585 represents a SubModule of Git or SubRepo of Mercurial
583 represents a SubModule of Git or SubRepo of Mercurial
586 """
584 """
587 is_binary = False
585 is_binary = False
588 size = 0
586 size = 0
589
587
590 def __init__(self, name, url=None, changeset=None, alias=None):
588 def __init__(self, name, url=None, changeset=None, alias=None):
591 self.path = name
589 self.path = name
592 self.kind = NodeKind.SUBMODULE
590 self.kind = NodeKind.SUBMODULE
593 self.alias = alias
591 self.alias = alias
594 # we have to use emptyChangeset here since this can point to svn/git/hg
592 # we have to use emptyChangeset here since this can point to svn/git/hg
595 # submodules we cannot get from repository
593 # submodules we cannot get from repository
596 self.changeset = EmptyChangeset(str(changeset), alias=alias)
594 self.changeset = EmptyChangeset(str(changeset), alias=alias)
597 self.url = url or self._extract_submodule_url()
595 self.url = url or self._extract_submodule_url()
598
596
599 def __repr__(self):
597 def __repr__(self):
600 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
598 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
601 getattr(self.changeset, 'short_id', ''))
599 getattr(self.changeset, 'short_id', ''))
602
600
603 def _extract_submodule_url(self):
601 def _extract_submodule_url(self):
604 if self.alias == 'git':
602 if self.alias == 'git':
605 #TODO: find a way to parse gits submodule file and extract the
603 #TODO: find a way to parse gits submodule file and extract the
606 # linking URL
604 # linking URL
607 return self.path
605 return self.path
608 if self.alias == 'hg':
606 if self.alias == 'hg':
609 return self.path
607 return self.path
610
608
611 @LazyProperty
609 @LazyProperty
612 def name(self):
610 def name(self):
613 """
611 """
614 Returns name of the node so if its path
612 Returns name of the node so if its path
615 then only last part is returned.
613 then only last part is returned.
616 """
614 """
617 org = safe_unicode(self.path.rstrip('/').split('/')[-1])
615 org = safe_unicode(self.path.rstrip('/').split('/')[-1])
618 return u'%s @ %s' % (org, self.changeset.short_id)
616 return u'%s @ %s' % (org, self.changeset.short_id)
General Comments 0
You need to be logged in to leave comments. Login now