|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2013 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 # pylint: disable=C0103 |
|
6 |
|
7 """ |
|
8 Module implementing an interface to add different languages to do a syntax |
|
9 check. |
|
10 """ |
|
11 |
|
12 from PyQt5.QtCore import QObject, pyqtSignal |
|
13 |
|
14 from E5Gui.E5Application import e5App |
|
15 from Utilities import determinePythonVersion |
|
16 |
|
17 |
|
18 class SyntaxCheckService(QObject): |
|
19 """ |
|
20 Implement the syntax check service. |
|
21 |
|
22 Plugins can add other languages to the syntax check by calling addLanguage |
|
23 and support of an extra checker module on the client side which has to |
|
24 connect directly to the background service. |
|
25 |
|
26 @signal syntaxChecked(str, dict) emitted when the syntax check was done for |
|
27 one file |
|
28 @signal batchFinished() emitted when a syntax check batch is done |
|
29 @signal error(str, str) emitted in case of an error |
|
30 """ |
|
31 syntaxChecked = pyqtSignal(str, dict) |
|
32 batchFinished = pyqtSignal() |
|
33 error = pyqtSignal(str, str) |
|
34 |
|
35 def __init__(self): |
|
36 """ |
|
37 Constructor |
|
38 """ |
|
39 super().__init__() |
|
40 self.backgroundService = e5App().getObject("BackgroundService") |
|
41 self.__supportedLanguages = {} |
|
42 |
|
43 self.queuedBatches = [] |
|
44 self.batchesFinished = True |
|
45 |
|
46 def __determineLanguage(self, filename, source): |
|
47 """ |
|
48 Private methode to determine the language of the file. |
|
49 |
|
50 @param filename of the sourcefile (str) |
|
51 @param source code of the file (str) |
|
52 @return language of the file or None if not found (str or None) |
|
53 """ |
|
54 pyVer = determinePythonVersion(filename, source) |
|
55 if pyVer: |
|
56 return 'Python{0}'.format(pyVer) |
|
57 |
|
58 for lang, (_env, _getArgs, getExt) in ( |
|
59 self.__supportedLanguages.items() |
|
60 ): |
|
61 if filename.endswith(tuple(getExt())): |
|
62 return lang |
|
63 |
|
64 return None |
|
65 |
|
66 def addLanguage( |
|
67 self, lang, env, path, module, getArgs, getExt, callback, onError): |
|
68 """ |
|
69 Public method to register a new language to the supported languages. |
|
70 |
|
71 @param lang new language to check syntax (str) |
|
72 @param env the environment in which the checker is implemented (str) |
|
73 @param path full path to the module (str) |
|
74 @param module name to import (str) |
|
75 @param getArgs function to collect the required arguments to call the |
|
76 syntax checker on client side (function) |
|
77 @param getExt function that returns the supported file extensions of |
|
78 the syntax checker (function) |
|
79 @param callback function on service response (function) |
|
80 @param onError callback function if client or service isn't available |
|
81 (function) |
|
82 """ |
|
83 self.__supportedLanguages[lang] = env, getArgs, getExt |
|
84 # Connect to the background service |
|
85 self.backgroundService.serviceConnect( |
|
86 '{0}Syntax'.format(lang), env, path, module, callback, onError, |
|
87 onBatchDone=self.batchJobDone) |
|
88 |
|
89 def getLanguages(self): |
|
90 """ |
|
91 Public method to return the supported language names. |
|
92 |
|
93 @return list of languanges supported (list of str) |
|
94 """ |
|
95 return list(self.__supportedLanguages.keys()) + ["MicroPython"] |
|
96 |
|
97 def removeLanguage(self, lang): |
|
98 """ |
|
99 Public method to remove the language from syntax check. |
|
100 |
|
101 @param lang language to remove (str) |
|
102 """ |
|
103 self.__supportedLanguages.pop(lang, None) |
|
104 self.backgroundService.serviceDisconnect( |
|
105 '{0}Syntax'.format(lang), lang) |
|
106 |
|
107 def getExtensions(self): |
|
108 """ |
|
109 Public method to return all supported file extensions for the |
|
110 syntax checker dialog. |
|
111 |
|
112 @return set of all supported file extensions (set of str) |
|
113 """ |
|
114 extensions = set() |
|
115 for _env, _getArgs, getExt in self.__supportedLanguages.values(): |
|
116 for ext in getExt(): |
|
117 extensions.add(ext) |
|
118 return extensions |
|
119 |
|
120 def syntaxCheck(self, lang, filename, source): |
|
121 """ |
|
122 Public method to prepare a syntax check of one source file. |
|
123 |
|
124 @param lang language of the file or None to determine by internal |
|
125 algorithm (str or None) |
|
126 @param filename source filename (string) |
|
127 @param source string containing the code to check (string) |
|
128 """ |
|
129 if not lang: |
|
130 lang = self.__determineLanguage(filename, source) |
|
131 if lang not in self.getLanguages(): |
|
132 return |
|
133 if lang == "MicroPython": |
|
134 lang = "Python3" |
|
135 |
|
136 data = [source] |
|
137 # Call the getArgs function to get the required arguments |
|
138 env, args, getExt = self.__supportedLanguages[lang] |
|
139 data.extend(args()) |
|
140 self.backgroundService.enqueueRequest( |
|
141 '{0}Syntax'.format(lang), env, filename, data) |
|
142 |
|
143 def syntaxBatchCheck(self, argumentsList): |
|
144 """ |
|
145 Public method to prepare a syntax check on multiple source files. |
|
146 |
|
147 @param argumentsList list of arguments tuples with each tuple |
|
148 containing filename and source (string, string) |
|
149 """ |
|
150 data = { |
|
151 } |
|
152 for lang in self.getLanguages(): |
|
153 data[lang] = [] |
|
154 |
|
155 for filename, source in argumentsList: |
|
156 lang = self.__determineLanguage(filename, source) |
|
157 if lang not in self.getLanguages(): |
|
158 continue |
|
159 else: |
|
160 jobData = [source] |
|
161 # Call the getArgs function to get the required arguments |
|
162 args = self.__supportedLanguages[lang][1] |
|
163 jobData.extend(args()) |
|
164 data[lang].append((filename, jobData)) |
|
165 |
|
166 self.queuedBatches = [] |
|
167 for lang in self.getLanguages(): |
|
168 if data[lang]: |
|
169 self.queuedBatches.append(lang) |
|
170 env = self.__supportedLanguages[lang][0] |
|
171 self.backgroundService.enqueueRequest( |
|
172 'batch_{0}Syntax'.format(lang), env, "", data[lang]) |
|
173 self.batchesFinished = False |
|
174 |
|
175 def cancelSyntaxBatchCheck(self): |
|
176 """ |
|
177 Public method to cancel all batch jobs. |
|
178 """ |
|
179 for lang in self.getLanguages(): |
|
180 try: |
|
181 env = self.__supportedLanguages[lang][0] |
|
182 self.backgroundService.requestCancel( |
|
183 'batch_{0}Syntax'.format(lang), env) |
|
184 except KeyError: |
|
185 continue |
|
186 |
|
187 def __serviceError(self, fn, msg): |
|
188 """ |
|
189 Private slot handling service errors. |
|
190 |
|
191 @param fn file name (string) |
|
192 @param msg message text (string) |
|
193 """ |
|
194 self.error.emit(fn, msg) |
|
195 |
|
196 def serviceErrorPy3(self, fx, lang, fn, msg): |
|
197 """ |
|
198 Public method handling service errors for Python 3. |
|
199 |
|
200 @param fx service name (string) |
|
201 @param lang language (string) |
|
202 @param fn file name (string) |
|
203 @param msg message text (string) |
|
204 """ |
|
205 if fx in ['Python3Syntax', 'batch_Python3Syntax']: |
|
206 if fx == 'Python3Syntax': |
|
207 self.__serviceError(fn, msg) |
|
208 else: |
|
209 self.__serviceError(self.tr("Python 3 batch check"), msg) |
|
210 self.batchJobDone(fx, lang) |
|
211 |
|
212 def serviceErrorJavaScript(self, fx, lang, fn, msg): |
|
213 """ |
|
214 Public method handling service errors for JavaScript. |
|
215 |
|
216 @param fx service name (string) |
|
217 @param lang language (string) |
|
218 @param fn file name (string) |
|
219 @param msg message text (string) |
|
220 """ |
|
221 if fx in ['JavaScriptSyntax', 'batch_JavaScriptSyntax']: |
|
222 if fx == 'JavaScriptSyntax': |
|
223 self.__serviceError(fn, msg) |
|
224 else: |
|
225 self.__serviceError(self.tr("JavaScript batch check"), msg) |
|
226 self.batchJobDone(fx, lang) |
|
227 |
|
228 def serviceErrorYAML(self, fx, lang, fn, msg): |
|
229 """ |
|
230 Public method handling service errors for YAML. |
|
231 |
|
232 @param fx service name (string) |
|
233 @param lang language (string) |
|
234 @param fn file name (string) |
|
235 @param msg message text (string) |
|
236 """ |
|
237 if fx in ['YAMLSyntax', 'batch_YAMLSyntax']: |
|
238 if fx == 'YAMLSyntax': |
|
239 self.__serviceError(fn, msg) |
|
240 else: |
|
241 self.__serviceError(self.tr("YAML batch check"), msg) |
|
242 self.batchJobDone(fx, lang) |
|
243 |
|
244 def serviceErrorJSON(self, fx, lang, fn, msg): |
|
245 """ |
|
246 Public method handling service errors for JSON. |
|
247 |
|
248 @param fx service name (string) |
|
249 @param lang language (string) |
|
250 @param fn file name (string) |
|
251 @param msg message text (string) |
|
252 """ |
|
253 if fx in ['JSONSyntax', 'batch_JSONSyntax']: |
|
254 if fx == 'JSONSyntax': |
|
255 self.__serviceError(fn, msg) |
|
256 else: |
|
257 self.__serviceError(self.tr("JSON batch check"), msg) |
|
258 self.batchJobDone(fx, lang) |
|
259 |
|
260 def serviceErrorTOML(self, fx, lang, fn, msg): |
|
261 """ |
|
262 Public method handling service errors for TOML. |
|
263 |
|
264 @param fx service name (string) |
|
265 @param lang language (string) |
|
266 @param fn file name (string) |
|
267 @param msg message text (string) |
|
268 """ |
|
269 if fx in ['TOMLSyntax', 'batch_TOMLSyntax']: |
|
270 if fx == 'TOMLSyntax': |
|
271 self.__serviceError(fn, msg) |
|
272 else: |
|
273 self.__serviceError(self.tr("TOML batch check"), msg) |
|
274 self.batchJobDone(fx, lang) |
|
275 |
|
276 def batchJobDone(self, fx, lang): |
|
277 """ |
|
278 Public slot handling the completion of a batch job. |
|
279 |
|
280 @param fx service name (string) |
|
281 @param lang language (string) |
|
282 """ |
|
283 if fx in [ |
|
284 'Python3Syntax', 'batch_Python3Syntax', |
|
285 'JavaScriptSyntax', 'batch_JavaScriptSyntax', |
|
286 'YAMLSyntax', 'batch_YAMLSyntax', |
|
287 'JSONSyntax', 'batch_JSONSyntax', |
|
288 'TOMLSyntax', 'batch_TOMLSyntax', |
|
289 ]: |
|
290 if lang in self.queuedBatches: |
|
291 self.queuedBatches.remove(lang) |
|
292 # prevent sending the signal multiple times |
|
293 if len(self.queuedBatches) == 0 and not self.batchesFinished: |
|
294 self.batchFinished.emit() |
|
295 self.batchesFinished = True |