|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2019 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the syntax check for JSON. |
|
8 """ |
|
9 |
|
10 import queue |
|
11 import multiprocessing |
|
12 |
|
13 |
|
14 def initService(): |
|
15 """ |
|
16 Initialize the service and return the entry point. |
|
17 |
|
18 @return the entry point for the background client |
|
19 @rtype func |
|
20 """ |
|
21 return jsonSyntaxCheck |
|
22 |
|
23 |
|
24 def initBatchService(): |
|
25 """ |
|
26 Initialize the batch service and return the entry point. |
|
27 |
|
28 @return the entry point for the background client |
|
29 @rtype func |
|
30 """ |
|
31 return jsonSyntaxBatchCheck |
|
32 |
|
33 |
|
34 def normalizeCode(codestring): |
|
35 """ |
|
36 Function to normalize the given code. |
|
37 |
|
38 @param codestring code to be normalized |
|
39 @type str |
|
40 @return normalized code |
|
41 @rtype str |
|
42 """ |
|
43 codestring = codestring.replace("\r\n", "\n").replace("\r", "\n") |
|
44 |
|
45 if codestring and codestring[-1] != '\n': |
|
46 codestring += '\n' |
|
47 |
|
48 return codestring |
|
49 |
|
50 |
|
51 def jsonSyntaxCheck(file, codestring): |
|
52 """ |
|
53 Function to check a JSON source file for syntax errors. |
|
54 |
|
55 @param file source filename |
|
56 @type str |
|
57 @param codestring string containing the code to check |
|
58 @type str |
|
59 @return dictionary with the keys 'error' and 'warnings' which |
|
60 hold a list containing details about the error/ warnings |
|
61 (file name, line number, column, codestring (only at syntax |
|
62 errors), the message, a list with arguments for the message) |
|
63 @rtype dict |
|
64 """ |
|
65 return __jsonSyntaxCheck(file, codestring) |
|
66 |
|
67 |
|
68 def jsonSyntaxBatchCheck(argumentsList, send, fx, cancelled, maxProcesses=0): |
|
69 """ |
|
70 Module function to check syntax for a batch of files. |
|
71 |
|
72 @param argumentsList list of arguments tuples as given for yamlSyntaxCheck |
|
73 @type list |
|
74 @param send reference to send function |
|
75 @type func |
|
76 @param fx registered service name |
|
77 @type str |
|
78 @param cancelled reference to function checking for a cancellation |
|
79 @type func |
|
80 @param maxProcesses number of processes to be used |
|
81 @type int |
|
82 """ |
|
83 if maxProcesses == 0: |
|
84 # determine based on CPU count |
|
85 try: |
|
86 NumberOfProcesses = multiprocessing.cpu_count() |
|
87 if NumberOfProcesses >= 1: |
|
88 NumberOfProcesses -= 1 |
|
89 except NotImplementedError: |
|
90 NumberOfProcesses = 1 |
|
91 else: |
|
92 NumberOfProcesses = maxProcesses |
|
93 |
|
94 # Create queues |
|
95 taskQueue = multiprocessing.Queue() |
|
96 doneQueue = multiprocessing.Queue() |
|
97 |
|
98 # Submit tasks (initially two time number of processes |
|
99 initialTasks = 2 * NumberOfProcesses |
|
100 for task in argumentsList[:initialTasks]: |
|
101 taskQueue.put(task) |
|
102 |
|
103 # Start worker processes |
|
104 workers = [ |
|
105 multiprocessing.Process( |
|
106 target=workerTask, args=(taskQueue, doneQueue) |
|
107 ) for _ in range(NumberOfProcesses) |
|
108 ] |
|
109 for worker in workers: |
|
110 worker.start() |
|
111 |
|
112 # Get and send results |
|
113 endIndex = len(argumentsList) - initialTasks |
|
114 for i in range(len(argumentsList)): |
|
115 resultSent = False |
|
116 wasCancelled = False |
|
117 |
|
118 while not resultSent: |
|
119 try: |
|
120 # get result (waiting max. 3 seconds and send it to frontend |
|
121 filename, result = doneQueue.get() |
|
122 send(fx, filename, result) |
|
123 resultSent = True |
|
124 except queue.Empty: |
|
125 # ignore empty queue, just carry on |
|
126 if cancelled(): |
|
127 wasCancelled = True |
|
128 break |
|
129 |
|
130 if wasCancelled or cancelled(): |
|
131 # just exit the loop ignoring the results of queued tasks |
|
132 break |
|
133 |
|
134 if i < endIndex: |
|
135 taskQueue.put(argumentsList[i + initialTasks]) |
|
136 |
|
137 # Tell child processes to stop |
|
138 for _ in range(NumberOfProcesses): |
|
139 taskQueue.put('STOP') |
|
140 |
|
141 for worker in workers: |
|
142 worker.join() |
|
143 worker.close() |
|
144 |
|
145 |
|
146 def workerTask(inputQueue, outputQueue): |
|
147 """ |
|
148 Module function acting as the parallel worker for the syntax check. |
|
149 |
|
150 @param inputQueue input queue |
|
151 @type multiprocessing.Queue |
|
152 @param outputQueue output queue |
|
153 @type multiprocessing.Queue |
|
154 """ |
|
155 for filename, args in iter(inputQueue.get, 'STOP'): |
|
156 source = args[0] |
|
157 result = __jsonSyntaxCheck(filename, source) |
|
158 outputQueue.put((filename, result)) |
|
159 |
|
160 |
|
161 def __jsonSyntaxCheck(file, codestring): |
|
162 """ |
|
163 Function to check a YAML source file for syntax errors. |
|
164 |
|
165 @param file source filename |
|
166 @type str |
|
167 @param codestring string containing the code to check |
|
168 @type str |
|
169 @return dictionary with the keys 'error' and 'warnings' which |
|
170 hold a list containing details about the error/ warnings |
|
171 (file name, line number, column, codestring (only at syntax |
|
172 errors), the message, a list with arguments for the message) |
|
173 @rtype dict |
|
174 """ |
|
175 import json |
|
176 |
|
177 codestring = normalizeCode(codestring) |
|
178 |
|
179 try: |
|
180 json.loads(codestring) |
|
181 except json.JSONDecodeError as exc: |
|
182 line = exc.lineno |
|
183 column = exc.colno |
|
184 error = exc.msg |
|
185 |
|
186 cline = min(len(codestring.splitlines()), int(line)) - 1 |
|
187 code = codestring.splitlines()[cline] |
|
188 |
|
189 return [{'error': (file, line, column, code, error)}] |
|
190 |
|
191 return [{}] |