-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
Copy pathbase.py
186 lines (145 loc) · 5.98 KB
/
base.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import functools
import re
from coalib.bearlib.languages import Language
import coalib.bearlib.aspects
from .taste import TasteError
def get_subaspect(parent, subaspect):
"""
Get a subaspect from an aspectclass or aspectclass instance.
>>> import coalib.bearlib.aspects as coala_aspects
>>> metadata = coala_aspects['Metadata']
>>> commit_msg = coala_aspects['CommitMessage']
>>> shortlog = coala_aspects['Shortlog']
We can get direct children.
>>> get_subaspect(metadata, commit_msg)
<aspectclass 'Root.Metadata.CommitMessage'>
Or even a grandchildren.
>>> get_subaspect(metadata, shortlog)
<aspectclass 'Root.Metadata.CommitMessage.Shortlog'>
Or with string of aspect name
>>> get_subaspect(metadata, 'shortlog')
<aspectclass 'Root.Metadata.CommitMessage.Shortlog'>
We can also get child instance of an aspect instance.
>>> get_subaspect(metadata('Python'), commit_msg)
<...CommitMessage object at 0x...>
But, passing subaspect instance as argument is prohibited, because
it doesn't really make sense.
>>> get_subaspect(metadata('Python'), commit_msg('Java'))
Traceback (most recent call last):
...
AttributeError: Cannot search an aspect instance using another ...
:param parent: The parent aspect that should be searched.
:param subaspect: An subaspect that we want to find in an
aspectclass.
:return: An aspectclass. Return None if not found.
"""
# Avoid circular import
from .meta import isaspect, issubaspect
if not isaspect(subaspect):
subaspect = coalib.bearlib.aspects[subaspect]
if not issubaspect(subaspect, parent):
return None
if isinstance(subaspect, aspectbase):
raise AttributeError('Cannot search an aspect instance using '
'another aspect instance as argument.')
parent_qualname = (type(parent).__qualname__ if isinstance(
parent, aspectbase) else parent.__qualname__)
if parent_qualname == subaspect.__qualname__:
return parent
# Trim common parent name
aspect_path = re.sub(r'^%s\.' % parent_qualname, '',
subaspect.__qualname__)
aspect_path = aspect_path.split('.')
child = parent
# Traverse through children until we got our subaspect
for path in aspect_path:
child = child.subaspects[path]
return child
def _get_leaf_aspects(aspect):
"""
Explode an aspect into list of its leaf aspects.
:param aspect: An aspect class or instance.
:return: List of leaf aspects.
"""
# Avoid circular import
from .collections import AspectList
leaf_aspects = AspectList()
def search_leaf(aspects):
for aspect in aspects:
if not aspect.subaspects:
nonlocal leaf_aspects
leaf_aspects.append(aspect)
else:
search_leaf(aspect.subaspects.values())
search_leaf([aspect])
return leaf_aspects
class SubaspectGetter:
"""
Special "getter" class to implement ``get()`` method in aspectbase that
could be accessed from the aspectclass or aspectclass instance.
"""
def __get__(self, obj, owner):
parent = obj if obj is not None else owner
return functools.partial(get_subaspect, parent)
class LeafAspectGetter:
"""
Descriptor class for ``get_leaf_aspects()`` method in aspectbase.
This class is required to make the ``get_leaf_aspects()`` accessible from
both aspectclass and aspectclass instance.
"""
def __get__(self, obj, owner):
parent = obj if obj is not None else owner
return functools.partial(_get_leaf_aspects, parent)
class aspectbase:
"""
Base class for aspectclasses with common features for their instances.
Derived classes must use
:class:`coalib.bearlib.aspects.meta.aspectclass` as metaclass.
This is automatically handled by
:meth:`coalib.bearlib.aspects.meta.aspectclass.subaspect` decorator.
"""
get = SubaspectGetter()
get_leaf_aspects = LeafAspectGetter()
def __init__(self, language, **taste_values):
"""
Instantiate an aspectclass with specific `taste_values`,
including parent tastes.
Given tastes must be available for the given `language`,
which must be a language identifier supported by
:class:`coalib.bearlib.languages.Language`.
All taste values will be casted to the related taste cast types.
Non-given available tastes will get their default values.
"""
# bypass self.__setattr__
self.__dict__['language'] = Language[language]
for name, taste in type(self).tastes.items():
if taste.languages and language not in taste.languages:
if name in taste_values:
raise TasteError('%s.%s is not available for %s.' % (
type(self).__qualname__, name, language))
else:
setattr(self, name, taste_values.get(name, taste.default))
# Recursively instance its subaspects too
instanced_child = {}
for name, child in self.subaspects.items():
instanced_child[name] = child(language, **taste_values)
self.__dict__['subaspects'] = instanced_child
def __eq__(self, other):
return type(self) is type(other) and self.tastes == other.tastes
@property
def tastes(self):
"""
Get a dictionary of all taste names mapped to their specific values,
including parent tastes.
"""
return {name: self.__dict__[name] for name in type(self).tastes
if name in self.__dict__}
def __setattr__(self, name, value):
"""
Don't allow attribute manipulations after instantiation of
aspectclasses.
"""
if name not in type(self).tastes:
raise AttributeError(
"can't set attributes of aspectclass instances")
super().__setattr__(name, value)