|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2015 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the cyclomatic complexity service. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 |
|
12 try: |
|
13 str = unicode # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ |
|
14 except NameError: |
|
15 pass |
|
16 |
|
17 import ast |
|
18 |
|
19 import multiprocessing |
|
20 import sys |
|
21 |
|
22 from vulture import Vulture |
|
23 |
|
24 |
|
25 def initService(): |
|
26 """ |
|
27 Initialize the service and return the entry point. |
|
28 |
|
29 @return the entry point for the background client (function) |
|
30 """ |
|
31 return vultureCheck |
|
32 |
|
33 |
|
34 def initBatchService(): |
|
35 """ |
|
36 Initialize the batch service and return the entry point. |
|
37 |
|
38 @return the entry point for the background client (function) |
|
39 """ |
|
40 return batchVultureCheck |
|
41 |
|
42 |
|
43 def vultureCheck(file, text=""): |
|
44 """ |
|
45 Private function to analyze one file. |
|
46 |
|
47 @param file source filename |
|
48 @type str |
|
49 @param text source text |
|
50 @type str |
|
51 @return tuple containing the result dictionary |
|
52 @rtype (tuple of dict) |
|
53 """ |
|
54 return __analyze(file, text) |
|
55 |
|
56 |
|
57 def batchVultureCheck(argumentsList, send, fx, cancelled): |
|
58 """ |
|
59 Module function to analyze a batch of files. |
|
60 |
|
61 @param argumentsList list of arguments tuples as given for vultureCheck |
|
62 @type list |
|
63 @param send reference to send function |
|
64 @type function |
|
65 @param fx registered service name |
|
66 @type str |
|
67 @param cancelled reference to function checking for a cancellation |
|
68 @type function |
|
69 """ |
|
70 try: |
|
71 NumberOfProcesses = multiprocessing.cpu_count() |
|
72 if NumberOfProcesses >= 1: |
|
73 NumberOfProcesses -= 1 |
|
74 except NotImplementedError: |
|
75 NumberOfProcesses = 1 |
|
76 |
|
77 # Create queues |
|
78 taskQueue = multiprocessing.Queue() |
|
79 doneQueue = multiprocessing.Queue() |
|
80 |
|
81 # Submit tasks (initially two time number of processes |
|
82 initialTasks = 2 * NumberOfProcesses |
|
83 for task in argumentsList[:initialTasks]: |
|
84 taskQueue.put(task) |
|
85 |
|
86 # Start worker processes |
|
87 for i in range(NumberOfProcesses): |
|
88 multiprocessing.Process(target=worker, args=(taskQueue, doneQueue))\ |
|
89 .start() |
|
90 |
|
91 # Get and send results |
|
92 endIndex = len(argumentsList) - initialTasks |
|
93 for i in range(len(argumentsList)): |
|
94 filename, result = doneQueue.get() |
|
95 send(fx, filename, result) |
|
96 if cancelled(): |
|
97 # just exit the loop ignoring the results of queued tasks |
|
98 break |
|
99 if i < endIndex: |
|
100 taskQueue.put(argumentsList[i + initialTasks]) |
|
101 |
|
102 # Tell child processes to stop |
|
103 for i in range(NumberOfProcesses): |
|
104 taskQueue.put('STOP') |
|
105 |
|
106 |
|
107 def worker(input, output): |
|
108 """ |
|
109 Module function acting as the parallel worker for the cyclomatic |
|
110 complexity calculation. |
|
111 |
|
112 @param input input queue |
|
113 @type multiprocessing.Queue |
|
114 @param output output queue |
|
115 @type multiprocessing.Queue |
|
116 """ |
|
117 for filename, source in iter(input.get, 'STOP'): |
|
118 result = __analyze(filename, source) |
|
119 output.put((filename, result)) |
|
120 |
|
121 |
|
122 def __analyze(file, text=""): |
|
123 """ |
|
124 Private function to analyze one Python file. |
|
125 |
|
126 @param file source file name |
|
127 @type str |
|
128 @param text source text |
|
129 @type str |
|
130 @return tuple containing the result dictionary |
|
131 @rtype (tuple of dict) |
|
132 """ |
|
133 |
|
134 # Check type for py2: if not str it's unicode |
|
135 if sys.version_info[0] == 2: |
|
136 try: |
|
137 text = text.encode('utf-8') |
|
138 except UnicodeError: |
|
139 pass |
|
140 |
|
141 try: |
|
142 v = EricVulture(file) |
|
143 v.scan(text) |
|
144 res = v.getResults() |
|
145 except Exception as err: |
|
146 res = {"error": str(err)} |
|
147 return (res, ) |
|
148 |
|
149 |
|
150 class EricVulture(Vulture): |
|
151 """ |
|
152 Class to adopt the Vulture class to the eric plug-in functionality. |
|
153 """ |
|
154 def __init__(self, filename): |
|
155 """ |
|
156 Constructor |
|
157 |
|
158 @param filename source file name |
|
159 @type str |
|
160 """ |
|
161 super(EricVulture, self).__init__() |
|
162 |
|
163 self.file = filename |
|
164 self.code = None |
|
165 |
|
166 def scan(self, source): |
|
167 """ |
|
168 Public method to scan the source text. |
|
169 |
|
170 @param source source text |
|
171 @type str |
|
172 """ |
|
173 self.code = source.splitlines() |
|
174 node = ast.parse(source, filename=self.file) |
|
175 self.visit(node) |
|
176 |
|
177 def __item2Dict(self, item): |
|
178 """ |
|
179 Private method to convert a vulture item to a dictionary. |
|
180 |
|
181 @param item vulture item |
|
182 @type vulture.Item |
|
183 @return item dictionary |
|
184 @rtype dict |
|
185 """ |
|
186 d = { |
|
187 "name": str(item), |
|
188 "type": getattr(item, "typ", ""), |
|
189 "file": getattr(item, "file", ""), |
|
190 "line": getattr(item, "lineno", ""), |
|
191 } |
|
192 return d |
|
193 |
|
194 def getResults(self): |
|
195 """ |
|
196 Public method to get the scan results. |
|
197 |
|
198 @return scan results |
|
199 @rtype dict |
|
200 """ |
|
201 return { |
|
202 "DefinedAttributes": |
|
203 [self.__item2Dict(i) for i in self.defined_attrs], |
|
204 "DefinedFunctions": |
|
205 [self.__item2Dict(i) for i in self.defined_funcs], |
|
206 "DefinedProperties": |
|
207 [self.__item2Dict(i) for i in self.defined_props], |
|
208 "DefinedVariables": |
|
209 [self.__item2Dict(i) for i in self.defined_vars], |
|
210 "UsedAttributes": |
|
211 [self.__item2Dict(i) for i in self.used_attrs], |
|
212 "UsedVariables": |
|
213 [self.__item2Dict(i) for i in self.used_vars], |
|
214 "TupleVariables": |
|
215 [self.__item2Dict(i) for i in self.tuple_assign_vars], |
|
216 "Aliases": |
|
217 [self.__item2Dict(i) for i in self.names_imported_as_aliases], |
|
218 } |