|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2003 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Package implementing various functions/classes needed everywhere within eric. |
|
8 """ |
|
9 |
|
10 import codecs |
|
11 import contextlib |
|
12 import ctypes |
|
13 import fnmatch |
|
14 import functools |
|
15 import getpass |
|
16 import glob |
|
17 import os |
|
18 import re |
|
19 import shlex |
|
20 import subprocess # secok |
|
21 import sys |
|
22 |
|
23 |
|
24 def __showwarning(message, category, filename, lineno, file=None, line=""): |
|
25 """ |
|
26 Module function to raise a SyntaxError for a SyntaxWarning. |
|
27 |
|
28 @param message warning object |
|
29 @param category type object of the warning |
|
30 @param filename name of the file causing the warning (string) |
|
31 @param lineno line number causing the warning (integer) |
|
32 @param file file to write the warning message to (ignored) |
|
33 @param line line causing the warning (ignored) |
|
34 @raise err exception of type SyntaxError |
|
35 """ |
|
36 if category is SyntaxWarning: |
|
37 err = SyntaxError(str(message)) |
|
38 err.filename = filename |
|
39 err.lineno = lineno |
|
40 raise err |
|
41 |
|
42 import warnings |
|
43 warnings.showwarning = __showwarning |
|
44 |
|
45 from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF32 |
|
46 |
|
47 from PyQt6.QtCore import ( |
|
48 qVersion, PYQT_VERSION_STR, QDir, QProcess, QByteArray, QCoreApplication, |
|
49 QCryptographicHash |
|
50 ) |
|
51 from PyQt6.Qsci import QSCINTILLA_VERSION_STR, QsciScintilla |
|
52 |
|
53 # import these methods into the Utilities namespace |
|
54 from Globals import ( # __IGNORE_WARNING__ |
|
55 isWindowsPlatform, isLinuxPlatform, isMacPlatform, desktopName, |
|
56 sessionType, getConfigDir, setConfigDir, getPythonLibraryDirectory, |
|
57 getPyQt6ModulesDirectory, getQtBinariesPath, getPyQtToolsPath, |
|
58 qVersionTuple, getPythonExecutable |
|
59 ) |
|
60 |
|
61 from EricWidgets.EricApplication import ericApp |
|
62 |
|
63 from UI.Info import Program, Version |
|
64 |
|
65 import Preferences |
|
66 from Plugins.CheckerPlugins.SyntaxChecker.SyntaxCheck import ( |
|
67 # __IGNORE_WARNING__ |
|
68 normalizeCode) |
|
69 |
|
70 from eric7config import getConfig |
|
71 |
|
72 configDir = None |
|
73 |
|
74 codingBytes_regexps = [ |
|
75 (5, re.compile(br'''coding[:=]\s*([-\w_.]+)''')), |
|
76 (1, re.compile(br'''<\?xml.*\bencoding\s*=\s*['"]([-\w_.]+)['"]\?>''')), |
|
77 ] |
|
78 coding_regexps = [ |
|
79 (5, re.compile(r'''coding[:=]\s*([-\w_.]+)''')), |
|
80 (1, re.compile(r'''<\?xml.*\bencoding\s*=\s*['"]([-\w_.]+)['"]\?>''')), |
|
81 ] |
|
82 |
|
83 supportedCodecs = [ |
|
84 'utf-8', |
|
85 |
|
86 'iso-8859-1', 'iso-8859-2', 'iso-8859-3', |
|
87 'iso-8859-4', 'iso-8859-5', 'iso-8859-6', 'iso-8859-7', |
|
88 'iso-8859-8', 'iso-8859-9', 'iso-8859-10', 'iso-8859-11', |
|
89 'iso-8859-13', 'iso-8859-14', 'iso-8859-15', 'iso-8859-16', |
|
90 'latin-1', |
|
91 |
|
92 'koi8-r', 'koi8-t', 'koi8-u', |
|
93 |
|
94 'utf-7', |
|
95 'utf-16', 'utf-16-be', 'utf-16-le', |
|
96 'utf-32', 'utf-32-be', 'utf-32-le', |
|
97 |
|
98 'cp037', 'cp273', 'cp424', 'cp437', 'cp500', 'cp720', |
|
99 'cp737', 'cp775', 'cp850', 'cp852', 'cp855', 'cp856', |
|
100 'cp857', 'cp858', 'cp860', 'cp861', 'cp862', 'cp863', |
|
101 'cp864', 'cp865', 'cp866', 'cp869', 'cp874', 'cp875', |
|
102 'cp932', 'cp949', 'cp950', 'cp1006', 'cp1026', 'cp1125', |
|
103 'cp1140', |
|
104 |
|
105 'windows-1250', 'windows-1251', 'windows-1252', 'windows-1253', |
|
106 'windows-1254', 'windows-1255', 'windows-1256', 'windows-1257', |
|
107 'windows-1258', |
|
108 |
|
109 'gb2312', 'hz', 'gb18030', 'gbk', |
|
110 |
|
111 'iso-2022-jp', 'iso-2022-jp-1', 'iso-2022-jp-2', 'iso-2022-jp-2004', |
|
112 'iso-2022-jp-3', 'iso-2022-jp-ext', 'iso-2022-kr', |
|
113 |
|
114 'mac-cyrillic', 'mac-greek', 'mac-iceland', 'mac-latin2', |
|
115 'mac-roman', 'mac-turkish', |
|
116 |
|
117 'ascii', |
|
118 'big5-tw', 'big5-hkscs', |
|
119 ] |
|
120 |
|
121 |
|
122 class CodingError(Exception): |
|
123 """ |
|
124 Class implementing an exception, which is raised, if a given coding is |
|
125 incorrect. |
|
126 """ |
|
127 def __init__(self, coding): |
|
128 """ |
|
129 Constructor |
|
130 |
|
131 @param coding coding to include in the message (string) |
|
132 """ |
|
133 self.errorMessage = QCoreApplication.translate( |
|
134 "CodingError", |
|
135 "The coding '{0}' is wrong for the given text.").format(coding) |
|
136 |
|
137 def __repr__(self): |
|
138 """ |
|
139 Special method returning a representation of the exception. |
|
140 |
|
141 @return string representing the error message |
|
142 """ |
|
143 return str(self.errorMessage) |
|
144 |
|
145 def __str__(self): |
|
146 """ |
|
147 Special method returning a string representation of the exception. |
|
148 |
|
149 @return string representing the error message |
|
150 """ |
|
151 return str(self.errorMessage) |
|
152 |
|
153 |
|
154 def get_codingBytes(text): |
|
155 """ |
|
156 Function to get the coding of a bytes text. |
|
157 |
|
158 @param text bytes text to inspect (bytes) |
|
159 @return coding string |
|
160 """ |
|
161 lines = text.splitlines() |
|
162 for coding in codingBytes_regexps: |
|
163 coding_re = coding[1] |
|
164 head = lines[:coding[0]] |
|
165 for line in head: |
|
166 m = coding_re.search(line) |
|
167 if m: |
|
168 return str(m.group(1), "ascii").lower() |
|
169 return None |
|
170 |
|
171 |
|
172 def get_coding(text): |
|
173 """ |
|
174 Function to get the coding of a text. |
|
175 |
|
176 @param text text to inspect (string) |
|
177 @return coding string |
|
178 """ |
|
179 lines = text.splitlines() |
|
180 for coding in coding_regexps: |
|
181 coding_re = coding[1] |
|
182 head = lines[:coding[0]] |
|
183 for line in head: |
|
184 m = coding_re.search(line) |
|
185 if m: |
|
186 return m.group(1).lower() |
|
187 return None |
|
188 |
|
189 |
|
190 def readEncodedFile(filename): |
|
191 """ |
|
192 Function to read a file and decode its contents into proper text. |
|
193 |
|
194 @param filename name of the file to read (string) |
|
195 @return tuple of decoded text and encoding (string, string) |
|
196 """ |
|
197 with open(filename, "rb") as f: |
|
198 text = f.read() |
|
199 return decode(text) |
|
200 |
|
201 |
|
202 def readEncodedFileWithHash(filename): |
|
203 """ |
|
204 Function to read a file, calculate a hash value and decode its contents |
|
205 into proper text. |
|
206 |
|
207 @param filename name of the file to read (string) |
|
208 @return tuple of decoded text, encoding and hash value (string, string, |
|
209 string) |
|
210 """ |
|
211 with open(filename, "rb") as f: |
|
212 text = f.read() |
|
213 hashStr = str(QCryptographicHash.hash( |
|
214 QByteArray(text), QCryptographicHash.Algorithm.Md5).toHex(), |
|
215 encoding="ASCII") |
|
216 return decode(text) + (hashStr, ) |
|
217 |
|
218 |
|
219 def decode(text): |
|
220 """ |
|
221 Function to decode some byte text into a string. |
|
222 |
|
223 @param text byte text to decode (bytes) |
|
224 @return tuple of decoded text and encoding (string, string) |
|
225 """ |
|
226 with contextlib.suppress(UnicodeError, LookupError): |
|
227 if text.startswith(BOM_UTF8): |
|
228 # UTF-8 with BOM |
|
229 return str(text[len(BOM_UTF8):], 'utf-8'), 'utf-8-bom' |
|
230 elif text.startswith(BOM_UTF16): |
|
231 # UTF-16 with BOM |
|
232 return str(text[len(BOM_UTF16):], 'utf-16'), 'utf-16' |
|
233 elif text.startswith(BOM_UTF32): |
|
234 # UTF-32 with BOM |
|
235 return str(text[len(BOM_UTF32):], 'utf-32'), 'utf-32' |
|
236 coding = get_codingBytes(text) |
|
237 if coding: |
|
238 return str(text, coding), coding |
|
239 |
|
240 # Assume UTF-8 |
|
241 with contextlib.suppress(UnicodeError, LookupError): |
|
242 return str(text, 'utf-8'), 'utf-8-guessed' |
|
243 |
|
244 guess = None |
|
245 if Preferences.getEditor("AdvancedEncodingDetection"): |
|
246 # Try the universal character encoding detector |
|
247 try: |
|
248 import chardet |
|
249 guess = chardet.detect(text) |
|
250 if ( |
|
251 guess and |
|
252 guess['confidence'] > 0.95 and |
|
253 guess['encoding'] is not None |
|
254 ): |
|
255 codec = guess['encoding'].lower() |
|
256 return str(text, codec), '{0}-guessed'.format(codec) |
|
257 except (UnicodeError, LookupError): |
|
258 pass |
|
259 except ImportError: |
|
260 pass |
|
261 |
|
262 # Try default encoding |
|
263 with contextlib.suppress(UnicodeError, LookupError): |
|
264 codec = Preferences.getEditor("DefaultEncoding") |
|
265 return str(text, codec), '{0}-default'.format(codec) |
|
266 |
|
267 if ( |
|
268 Preferences.getEditor("AdvancedEncodingDetection") and |
|
269 guess and |
|
270 guess['encoding'] is not None |
|
271 ): |
|
272 # Use the guessed one even if confidence level is low |
|
273 with contextlib.suppress(UnicodeError, LookupError): |
|
274 codec = guess['encoding'].lower() |
|
275 return str(text, codec), '{0}-guessed'.format(codec) |
|
276 |
|
277 # Assume UTF-8 loosing information |
|
278 return str(text, "utf-8", "ignore"), 'utf-8-ignore' |
|
279 |
|
280 |
|
281 def readEncodedFileWithEncoding(filename, encoding): |
|
282 """ |
|
283 Function to read a file and decode its contents into proper text. |
|
284 |
|
285 @param filename name of the file to read (string) |
|
286 @param encoding encoding to be used to read the file (string) |
|
287 @return tuple of decoded text and encoding (string, string) |
|
288 """ |
|
289 with open(filename, "rb") as f: |
|
290 text = f.read() |
|
291 if encoding: |
|
292 with contextlib.suppress(UnicodeError, LookupError): |
|
293 return str(text, encoding), '{0}-selected'.format(encoding) |
|
294 |
|
295 # Try default encoding |
|
296 with contextlib.suppress(UnicodeError, LookupError): |
|
297 codec = Preferences.getEditor("DefaultEncoding") |
|
298 return str(text, codec), '{0}-default'.format(codec) |
|
299 |
|
300 # Assume UTF-8 loosing information |
|
301 return str(text, "utf-8", "ignore"), 'utf-8-ignore' |
|
302 else: |
|
303 return decode(text) |
|
304 |
|
305 |
|
306 def writeEncodedFile(filename, text, origEncoding, forcedEncoding=""): |
|
307 """ |
|
308 Function to write a file with properly encoded text. |
|
309 |
|
310 @param filename name of the file to read |
|
311 @type str |
|
312 @param text text to be written |
|
313 @type str |
|
314 @param origEncoding type of the original encoding |
|
315 @type str |
|
316 @param forcedEncoding encoding to be used for writing, if no coding |
|
317 line is present |
|
318 @type str |
|
319 @return encoding used for writing the file |
|
320 @rtype str |
|
321 """ |
|
322 etext, encoding = encode(text, origEncoding, forcedEncoding=forcedEncoding) |
|
323 |
|
324 with open(filename, "wb") as f: |
|
325 f.write(etext) |
|
326 |
|
327 return encoding |
|
328 |
|
329 |
|
330 def encode(text, origEncoding, forcedEncoding=""): |
|
331 """ |
|
332 Function to encode text into a byte text. |
|
333 |
|
334 @param text text to be encoded |
|
335 @type str |
|
336 @param origEncoding type of the original encoding |
|
337 @type str |
|
338 @param forcedEncoding encoding to be used for writing, if no coding line |
|
339 is present |
|
340 @type str |
|
341 @return tuple of encoded text and encoding used |
|
342 @rtype tuple of (bytes, str) |
|
343 @exception CodingError raised to indicate an invalid encoding |
|
344 """ |
|
345 encoding = None |
|
346 if origEncoding == 'utf-8-bom': |
|
347 etext, encoding = BOM_UTF8 + text.encode("utf-8"), 'utf-8-bom' |
|
348 else: |
|
349 # Try declared coding spec |
|
350 coding = get_coding(text) |
|
351 if coding: |
|
352 try: |
|
353 etext, encoding = text.encode(coding), coding |
|
354 except (UnicodeError, LookupError): |
|
355 # Error: Declared encoding is incorrect |
|
356 raise CodingError(coding) |
|
357 else: |
|
358 if forcedEncoding: |
|
359 with contextlib.suppress(UnicodeError, LookupError): |
|
360 etext, encoding = ( |
|
361 text.encode(forcedEncoding), forcedEncoding) |
|
362 # if forced encoding is incorrect, ignore it |
|
363 |
|
364 if encoding is None: |
|
365 # Try the original encoding |
|
366 if origEncoding and origEncoding.endswith( |
|
367 ('-selected', '-default', '-guessed', '-ignore')): |
|
368 coding = ( |
|
369 origEncoding |
|
370 .replace("-selected", "") |
|
371 .replace("-default", "") |
|
372 .replace("-guessed", "") |
|
373 .replace("-ignore", "") |
|
374 ) |
|
375 with contextlib.suppress(UnicodeError, LookupError): |
|
376 etext, encoding = text.encode(coding), coding |
|
377 |
|
378 if encoding is None: |
|
379 # Try configured default |
|
380 with contextlib.suppress(UnicodeError, LookupError): |
|
381 codec = Preferences.getEditor("DefaultEncoding") |
|
382 etext, encoding = text.encode(codec), codec |
|
383 |
|
384 if encoding is None: |
|
385 # Try saving as ASCII |
|
386 with contextlib.suppress(UnicodeError): |
|
387 etext, encoding = text.encode('ascii'), 'ascii' |
|
388 |
|
389 if encoding is None: |
|
390 # Save as UTF-8 without BOM |
|
391 etext, encoding = text.encode('utf-8'), 'utf-8' |
|
392 |
|
393 return etext, encoding |
|
394 |
|
395 |
|
396 def decodeString(text): |
|
397 """ |
|
398 Function to decode a string containing Unicode encoded characters. |
|
399 |
|
400 @param text text containing encoded chars (string) |
|
401 @return decoded text (string) |
|
402 """ |
|
403 buf = b"" |
|
404 index = 0 |
|
405 while index < len(text): |
|
406 if text[index] == "\\": |
|
407 qb = QByteArray.fromHex(text[index:index + 4].encode()) |
|
408 buf += bytes(qb) |
|
409 index += 4 |
|
410 else: |
|
411 buf += codecs.encode(text[index], "utf-8") |
|
412 index += 1 |
|
413 buf = buf.replace(b"\x00", b"") |
|
414 return decodeBytes(buf) |
|
415 |
|
416 |
|
417 def decodeBytes(buffer): |
|
418 """ |
|
419 Function to decode some byte text into a string. |
|
420 |
|
421 @param buffer byte buffer to decode (bytes) |
|
422 @return decoded text (string) |
|
423 """ |
|
424 # try UTF with BOM |
|
425 with contextlib.suppress(UnicodeError, LookupError): |
|
426 if buffer.startswith(BOM_UTF8): |
|
427 # UTF-8 with BOM |
|
428 return str(buffer[len(BOM_UTF8):], encoding='utf-8') |
|
429 elif buffer.startswith(BOM_UTF16): |
|
430 # UTF-16 with BOM |
|
431 return str(buffer[len(BOM_UTF16):], encoding='utf-16') |
|
432 elif buffer.startswith(BOM_UTF32): |
|
433 # UTF-32 with BOM |
|
434 return str(buffer[len(BOM_UTF32):], encoding='utf-32') |
|
435 |
|
436 # try UTF-8 |
|
437 with contextlib.suppress(UnicodeError): |
|
438 return str(buffer, encoding="utf-8") |
|
439 |
|
440 # try codec detection |
|
441 try: |
|
442 import chardet |
|
443 guess = chardet.detect(buffer) |
|
444 if guess and guess['encoding'] is not None: |
|
445 codec = guess['encoding'].lower() |
|
446 return str(buffer, encoding=codec) |
|
447 except (UnicodeError, LookupError): |
|
448 pass |
|
449 except ImportError: |
|
450 pass |
|
451 |
|
452 return str(buffer, encoding="utf-8", errors="ignore") |
|
453 |
|
454 |
|
455 def readStringFromStream(stream): |
|
456 """ |
|
457 Module function to read a string from the given stream. |
|
458 |
|
459 @param stream data stream opened for reading (QDataStream) |
|
460 @return string read from the stream (string) |
|
461 """ |
|
462 data = stream.readString() |
|
463 if data is None: |
|
464 data = b"" |
|
465 return data.decode('utf-8') |
|
466 |
|
467 |
|
468 _escape = re.compile("[&<>\"'\u0080-\uffff]") |
|
469 |
|
470 _escape_map = { |
|
471 "&": "&", |
|
472 "<": "<", |
|
473 ">": ">", |
|
474 '"': """, |
|
475 "'": "'", |
|
476 } |
|
477 |
|
478 |
|
479 def escape_entities(m, escmap=_escape_map): |
|
480 """ |
|
481 Function to encode html entities. |
|
482 |
|
483 @param m the match object |
|
484 @param escmap the map of entities to encode |
|
485 @return the converted text (string) |
|
486 """ |
|
487 char = m.group() |
|
488 text = escmap.get(char) |
|
489 if text is None: |
|
490 text = "&#{0:d};".format(ord(char)) |
|
491 return text |
|
492 |
|
493 |
|
494 def html_encode(text, pattern=_escape): |
|
495 """ |
|
496 Function to correctly encode a text for html. |
|
497 |
|
498 @param text text to be encoded (string) |
|
499 @param pattern search pattern for text to be encoded (string) |
|
500 @return the encoded text (string) |
|
501 """ |
|
502 if not text: |
|
503 return "" |
|
504 text = pattern.sub(escape_entities, text) |
|
505 return text |
|
506 |
|
507 _uescape = re.compile('[\u0080-\uffff]') |
|
508 |
|
509 |
|
510 def escape_uentities(m): |
|
511 """ |
|
512 Function to encode html entities. |
|
513 |
|
514 @param m the match object |
|
515 @return the converted text (string) |
|
516 """ |
|
517 char = m.group() |
|
518 text = "&#{0:d};".format(ord(char)) |
|
519 return text |
|
520 |
|
521 |
|
522 def html_uencode(text, pattern=_uescape): |
|
523 """ |
|
524 Function to correctly encode a unicode text for html. |
|
525 |
|
526 @param text text to be encoded (string) |
|
527 @param pattern search pattern for text to be encoded (string) |
|
528 @return the encoded text (string) |
|
529 """ |
|
530 if not text: |
|
531 return "" |
|
532 text = pattern.sub(escape_uentities, text) |
|
533 return text |
|
534 |
|
535 _uunescape = re.compile(r'&#\d+;') |
|
536 |
|
537 |
|
538 def unescape_uentities(m): |
|
539 """ |
|
540 Function to decode html entities. |
|
541 |
|
542 @param m the match object |
|
543 @return the converted text (string) |
|
544 """ |
|
545 char = m.group() |
|
546 ordinal = int(char[2:-1]) |
|
547 return chr(ordinal) |
|
548 |
|
549 |
|
550 def html_udecode(text, pattern=_uunescape): |
|
551 """ |
|
552 Function to correctly decode a html text to a unicode text. |
|
553 |
|
554 @param text text to be decoded (string) |
|
555 @param pattern search pattern for text to be decoded (string) |
|
556 @return the decoded text (string) |
|
557 """ |
|
558 if not text: |
|
559 return "" |
|
560 text = pattern.sub(unescape_uentities, text) |
|
561 return text |
|
562 |
|
563 |
|
564 def convertLineEnds(text, eol): |
|
565 """ |
|
566 Function to convert the end of line characters. |
|
567 |
|
568 @param text text to be converted (string) |
|
569 @param eol new eol setting (string) |
|
570 @return text with converted eols (string) |
|
571 """ |
|
572 if eol == '\r\n': |
|
573 regexp = re.compile(r"""(\r(?!\n)|(?<!\r)\n)""") |
|
574 return regexp.sub(lambda m, eol='\r\n': eol, text) |
|
575 elif eol == '\n': |
|
576 regexp = re.compile(r"""(\r\n|\r)""") |
|
577 return regexp.sub(lambda m, eol='\n': eol, text) |
|
578 elif eol == '\r': |
|
579 regexp = re.compile(r"""(\r\n|\n)""") |
|
580 return regexp.sub(lambda m, eol='\r': eol, text) |
|
581 else: |
|
582 return text |
|
583 |
|
584 |
|
585 def linesep(): |
|
586 """ |
|
587 Function to return the line separator used by the editor. |
|
588 |
|
589 @return line separator used by the editor (string) |
|
590 """ |
|
591 eolMode = Preferences.getEditor("EOLMode") |
|
592 if eolMode == QsciScintilla.EolMode.EolUnix: |
|
593 return "\n" |
|
594 elif eolMode == QsciScintilla.EolMode.EolMac: |
|
595 return "\r" |
|
596 else: |
|
597 return "\r\n" |
|
598 |
|
599 |
|
600 def extractFlags(text): |
|
601 """ |
|
602 Function to extract eric specific flags out of the given text. |
|
603 |
|
604 Flags are contained in comments and are introduced by 'eflag:'. |
|
605 The rest of the line is interpreted as 'key = value'. value is |
|
606 analyzed for being an integer or float value. If that fails, it |
|
607 is assumed to be a string. If a key does not contain a '=' |
|
608 character, it is assumed to be a boolean flag. Flags are expected |
|
609 at the very end of a file. The search is ended, if a line without |
|
610 the 'eflag:' marker is found. |
|
611 |
|
612 @param text text to be scanned (string) |
|
613 @return dictionary of string, boolean, complex, float and int |
|
614 """ |
|
615 flags = {} |
|
616 lines = text.rstrip().splitlines() if isinstance(text, str) else text |
|
617 for line in reversed(lines): |
|
618 try: |
|
619 index = line.index("eflag:") |
|
620 except ValueError: |
|
621 # no flag found, don't look any further |
|
622 break |
|
623 |
|
624 flag = line[index + 6:].strip() |
|
625 if "=" in flag: |
|
626 key, value = flag.split("=", 1) |
|
627 key = key.strip() |
|
628 value = value.strip() |
|
629 |
|
630 if value.lower() in ["true", "false", "yes", "no", "ok"]: |
|
631 # it is a flag |
|
632 flags[key] = value.lower() in ["true", "yes", "ok"] |
|
633 continue |
|
634 |
|
635 try: |
|
636 # interpret as int first |
|
637 value = int(value) |
|
638 except ValueError: |
|
639 with contextlib.suppress(ValueError): |
|
640 # interpret as float next |
|
641 value = float(value) |
|
642 |
|
643 flags[key] = value |
|
644 else: |
|
645 # treat it as a boolean |
|
646 if flag[0] == "-": |
|
647 # false flags start with '-' |
|
648 flags[flag[1:]] = False |
|
649 else: |
|
650 flags[flag] = True |
|
651 |
|
652 return flags |
|
653 |
|
654 |
|
655 def extractFlagsFromFile(filename): |
|
656 """ |
|
657 Function to extract eric specific flags out of the given file. |
|
658 |
|
659 @param filename name of the file to be scanned (string) |
|
660 @return dictionary of string, boolean, complex, float and int |
|
661 """ |
|
662 try: |
|
663 source, encoding = readEncodedFile(filename) |
|
664 except (UnicodeError, OSError): |
|
665 return {} |
|
666 |
|
667 return extractFlags(source) |
|
668 |
|
669 |
|
670 def extractLineFlags(line, startComment="#", endComment="", flagsLine=False): |
|
671 """ |
|
672 Function to extract flags starting and ending with '__' from a line |
|
673 comment. |
|
674 |
|
675 @param line line to extract flags from (string) |
|
676 @param startComment string identifying the start of the comment (string) |
|
677 @param endComment string identifying the end of a comment (string) |
|
678 @param flagsLine flag indicating to check for a flags only line (bool) |
|
679 @return list containing the extracted flags (list of strings) |
|
680 """ |
|
681 flags = [] |
|
682 |
|
683 if not flagsLine or ( |
|
684 flagsLine and line.strip().startswith(startComment)): |
|
685 pos = line.rfind(startComment) |
|
686 if pos >= 0: |
|
687 comment = line[pos + len(startComment):].strip() |
|
688 if endComment: |
|
689 endPos = line.rfind(endComment) |
|
690 if endPos >= 0: |
|
691 comment = comment[:endPos] |
|
692 flags = [f.strip() for f in comment.split() |
|
693 if (f.startswith("__") and f.endswith("__"))] |
|
694 return flags |
|
695 |
|
696 |
|
697 def filterAnsiSequences(txt): |
|
698 """ |
|
699 Function to filter out ANSI escape sequences (color only). |
|
700 |
|
701 @param txt text to be filtered |
|
702 @type str |
|
703 @return text without ANSI escape sequences |
|
704 @rtype str |
|
705 """ |
|
706 ntxt = txt[:] |
|
707 while True: |
|
708 start = ntxt.find("\33[") # find escape character |
|
709 if start == -1: |
|
710 break |
|
711 end = ntxt.find("m", start) |
|
712 if end == -1: |
|
713 break |
|
714 ntxt = ntxt[:start] + ntxt[end + 1:] |
|
715 |
|
716 return ntxt |
|
717 |
|
718 |
|
719 def toNativeSeparators(path): |
|
720 """ |
|
721 Function returning a path, that is using native separator characters. |
|
722 |
|
723 @param path path to be converted (string) |
|
724 @return path with converted separator characters (string) |
|
725 """ |
|
726 return QDir.toNativeSeparators(path) |
|
727 |
|
728 |
|
729 def fromNativeSeparators(path): |
|
730 """ |
|
731 Function returning a path, that is using "/" separator characters. |
|
732 |
|
733 @param path path to be converted (string) |
|
734 @return path with converted separator characters (string) |
|
735 """ |
|
736 return QDir.fromNativeSeparators(path) |
|
737 |
|
738 |
|
739 def normcasepath(path): |
|
740 """ |
|
741 Function returning a path, that is normalized with respect to its case |
|
742 and references. |
|
743 |
|
744 @param path file path (string) |
|
745 @return case normalized path (string) |
|
746 """ |
|
747 return os.path.normcase(os.path.normpath(path)) |
|
748 |
|
749 |
|
750 def normcaseabspath(path): |
|
751 """ |
|
752 Function returning an absolute path, that is normalized with respect to |
|
753 its case and references. |
|
754 |
|
755 @param path file path (string) |
|
756 @return absolute, normalized path (string) |
|
757 """ |
|
758 return os.path.normcase(os.path.abspath(path)) |
|
759 |
|
760 |
|
761 def normjoinpath(a, *p): |
|
762 """ |
|
763 Function returning a normalized path of the joined parts passed into it. |
|
764 |
|
765 @param a first path to be joined (string) |
|
766 @param p variable number of path parts to be joined (string) |
|
767 @return normalized path (string) |
|
768 """ |
|
769 return os.path.normpath(os.path.join(a, *p)) |
|
770 |
|
771 |
|
772 def normabsjoinpath(a, *p): |
|
773 """ |
|
774 Function returning a normalized, absolute path of the joined parts passed |
|
775 into it. |
|
776 |
|
777 @param a first path to be joined (string) |
|
778 @param p variable number of path parts to be joind (string) |
|
779 @return absolute, normalized path (string) |
|
780 """ |
|
781 return os.path.abspath(os.path.join(a, *p)) |
|
782 |
|
783 |
|
784 def isinpath(file): |
|
785 """ |
|
786 Function to check for an executable file. |
|
787 |
|
788 @param file filename of the executable to check (string) |
|
789 @return flag to indicate, if the executable file is accessible |
|
790 via the searchpath defined by the PATH environment variable. |
|
791 """ |
|
792 if os.path.isabs(file): |
|
793 return os.access(file, os.X_OK) |
|
794 |
|
795 if os.path.exists(os.path.join(os.curdir, file)): |
|
796 return os.access(os.path.join(os.curdir, file), os.X_OK) |
|
797 |
|
798 path = getEnvironmentEntry('PATH') |
|
799 |
|
800 # environment variable not defined |
|
801 if path is None: |
|
802 return False |
|
803 |
|
804 dirs = path.split(os.pathsep) |
|
805 return any(os.access(os.path.join(directory, file), os.X_OK) |
|
806 for directory in dirs) |
|
807 |
|
808 |
|
809 def startswithPath(path, start): |
|
810 """ |
|
811 Function to check, if a path starts with a given start path. |
|
812 |
|
813 @param path path to be checked |
|
814 @type str |
|
815 @param start start path |
|
816 @type str |
|
817 @return flag indicating that the path starts with the given start |
|
818 path |
|
819 @rtype bool |
|
820 """ |
|
821 return ( |
|
822 bool(start) and |
|
823 ( |
|
824 path == start or |
|
825 normcasepath(path).startswith(normcasepath(start + "/")) |
|
826 ) |
|
827 ) |
|
828 |
|
829 |
|
830 def relativeUniversalPath(path, start): |
|
831 """ |
|
832 Function to convert a file path to a path relative to a start path |
|
833 with universal separators. |
|
834 |
|
835 @param path file or directory name to convert (string) |
|
836 @param start start path (string) |
|
837 @return relative path or unchanged path, if path does not start with |
|
838 the start path with universal separators (string) |
|
839 """ |
|
840 return fromNativeSeparators(os.path.relpath(path, start)) |
|
841 |
|
842 |
|
843 def absolutePath(path, start): |
|
844 """ |
|
845 Public method to convert a path relative to a start path to an |
|
846 absolute path. |
|
847 |
|
848 @param path file or directory name to convert (string) |
|
849 @param start start path (string) |
|
850 @return absolute path (string) |
|
851 """ |
|
852 if not os.path.isabs(path): |
|
853 path = os.path.normpath(os.path.join(start, path)) |
|
854 return path |
|
855 |
|
856 |
|
857 def absoluteUniversalPath(path, start): |
|
858 """ |
|
859 Public method to convert a path relative to a start path with |
|
860 universal separators to an absolute path. |
|
861 |
|
862 @param path file or directory name to convert (string) |
|
863 @param start start path (string) |
|
864 @return absolute path with native separators (string) |
|
865 """ |
|
866 if not os.path.isabs(path): |
|
867 path = toNativeSeparators(os.path.normpath(os.path.join(start, path))) |
|
868 return path |
|
869 |
|
870 |
|
871 def getExecutablePath(file): |
|
872 """ |
|
873 Function to build the full path of an executable file from the environment. |
|
874 |
|
875 @param file filename of the executable to check (string) |
|
876 @return full executable name, if the executable file is accessible |
|
877 via the searchpath defined by the PATH environment variable, or an |
|
878 empty string otherwise. |
|
879 """ |
|
880 if os.path.isabs(file): |
|
881 if os.access(file, os.X_OK): |
|
882 return file |
|
883 else: |
|
884 return "" |
|
885 |
|
886 cur_path = os.path.join(os.curdir, file) |
|
887 if os.path.exists(cur_path) and os.access(cur_path, os.X_OK): |
|
888 return cur_path |
|
889 |
|
890 path = os.getenv('PATH') |
|
891 |
|
892 # environment variable not defined |
|
893 if path is None: |
|
894 return "" |
|
895 |
|
896 dirs = path.split(os.pathsep) |
|
897 for directory in dirs: |
|
898 exe = os.path.join(directory, file) |
|
899 if os.access(exe, os.X_OK): |
|
900 return exe |
|
901 |
|
902 return "" |
|
903 |
|
904 |
|
905 def getExecutablePaths(file): |
|
906 """ |
|
907 Function to build all full path of an executable file from the environment. |
|
908 |
|
909 @param file filename of the executable (string) |
|
910 @return list of full executable names (list of strings), if the executable |
|
911 file is accessible via the searchpath defined by the PATH environment |
|
912 variable, or an empty list otherwise. |
|
913 """ |
|
914 paths = [] |
|
915 |
|
916 if os.path.isabs(file): |
|
917 if os.access(file, os.X_OK): |
|
918 return [file] |
|
919 else: |
|
920 return [] |
|
921 |
|
922 cur_path = os.path.join(os.curdir, file) |
|
923 if os.path.exists(cur_path) and os.access(cur_path, os.X_OK): |
|
924 paths.append(cur_path) |
|
925 |
|
926 path = os.getenv('PATH') |
|
927 |
|
928 # environment variable not defined |
|
929 if path is not None: |
|
930 dirs = path.split(os.pathsep) |
|
931 for directory in dirs: |
|
932 exe = os.path.join(directory, file) |
|
933 if os.access(exe, os.X_OK) and exe not in paths: |
|
934 paths.append(exe) |
|
935 |
|
936 return paths |
|
937 |
|
938 |
|
939 def getWindowsExecutablePath(file): |
|
940 """ |
|
941 Function to build the full path of an executable file from the environment |
|
942 on Windows platforms. |
|
943 |
|
944 First an executable with the extension .exe is searched for, thereafter |
|
945 such with the extensions .cmd or .bat and finally the given file name as |
|
946 is. The first match is returned. |
|
947 |
|
948 @param file filename of the executable to check (string) |
|
949 @return full executable name, if the executable file is accessible |
|
950 via the searchpath defined by the PATH environment variable, or an |
|
951 empty string otherwise. |
|
952 """ |
|
953 if os.path.isabs(file): |
|
954 if os.access(file, os.X_OK): |
|
955 return file |
|
956 else: |
|
957 return "" |
|
958 |
|
959 filenames = [file + ".exe", file + ".cmd", file + ".bat", file] |
|
960 |
|
961 for filename in filenames: |
|
962 cur_path = os.path.join(os.curdir, filename) |
|
963 if os.path.exists(cur_path) and os.access(cur_path, os.X_OK): |
|
964 return os.path.abspath(cur_path) |
|
965 |
|
966 path = os.getenv('PATH') |
|
967 |
|
968 # environment variable not defined |
|
969 if path is None: |
|
970 return "" |
|
971 |
|
972 dirs = path.split(os.pathsep) |
|
973 for directory in dirs: |
|
974 for filename in filenames: |
|
975 exe = os.path.join(directory, filename) |
|
976 if os.access(exe, os.X_OK): |
|
977 return exe |
|
978 |
|
979 return "" |
|
980 |
|
981 |
|
982 def isExecutable(exe): |
|
983 """ |
|
984 Function to check, if a file is executable. |
|
985 |
|
986 @param exe filename of the executable to check (string) |
|
987 @return flag indicating executable status (boolean) |
|
988 """ |
|
989 return os.access(exe, os.X_OK) |
|
990 |
|
991 |
|
992 def isDrive(path): |
|
993 """ |
|
994 Function to check, if a path is a Windows drive. |
|
995 |
|
996 @param path path name to be checked |
|
997 @type str |
|
998 @return flag indicating a Windows drive |
|
999 @rtype bool |
|
1000 """ |
|
1001 isDrive = False |
|
1002 drive, directory = os.path.splitdrive(path) |
|
1003 if ( |
|
1004 drive and |
|
1005 len(drive) == 2 and |
|
1006 drive.endswith(":") and |
|
1007 directory in ["", "\\", "/"] |
|
1008 ): |
|
1009 isDrive = True |
|
1010 |
|
1011 return isDrive |
|
1012 |
|
1013 |
|
1014 def samepath(f1, f2): |
|
1015 """ |
|
1016 Function to compare two paths. |
|
1017 |
|
1018 @param f1 first path for the compare (string) |
|
1019 @param f2 second path for the compare (string) |
|
1020 @return flag indicating whether the two paths represent the |
|
1021 same path on disk. |
|
1022 """ |
|
1023 if f1 is None or f2 is None: |
|
1024 return False |
|
1025 |
|
1026 if ( |
|
1027 normcaseabspath(os.path.realpath(f1)) == |
|
1028 normcaseabspath(os.path.realpath(f2)) |
|
1029 ): |
|
1030 return True |
|
1031 |
|
1032 return False |
|
1033 |
|
1034 |
|
1035 def samefilepath(f1, f2): |
|
1036 """ |
|
1037 Function to compare two paths. Strips the filename. |
|
1038 |
|
1039 @param f1 first filepath for the compare (string) |
|
1040 @param f2 second filepath for the compare (string) |
|
1041 @return flag indicating whether the two paths represent the |
|
1042 same path on disk. |
|
1043 """ |
|
1044 if f1 is None or f2 is None: |
|
1045 return False |
|
1046 |
|
1047 if (normcaseabspath(os.path.dirname(os.path.realpath(f1))) == |
|
1048 normcaseabspath(os.path.dirname(os.path.realpath(f2)))): |
|
1049 return True |
|
1050 |
|
1051 return False |
|
1052 |
|
1053 try: |
|
1054 EXTSEP = os.extsep |
|
1055 except AttributeError: |
|
1056 EXTSEP = "." |
|
1057 |
|
1058 |
|
1059 def splitPath(name): |
|
1060 """ |
|
1061 Function to split a pathname into a directory part and a file part. |
|
1062 |
|
1063 @param name path name (string) |
|
1064 @return a tuple of 2 strings (dirname, filename). |
|
1065 """ |
|
1066 if os.path.isdir(name): |
|
1067 dn = os.path.abspath(name) |
|
1068 fn = "." |
|
1069 else: |
|
1070 dn, fn = os.path.split(name) |
|
1071 return (dn, fn) |
|
1072 |
|
1073 |
|
1074 def joinext(prefix, ext): |
|
1075 """ |
|
1076 Function to join a file extension to a path. |
|
1077 |
|
1078 The leading "." of ext is replaced by a platform specific extension |
|
1079 separator if necessary. |
|
1080 |
|
1081 @param prefix the basepart of the filename (string) |
|
1082 @param ext the extension part (string) |
|
1083 @return the complete filename (string) |
|
1084 """ |
|
1085 if ext[0] != ".": |
|
1086 ext = ".{0}".format(ext) |
|
1087 # require leading separator to match os.path.splitext |
|
1088 return prefix + EXTSEP + ext[1:] |
|
1089 |
|
1090 |
|
1091 def compactPath(path, width, measure=len): |
|
1092 """ |
|
1093 Function to return a compacted path fitting inside the given width. |
|
1094 |
|
1095 @param path path to be compacted (string) |
|
1096 @param width width for the compacted path (integer) |
|
1097 @param measure reference to a function used to measure the length of the |
|
1098 string |
|
1099 @return compacted path (string) |
|
1100 """ |
|
1101 if measure(path) <= width: |
|
1102 return path |
|
1103 |
|
1104 ellipsis = '...' |
|
1105 |
|
1106 head, tail = os.path.split(path) |
|
1107 mid = len(head) // 2 |
|
1108 head1 = head[:mid] |
|
1109 head2 = head[mid:] |
|
1110 while head1: |
|
1111 # head1 is same size as head2 or one shorter |
|
1112 path = os.path.join("{0}{1}{2}".format(head1, ellipsis, head2), tail) |
|
1113 if measure(path) <= width: |
|
1114 return path |
|
1115 head1 = head1[:-1] |
|
1116 head2 = head2[1:] |
|
1117 path = os.path.join(ellipsis, tail) |
|
1118 if measure(path) <= width: |
|
1119 return path |
|
1120 while tail: |
|
1121 path = "{0}{1}".format(ellipsis, tail) |
|
1122 if measure(path) <= width: |
|
1123 return path |
|
1124 tail = tail[1:] |
|
1125 return "" |
|
1126 |
|
1127 |
|
1128 def direntries(path, filesonly=False, pattern=None, followsymlinks=True, |
|
1129 checkStop=None): |
|
1130 """ |
|
1131 Function returning a list of all files and directories. |
|
1132 |
|
1133 @param path root of the tree to check |
|
1134 @type str |
|
1135 @param filesonly flag indicating that only files are wanted |
|
1136 @type bool |
|
1137 @param pattern a filename pattern or list of filename patterns to check |
|
1138 against |
|
1139 @type str or list of str |
|
1140 @param followsymlinks flag indicating whether symbolic links |
|
1141 should be followed |
|
1142 @type bool |
|
1143 @param checkStop function to be called to check for a stop |
|
1144 @type function |
|
1145 @return list of all files and directories in the tree rooted |
|
1146 at path. The names are expanded to start with path. |
|
1147 @rtype list of strs |
|
1148 """ |
|
1149 patterns = pattern if isinstance(pattern, list) else [pattern] |
|
1150 files = [] if filesonly else [path] |
|
1151 try: |
|
1152 entries = os.listdir(path) |
|
1153 for entry in entries: |
|
1154 if checkStop and checkStop(): |
|
1155 break |
|
1156 |
|
1157 if entry in ['.svn', |
|
1158 '.hg', |
|
1159 '.git', |
|
1160 '.ropeproject', |
|
1161 '.eric7project', |
|
1162 '.jedi']: |
|
1163 continue |
|
1164 |
|
1165 fentry = os.path.join(path, entry) |
|
1166 if ( |
|
1167 pattern and |
|
1168 not os.path.isdir(fentry) and |
|
1169 not any(fnmatch.fnmatch(entry, p) for p in patterns) |
|
1170 ): |
|
1171 # entry doesn't fit the given pattern |
|
1172 continue |
|
1173 |
|
1174 if os.path.isdir(fentry): |
|
1175 if os.path.islink(fentry) and not followsymlinks: |
|
1176 continue |
|
1177 files += direntries( |
|
1178 fentry, filesonly, pattern, followsymlinks, checkStop) |
|
1179 else: |
|
1180 files.append(fentry) |
|
1181 except OSError: |
|
1182 pass |
|
1183 except UnicodeDecodeError: |
|
1184 pass |
|
1185 return files |
|
1186 |
|
1187 |
|
1188 def getDirs(path, excludeDirs): |
|
1189 """ |
|
1190 Function returning a list of all directories below path. |
|
1191 |
|
1192 @param path root of the tree to check |
|
1193 @param excludeDirs basename of directories to ignore |
|
1194 @return list of all directories found |
|
1195 """ |
|
1196 try: |
|
1197 names = os.listdir(path) |
|
1198 except OSError: |
|
1199 return [] |
|
1200 |
|
1201 dirs = [] |
|
1202 for name in names: |
|
1203 if ( |
|
1204 os.path.isdir(os.path.join(path, name)) and |
|
1205 not os.path.islink(os.path.join(path, name)) |
|
1206 ): |
|
1207 exclude = 0 |
|
1208 for e in excludeDirs: |
|
1209 if name.split(os.sep, 1)[0] == e: |
|
1210 exclude = 1 |
|
1211 break |
|
1212 if not exclude: |
|
1213 dirs.append(os.path.join(path, name)) |
|
1214 |
|
1215 for name in dirs[:]: |
|
1216 if not os.path.islink(name): |
|
1217 dirs += getDirs(name, excludeDirs) |
|
1218 |
|
1219 return dirs |
|
1220 |
|
1221 |
|
1222 def findVolume(volumeName, findAll=False): |
|
1223 """ |
|
1224 Function to find the directory belonging to a given volume name. |
|
1225 |
|
1226 @param volumeName name of the volume to search for |
|
1227 @type str |
|
1228 @param findAll flag indicating to get the directories for all volumes |
|
1229 starting with the given name (defaults to False) |
|
1230 @type bool (optional) |
|
1231 @return directory path or list of directory paths for the given volume |
|
1232 name |
|
1233 @rtype str or list of str |
|
1234 """ |
|
1235 volumeDirectories = [] |
|
1236 volumeDirectory = None |
|
1237 |
|
1238 if isWindowsPlatform(): |
|
1239 # we are on a Windows platform |
|
1240 def getVolumeName(diskName): |
|
1241 """ |
|
1242 Local function to determine the volume of a disk or device. |
|
1243 |
|
1244 Each disk or external device connected to windows has an |
|
1245 attribute called "volume name". This function returns the |
|
1246 volume name for the given disk/device. |
|
1247 |
|
1248 Code from http://stackoverflow.com/a/12056414 |
|
1249 """ |
|
1250 volumeNameBuffer = ctypes.create_unicode_buffer(1024) |
|
1251 ctypes.windll.kernel32.GetVolumeInformationW( |
|
1252 ctypes.c_wchar_p(diskName), volumeNameBuffer, |
|
1253 ctypes.sizeof(volumeNameBuffer), None, None, None, None, 0) |
|
1254 return volumeNameBuffer.value |
|
1255 |
|
1256 # |
|
1257 # In certain circumstances, volumes are allocated to USB |
|
1258 # storage devices which cause a Windows popup to raise if their |
|
1259 # volume contains no media. Wrapping the check in SetErrorMode |
|
1260 # with SEM_FAILCRITICALERRORS (1) prevents this popup. |
|
1261 # |
|
1262 oldMode = ctypes.windll.kernel32.SetErrorMode(1) |
|
1263 try: |
|
1264 for disk in "ABCDEFGHIJKLMNOPQRSTUVWXYZ": |
|
1265 dirpath = "{0}:\\".format(disk) |
|
1266 if os.path.exists(dirpath): |
|
1267 if findAll: |
|
1268 if getVolumeName(dirpath).startswith(volumeName): |
|
1269 volumeDirectories.append(dirpath) |
|
1270 else: |
|
1271 if getVolumeName(dirpath) == volumeName: |
|
1272 volumeDirectory = dirpath |
|
1273 break |
|
1274 finally: |
|
1275 ctypes.windll.kernel32.SetErrorMode(oldMode) |
|
1276 else: |
|
1277 # we are on a Linux or macOS platform |
|
1278 for mountCommand in ["mount", "/sbin/mount", "/usr/sbin/mount"]: |
|
1279 with contextlib.suppress(FileNotFoundError): |
|
1280 mountOutput = subprocess.run( # secok |
|
1281 mountCommand, check=True, capture_output=True, text=True |
|
1282 ).stdout.splitlines() |
|
1283 mountedVolumes = [ |
|
1284 x.split(" type")[0].split(maxsplit=2)[2] |
|
1285 for x in mountOutput |
|
1286 ] |
|
1287 if findAll: |
|
1288 for volume in mountedVolumes: |
|
1289 if volumeName in volume: |
|
1290 volumeDirectories.append(volume) |
|
1291 if volumeDirectories: |
|
1292 break |
|
1293 else: |
|
1294 for volume in mountedVolumes: |
|
1295 if volume.endswith(volumeName): |
|
1296 volumeDirectory = volume |
|
1297 break |
|
1298 if volumeDirectory: |
|
1299 break |
|
1300 |
|
1301 if findAll: |
|
1302 return volumeDirectories |
|
1303 else: |
|
1304 return volumeDirectory |
|
1305 |
|
1306 |
|
1307 def getTestFileNames(fn): |
|
1308 """ |
|
1309 Function to build the potential file names of a test file. |
|
1310 |
|
1311 The file names for the test file is built by prepending the string |
|
1312 "test" and "test_" to the file name passed into this function and |
|
1313 by appending the string "_test". |
|
1314 |
|
1315 @param fn file name basis to be used for the test file names |
|
1316 @type str |
|
1317 @return file names of the corresponding test file |
|
1318 @rtype list of str |
|
1319 """ |
|
1320 dn, fn = os.path.split(fn) |
|
1321 fn, ext = os.path.splitext(fn) |
|
1322 prefixes = ["test", "test_"] |
|
1323 postfixes = ["_test"] |
|
1324 return [ |
|
1325 os.path.join(dn, "{0}{1}{2}".format(prefix, fn, ext)) |
|
1326 for prefix in prefixes |
|
1327 ] + [ |
|
1328 os.path.join(dn, "{0}{1}{2}".format(fn, postfix, ext)) |
|
1329 for postfix in postfixes |
|
1330 ] |
|
1331 |
|
1332 |
|
1333 def getCoverageFileNames(fn): |
|
1334 """ |
|
1335 Function to build a list of coverage data file names. |
|
1336 |
|
1337 @param fn file name basis to be used for the coverage data file |
|
1338 @type str |
|
1339 @return list of existing coverage data files |
|
1340 @rtype list of str |
|
1341 """ |
|
1342 files = [] |
|
1343 for filename in [fn, os.path.dirname(fn) + os.sep] + getTestFileNames(fn): |
|
1344 f = getCoverageFileName(filename) |
|
1345 if f: |
|
1346 files.append(f) |
|
1347 return files |
|
1348 |
|
1349 |
|
1350 def getCoverageFileName(fn, mustExist=True): |
|
1351 """ |
|
1352 Function to build a file name for a coverage data file. |
|
1353 |
|
1354 @param fn file name basis to be used for the coverage data file name |
|
1355 @type str |
|
1356 @param mustExist flag indicating to check that the file exists (defaults |
|
1357 to True) |
|
1358 @type bool (optional) |
|
1359 @return coverage data file name |
|
1360 @rtype str |
|
1361 """ |
|
1362 basename = os.path.splitext(fn)[0] |
|
1363 filename = "{0}.coverage".format(basename) |
|
1364 if mustExist: |
|
1365 if os.path.isfile(filename): |
|
1366 return filename |
|
1367 else: |
|
1368 return "" |
|
1369 else: |
|
1370 return filename |
|
1371 |
|
1372 |
|
1373 def getProfileFileNames(fn): |
|
1374 """ |
|
1375 Function to build a list of profile data file names. |
|
1376 |
|
1377 @param fn file name basis to be used for the profile data file |
|
1378 @type str |
|
1379 @return list of existing profile data files |
|
1380 @rtype list of str |
|
1381 """ |
|
1382 files = [] |
|
1383 for filename in [fn, os.path.dirname(fn) + os.sep] + getTestFileNames(fn): |
|
1384 f = getProfileFileName(filename) |
|
1385 if f: |
|
1386 files.append(f) |
|
1387 return files |
|
1388 |
|
1389 |
|
1390 def getProfileFileName(fn, mustExist=True): |
|
1391 """ |
|
1392 Function to build a file name for a profile data file. |
|
1393 |
|
1394 @param fn file name basis to be used for the profile data file name |
|
1395 @type str |
|
1396 @param mustExist flag indicating to check that the file exists (defaults |
|
1397 to True) |
|
1398 @type bool (optional) |
|
1399 @return profile data file name |
|
1400 @rtype str |
|
1401 """ |
|
1402 basename = os.path.splitext(fn)[0] |
|
1403 filename = "{0}.profile".format(basename) |
|
1404 if mustExist: |
|
1405 if os.path.isfile(filename): |
|
1406 return filename |
|
1407 else: |
|
1408 return "" |
|
1409 else: |
|
1410 return filename |
|
1411 |
|
1412 |
|
1413 def parseOptionString(s): |
|
1414 """ |
|
1415 Function used to convert an option string into a list of options. |
|
1416 |
|
1417 @param s option string |
|
1418 @type str |
|
1419 @return list of options |
|
1420 @rtype list of str |
|
1421 """ |
|
1422 s = re.sub(r"%[A-Z%]", _percentReplacementFunc, s) |
|
1423 return shlex.split(s) |
|
1424 |
|
1425 |
|
1426 def _percentReplacementFunc(matchobj): |
|
1427 """ |
|
1428 Protected function called for replacing % codes. |
|
1429 |
|
1430 @param matchobj match object for the code |
|
1431 @type re.Match |
|
1432 @return replacement string |
|
1433 @rtype str |
|
1434 """ |
|
1435 return getPercentReplacement(matchobj.group(0)) |
|
1436 |
|
1437 |
|
1438 def getPercentReplacement(code): |
|
1439 """ |
|
1440 Function to get the replacement for code. |
|
1441 |
|
1442 @param code code indicator |
|
1443 @type str |
|
1444 @return replacement string |
|
1445 @rtype str |
|
1446 """ |
|
1447 if code in ["C", "%C"]: |
|
1448 # column of the cursor of the current editor |
|
1449 aw = ericApp().getObject("ViewManager").activeWindow() |
|
1450 if aw is None: |
|
1451 column = -1 |
|
1452 else: |
|
1453 column = aw.getCursorPosition()[1] |
|
1454 return "{0:d}".format(column) |
|
1455 elif code in ["D", "%D"]: |
|
1456 # directory of active editor |
|
1457 aw = ericApp().getObject("ViewManager").activeWindow() |
|
1458 if aw is None: |
|
1459 dn = "not_available" |
|
1460 else: |
|
1461 fn = aw.getFileName() |
|
1462 if fn is None: |
|
1463 dn = "not_available" |
|
1464 else: |
|
1465 dn = os.path.dirname(fn) |
|
1466 return dn |
|
1467 elif code in ["F", "%F"]: |
|
1468 # filename (complete) of active editor |
|
1469 aw = ericApp().getObject("ViewManager").activeWindow() |
|
1470 if aw is None: |
|
1471 fn = "not_available" |
|
1472 else: |
|
1473 fn = aw.getFileName() |
|
1474 if fn is None: |
|
1475 fn = "not_available" |
|
1476 return fn |
|
1477 elif code in ["H", "%H"]: |
|
1478 # home directory |
|
1479 return getHomeDir() |
|
1480 elif code in ["L", "%L"]: |
|
1481 # line of the cursor of the current editor |
|
1482 aw = ericApp().getObject("ViewManager").activeWindow() |
|
1483 if aw is None: |
|
1484 line = 0 |
|
1485 else: |
|
1486 line = aw.getCursorPosition()[0] + 1 |
|
1487 return "{0:d}".format(line) |
|
1488 elif code in ["P", "%P"]: |
|
1489 # project path |
|
1490 projectPath = ericApp().getObject("Project").getProjectPath() |
|
1491 if not projectPath: |
|
1492 projectPath = "not_available" |
|
1493 return projectPath |
|
1494 elif code in ["S", "%S"]: |
|
1495 # selected text of the current editor |
|
1496 aw = ericApp().getObject("ViewManager").activeWindow() |
|
1497 if aw is None: |
|
1498 text = "not_available" |
|
1499 else: |
|
1500 text = aw.selectedText() |
|
1501 return text |
|
1502 elif code in ["U", "%U"]: |
|
1503 # username |
|
1504 un = getUserName() |
|
1505 if un is None: |
|
1506 return code |
|
1507 else: |
|
1508 return un |
|
1509 elif code in ["%", "%%"]: |
|
1510 # the percent sign |
|
1511 return "%" |
|
1512 else: |
|
1513 # unknown code, just return it |
|
1514 return code |
|
1515 |
|
1516 |
|
1517 def getPercentReplacementHelp(): |
|
1518 """ |
|
1519 Function to get the help text for the supported %-codes. |
|
1520 |
|
1521 @returns help text (string) |
|
1522 """ |
|
1523 return QCoreApplication.translate( |
|
1524 "Utilities", |
|
1525 """<p>You may use %-codes as placeholders in the string.""" |
|
1526 """ Supported codes are:""" |
|
1527 """<table>""" |
|
1528 """<tr><td>%C</td><td>column of the cursor of the current editor""" |
|
1529 """</td></tr>""" |
|
1530 """<tr><td>%D</td><td>directory of the current editor</td></tr>""" |
|
1531 """<tr><td>%F</td><td>filename of the current editor</td></tr>""" |
|
1532 """<tr><td>%H</td><td>home directory of the current user</td></tr>""" |
|
1533 """<tr><td>%L</td><td>line of the cursor of the current editor""" |
|
1534 """</td></tr>""" |
|
1535 """<tr><td>%P</td><td>path of the current project</td></tr>""" |
|
1536 """<tr><td>%S</td><td>selected text of the current editor</td></tr>""" |
|
1537 """<tr><td>%U</td><td>username of the current user</td></tr>""" |
|
1538 """<tr><td>%%</td><td>the percent sign</td></tr>""" |
|
1539 """</table>""" |
|
1540 """</p>""") |
|
1541 |
|
1542 |
|
1543 def getUserName(): |
|
1544 """ |
|
1545 Function to get the user name. |
|
1546 |
|
1547 @return user name (string) |
|
1548 """ |
|
1549 user = getpass.getuser() |
|
1550 |
|
1551 if isWindowsPlatform() and not user: |
|
1552 return win32_GetUserName() |
|
1553 |
|
1554 return user |
|
1555 |
|
1556 |
|
1557 def getRealName(): |
|
1558 """ |
|
1559 Function to get the real name of the user. |
|
1560 |
|
1561 @return real name of the user (string) |
|
1562 """ |
|
1563 if isWindowsPlatform(): |
|
1564 return win32_getRealName() |
|
1565 else: |
|
1566 import pwd |
|
1567 user = getpass.getuser() |
|
1568 return pwd.getpwnam(user).pw_gecos |
|
1569 |
|
1570 |
|
1571 def getHomeDir(): |
|
1572 """ |
|
1573 Function to get a users home directory. |
|
1574 |
|
1575 @return home directory (string) |
|
1576 """ |
|
1577 return QDir.homePath() |
|
1578 |
|
1579 |
|
1580 def getPythonLibPath(): |
|
1581 """ |
|
1582 Function to determine the path to Python's library. |
|
1583 |
|
1584 @return path to the Python library (string) |
|
1585 """ |
|
1586 pyFullVers = sys.version.split()[0] |
|
1587 |
|
1588 vl = re.findall("[0-9.]*", pyFullVers)[0].split(".") |
|
1589 major = vl[0] |
|
1590 minor = vl[1] |
|
1591 |
|
1592 pyVers = major + "." + minor |
|
1593 |
|
1594 if isWindowsPlatform(): |
|
1595 libDir = sys.prefix + "\\Lib" |
|
1596 else: |
|
1597 try: |
|
1598 syslib = sys.lib |
|
1599 except AttributeError: |
|
1600 syslib = "lib" |
|
1601 libDir = sys.prefix + "/" + syslib + "/python" + pyVers |
|
1602 |
|
1603 return libDir |
|
1604 |
|
1605 |
|
1606 def getPythonVersion(): |
|
1607 """ |
|
1608 Function to get the Python version (major, minor) as an integer value. |
|
1609 |
|
1610 @return An integer representing major and minor version number (integer) |
|
1611 """ |
|
1612 return sys.hexversion >> 16 |
|
1613 |
|
1614 |
|
1615 def determinePythonVersion(filename, source, editor=None): |
|
1616 """ |
|
1617 Function to determine the python version of a given file. |
|
1618 |
|
1619 @param filename name of the file with extension (str) |
|
1620 @param source of the file (str) |
|
1621 @param editor reference to the editor, if the file is opened |
|
1622 already (Editor object) |
|
1623 @return Python version if file is Python3 (int) |
|
1624 """ |
|
1625 pyAssignment = { |
|
1626 "Python3": 3, |
|
1627 "MicroPython": 3, |
|
1628 "Cython": 3, |
|
1629 } |
|
1630 |
|
1631 if not editor: |
|
1632 viewManager = ericApp().getObject('ViewManager') |
|
1633 editor = viewManager.getOpenEditor(filename) |
|
1634 |
|
1635 # Maybe the user has changed the language |
|
1636 if editor and editor.getFileType() in pyAssignment: |
|
1637 return pyAssignment[editor.getFileType()] |
|
1638 |
|
1639 pyVer = 0 |
|
1640 if filename: |
|
1641 if not source: |
|
1642 source = readEncodedFile(filename)[0] |
|
1643 flags = extractFlags(source) |
|
1644 ext = os.path.splitext(filename)[1] |
|
1645 py3Ext = Preferences.getPython("Python3Extensions") |
|
1646 project = ericApp().getObject('Project') |
|
1647 basename = os.path.basename(filename) |
|
1648 |
|
1649 if "FileType" in flags: |
|
1650 pyVer = pyAssignment.get(flags["FileType"], 0) |
|
1651 elif project.isOpen() and project.isProjectFile(filename): |
|
1652 language = project.getEditorLexerAssoc(basename) |
|
1653 if not language: |
|
1654 language = Preferences.getEditorLexerAssoc(basename) |
|
1655 if language == 'Python3': |
|
1656 pyVer = pyAssignment[language] |
|
1657 |
|
1658 if pyVer: |
|
1659 # Skip the next tests |
|
1660 pass |
|
1661 elif (Preferences.getProject("DeterminePyFromProject") and |
|
1662 project.isOpen() and |
|
1663 project.isProjectFile(filename) and |
|
1664 ext in py3Ext): |
|
1665 pyVer = pyAssignment.get(project.getProjectLanguage(), 0) |
|
1666 elif ext in py3Ext: |
|
1667 pyVer = 3 |
|
1668 elif source: |
|
1669 if isinstance(source, str): |
|
1670 line0 = source.splitlines()[0] |
|
1671 else: |
|
1672 line0 = source[0] |
|
1673 if ( |
|
1674 line0.startswith("#!") and |
|
1675 (("python3" in line0) or ("python" in line0)) |
|
1676 ): |
|
1677 pyVer = 3 |
|
1678 |
|
1679 if pyVer == 0 and ext in py3Ext: |
|
1680 pyVer = 3 |
|
1681 |
|
1682 return pyVer |
|
1683 |
|
1684 |
|
1685 def rxIndex(rx, txt): |
|
1686 """ |
|
1687 Function to get the index (start position) of a regular expression match |
|
1688 within some text. |
|
1689 |
|
1690 @param rx regular expression object as created by re.compile() |
|
1691 @type re.Pattern |
|
1692 @param txt text to be scanned |
|
1693 @type str |
|
1694 @return start position of the match or -1 indicating no match was found |
|
1695 @rtype int |
|
1696 """ |
|
1697 match = rx.search(txt) |
|
1698 if match is None: |
|
1699 return -1 |
|
1700 else: |
|
1701 return match.start() |
|
1702 |
|
1703 |
|
1704 ############################################################################### |
|
1705 ## functions for environment handling |
|
1706 ############################################################################### |
|
1707 |
|
1708 |
|
1709 def getEnvironmentEntry(key, default=None): |
|
1710 """ |
|
1711 Module function to get an environment entry. |
|
1712 |
|
1713 @param key key of the requested environment entry (string) |
|
1714 @param default value to be returned, if the environment doesn't contain |
|
1715 the requested entry (string) |
|
1716 @return the requested entry or the default value, if the entry wasn't |
|
1717 found (string or None) |
|
1718 """ |
|
1719 pattern = "^{0}[ \t]*=".format(key) |
|
1720 filterRe = ( |
|
1721 re.compile(pattern, re.IGNORECASE) |
|
1722 if isWindowsPlatform() else |
|
1723 re.compile(pattern) |
|
1724 ) |
|
1725 |
|
1726 entries = [e for e in QProcess.systemEnvironment() |
|
1727 if filterRe.search(e) is not None] |
|
1728 if not entries: |
|
1729 return default |
|
1730 |
|
1731 # if there are multiple entries, just consider the first one |
|
1732 ename, value = entries[0].split("=", 1) |
|
1733 return value.strip() |
|
1734 |
|
1735 |
|
1736 def hasEnvironmentEntry(key): |
|
1737 """ |
|
1738 Module function to check, if the environment contains an entry. |
|
1739 |
|
1740 @param key key of the requested environment entry |
|
1741 @type str |
|
1742 @return flag indicating the presence of the requested entry |
|
1743 @rtype bool |
|
1744 """ |
|
1745 pattern = "^{0}[ \t]*=".format(key) |
|
1746 filterRe = ( |
|
1747 re.compile(pattern, re.IGNORECASE) |
|
1748 if isWindowsPlatform() else |
|
1749 re.compile(pattern) |
|
1750 ) |
|
1751 |
|
1752 entries = [e for e in QProcess.systemEnvironment() |
|
1753 if filterRe.search(e) is not None] |
|
1754 return len(entries) > 0 |
|
1755 |
|
1756 ############################################################################### |
|
1757 ## Qt utility functions below |
|
1758 ############################################################################### |
|
1759 |
|
1760 |
|
1761 def generateQtToolName(toolname): |
|
1762 """ |
|
1763 Module function to generate the executable name for a Qt tool like |
|
1764 designer. |
|
1765 |
|
1766 @param toolname base name of the tool (string) |
|
1767 @return the Qt tool name without extension (string) |
|
1768 """ |
|
1769 return "{0}{1}{2}".format(Preferences.getQt("QtToolsPrefix"), |
|
1770 toolname, |
|
1771 Preferences.getQt("QtToolsPostfix") |
|
1772 ) |
|
1773 |
|
1774 |
|
1775 def getQtMacBundle(toolname): |
|
1776 """ |
|
1777 Module function to determine the correct Mac OS X bundle name for Qt tools. |
|
1778 |
|
1779 @param toolname plain name of the tool (e.g. "designer") (string) |
|
1780 @return bundle name of the Qt tool (string) |
|
1781 """ |
|
1782 qtDir = getQtBinariesPath() |
|
1783 bundles = [ |
|
1784 os.path.join( |
|
1785 qtDir, 'bin', generateQtToolName(toolname.capitalize())) + ".app", |
|
1786 os.path.join(qtDir, 'bin', generateQtToolName(toolname)) + ".app", |
|
1787 os.path.join( |
|
1788 qtDir, generateQtToolName(toolname.capitalize())) + ".app", |
|
1789 os.path.join(qtDir, generateQtToolName(toolname)) + ".app", |
|
1790 ] |
|
1791 if toolname == "designer": |
|
1792 # support the standalone Qt Designer installer from |
|
1793 # https://build-system.fman.io/qt-designer-download |
|
1794 designer = "Qt Designer.app" |
|
1795 bundles.extend([ |
|
1796 os.path.join(qtDir, 'bin', designer), |
|
1797 os.path.join(qtDir, designer), |
|
1798 ]) |
|
1799 for bundle in bundles: |
|
1800 if os.path.exists(bundle): |
|
1801 return bundle |
|
1802 return "" |
|
1803 |
|
1804 |
|
1805 def prepareQtMacBundle(toolname, args): |
|
1806 """ |
|
1807 Module function for starting Qt tools that are Mac OS X bundles. |
|
1808 |
|
1809 @param toolname plain name of the tool (e.g. "designer") |
|
1810 @type str |
|
1811 @param args name of input file for tool, if any |
|
1812 @type list of str |
|
1813 @return command-name and args for QProcess |
|
1814 @rtype tuple of (str, list of str) |
|
1815 """ |
|
1816 fullBundle = getQtMacBundle(toolname) |
|
1817 if fullBundle == "": |
|
1818 return ("", []) |
|
1819 |
|
1820 newArgs = [] |
|
1821 newArgs.append("-a") |
|
1822 newArgs.append(fullBundle) |
|
1823 if args: |
|
1824 newArgs.append("--args") |
|
1825 newArgs += args |
|
1826 |
|
1827 return ("open", newArgs) |
|
1828 |
|
1829 ############################################################################### |
|
1830 ## PyQt utility functions below |
|
1831 ############################################################################### |
|
1832 |
|
1833 |
|
1834 def generatePyQtToolPath(toolname, alternatives=None): |
|
1835 """ |
|
1836 Module function to generate the executable path for a PyQt tool. |
|
1837 |
|
1838 @param toolname base name of the tool |
|
1839 @type str |
|
1840 @param alternatives list of alternative tool names to try |
|
1841 @type list of str |
|
1842 @return executable path name of the tool |
|
1843 @rtype str |
|
1844 """ |
|
1845 pyqtVariant = int(toolname[-1]) |
|
1846 pyqtToolsPath = getPyQtToolsPath(pyqtVariant) |
|
1847 if pyqtToolsPath: |
|
1848 exe = os.path.join(pyqtToolsPath, toolname) |
|
1849 if isWindowsPlatform(): |
|
1850 exe += ".exe" |
|
1851 else: |
|
1852 if isWindowsPlatform(): |
|
1853 exe = getWindowsExecutablePath(toolname) |
|
1854 else: |
|
1855 exe = toolname |
|
1856 |
|
1857 if not isinpath(exe) and alternatives: |
|
1858 ex_ = generatePyQtToolPath(alternatives[0], alternatives[1:]) |
|
1859 if isinpath(ex_): |
|
1860 exe = ex_ |
|
1861 |
|
1862 return exe |
|
1863 |
|
1864 ############################################################################### |
|
1865 ## PySide2/PySide6 utility functions below |
|
1866 ############################################################################### |
|
1867 |
|
1868 |
|
1869 def generatePySideToolPath(toolname, variant=2): |
|
1870 """ |
|
1871 Module function to generate the executable path for a PySide2/PySide6 tool. |
|
1872 |
|
1873 @param toolname base name of the tool |
|
1874 @type str |
|
1875 @param variant indicator for the PySide variant |
|
1876 @type int or str |
|
1877 @return the PySide2/PySide6 tool path with extension |
|
1878 @rtype str |
|
1879 """ |
|
1880 if isWindowsPlatform(): |
|
1881 hasPyside = checkPyside(variant) |
|
1882 if not hasPyside: |
|
1883 return "" |
|
1884 |
|
1885 venvName = Preferences.getQt("PySide{0}VenvName".format(variant)) |
|
1886 if not venvName: |
|
1887 venvName = Preferences.getDebugger("Python3VirtualEnv") |
|
1888 interpreter = ericApp().getObject( |
|
1889 "VirtualEnvManager").getVirtualenvInterpreter(venvName) |
|
1890 if interpreter == "" or not isinpath(interpreter): |
|
1891 interpreter = getPythonExecutable() |
|
1892 prefix = os.path.dirname(interpreter) |
|
1893 if not prefix.endswith("Scripts"): |
|
1894 prefix = os.path.join(prefix, "Scripts") |
|
1895 return os.path.join(prefix, toolname + '.exe') |
|
1896 else: |
|
1897 # step 1: check, if the user has configured a tools path |
|
1898 path = Preferences.getQt("PySide{0}ToolsDir".format(variant)) |
|
1899 if path: |
|
1900 return os.path.join(path, toolname) |
|
1901 |
|
1902 # step 2: determine from used Python interpreter |
|
1903 dirName = os.path.dirname(sys.executable) |
|
1904 if os.path.exists(os.path.join(dirName, toolname)): |
|
1905 return os.path.join(dirName, toolname) |
|
1906 |
|
1907 return toolname |
|
1908 |
|
1909 |
|
1910 @functools.lru_cache() |
|
1911 def checkPyside(variant=2): |
|
1912 """ |
|
1913 Module function to check the presence of PySide2/PySide6. |
|
1914 |
|
1915 @param variant indicator for the PySide variant |
|
1916 @type int or str |
|
1917 @return flags indicating the presence of PySide2/PySide6 |
|
1918 @rtype bool |
|
1919 """ |
|
1920 venvName = Preferences.getQt("PySide{0}VenvName".format(variant)) |
|
1921 if not venvName: |
|
1922 venvName = Preferences.getDebugger("Python3VirtualEnv") |
|
1923 interpreter = ericApp().getObject( |
|
1924 "VirtualEnvManager").getVirtualenvInterpreter(venvName) |
|
1925 if interpreter == "" or not isinpath(interpreter): |
|
1926 interpreter = getPythonExecutable() |
|
1927 |
|
1928 checker = os.path.join( |
|
1929 getConfig('ericDir'), "Utilities", "PySideImporter.py") |
|
1930 args = [checker, "--variant={0}".format(variant)] |
|
1931 proc = QProcess() |
|
1932 proc.setProcessChannelMode(QProcess.ProcessChannelMode.MergedChannels) |
|
1933 proc.start(interpreter, args) |
|
1934 finished = proc.waitForFinished(30000) |
|
1935 return finished and proc.exitCode() == 0 |
|
1936 |
|
1937 ############################################################################### |
|
1938 ## Other utility functions below |
|
1939 ############################################################################### |
|
1940 |
|
1941 |
|
1942 def generateVersionInfo(linesep='\n'): |
|
1943 """ |
|
1944 Module function to generate a string with various version infos. |
|
1945 |
|
1946 @param linesep string to be used to separate lines |
|
1947 @type str |
|
1948 @return string with version infos |
|
1949 @rtype str |
|
1950 """ |
|
1951 try: |
|
1952 try: |
|
1953 from PyQt6 import sip |
|
1954 except ImportError: |
|
1955 import sip |
|
1956 sip_version_str = sip.SIP_VERSION_STR |
|
1957 except (ImportError, AttributeError): |
|
1958 sip_version_str = "sip version not available" |
|
1959 |
|
1960 sizeStr = "64-Bit" if sys.maxsize > 2**32 else "32-Bit" |
|
1961 |
|
1962 info = ["Version Numbers:"] |
|
1963 |
|
1964 info.append(" Python {0}, {1}".format(sys.version.split()[0], sizeStr)) |
|
1965 info.append(" Qt {0}".format(qVersion())) |
|
1966 info.append(" PyQt6 {0}".format(PYQT_VERSION_STR)) |
|
1967 try: |
|
1968 from PyQt6 import QtCharts |
|
1969 info.append(" PyQt6-Charts {0}".format( |
|
1970 QtCharts.PYQT_CHART_VERSION_STR)) |
|
1971 except (ImportError, AttributeError): |
|
1972 info.append(" PyQt6-Charts not installed") |
|
1973 try: |
|
1974 from PyQt6 import QtWebEngineCore |
|
1975 info.append(" PyQt6-WebEngine {0}".format( |
|
1976 QtWebEngineCore.PYQT_WEBENGINE_VERSION_STR)) |
|
1977 except (ImportError, AttributeError): |
|
1978 info.append(" PyQt6-WebEngine not installed") |
|
1979 info.append(" PyQt6-QScintilla {0}".format(QSCINTILLA_VERSION_STR)) |
|
1980 info.append(" sip {0}".format(sip_version_str)) |
|
1981 with contextlib.suppress(ImportError): |
|
1982 from PyQt6 import QtWebEngineWidgets # __IGNORE_WARNING__ |
|
1983 from WebBrowser.Tools import WebBrowserTools |
|
1984 chromiumVersion, chromiumSecurityVersion = ( |
|
1985 WebBrowserTools.getWebEngineVersions()[0:2] |
|
1986 ) |
|
1987 info.append(" WebEngine {0}".format(chromiumVersion)) |
|
1988 if chromiumSecurityVersion: |
|
1989 info.append(" (Security) {0}".format(chromiumSecurityVersion)) |
|
1990 info.append(" {0} {1}".format(Program, Version)) |
|
1991 info.append("") |
|
1992 info.append("Platform: {0}".format(sys.platform)) |
|
1993 info.append(sys.version) |
|
1994 desktop = desktopName() |
|
1995 if desktop: |
|
1996 info.append("") |
|
1997 info.append("Desktop: {0}".format(desktop)) |
|
1998 session = sessionType() |
|
1999 if session: |
|
2000 info.append("") |
|
2001 info.append("Session Type: {0}".format(session)) |
|
2002 |
|
2003 return linesep.join(info) |
|
2004 |
|
2005 |
|
2006 def generatePluginsVersionInfo(linesep='\n'): |
|
2007 """ |
|
2008 Module function to generate a string with plugins version infos. |
|
2009 |
|
2010 @param linesep string to be used to separate lines |
|
2011 @type str |
|
2012 @return string with plugins version infos |
|
2013 @rtype str |
|
2014 """ |
|
2015 info = [] |
|
2016 app = ericApp() |
|
2017 if app is not None: |
|
2018 with contextlib.suppress(KeyError): |
|
2019 pm = app.getObject("PluginManager") |
|
2020 versions = {} |
|
2021 for pinfo in pm.getPluginInfos(): |
|
2022 versions[pinfo["module_name"]] = pinfo["version"] |
|
2023 |
|
2024 info.append("Plugins Version Numbers:") |
|
2025 for pluginModuleName in sorted(versions.keys()): |
|
2026 info.append(" {0} {1}".format( |
|
2027 pluginModuleName, versions[pluginModuleName])) |
|
2028 |
|
2029 return linesep.join(info) |
|
2030 |
|
2031 |
|
2032 def generateDistroInfo(linesep='\n'): |
|
2033 """ |
|
2034 Module function to generate a string with distribution infos. |
|
2035 |
|
2036 @param linesep string to be used to separate lines |
|
2037 @type str |
|
2038 @return string with distribution infos |
|
2039 @rtype str |
|
2040 """ |
|
2041 info = [] |
|
2042 if isLinuxPlatform(): |
|
2043 releaseList = glob.glob("/etc/*-release") |
|
2044 if releaseList: |
|
2045 info.append("Distribution Info:") |
|
2046 for rfile in releaseList: |
|
2047 try: |
|
2048 with open(rfile, "r") as f: |
|
2049 lines = f.read().splitlines() |
|
2050 except OSError: |
|
2051 continue |
|
2052 |
|
2053 info.append(' {0}'.format(rfile)) |
|
2054 info.extend([' {0}'.format(line) for line in lines]) |
|
2055 info.append("") |
|
2056 |
|
2057 return linesep.join(info) |
|
2058 |
|
2059 |
|
2060 def toBool(dataStr): |
|
2061 """ |
|
2062 Module function to convert a string to a boolean value. |
|
2063 |
|
2064 @param dataStr string to be converted (string) |
|
2065 @return converted boolean value (boolean) |
|
2066 """ |
|
2067 if dataStr in ["True", "true", "1", "Yes", "yes"]: |
|
2068 return True |
|
2069 elif dataStr in ["False", "false", "0", "No", "no"]: |
|
2070 return False |
|
2071 else: |
|
2072 return bool(dataStr) |
|
2073 |
|
2074 |
|
2075 def getSysPath(interpreter): |
|
2076 """ |
|
2077 Module function to get the Python path (sys.path) of a specific |
|
2078 interpreter. |
|
2079 |
|
2080 @param interpreter Python interpreter executable to get sys.path for |
|
2081 @type str |
|
2082 @return list containing sys.path of the interpreter; an empty list |
|
2083 is returned, if the interpreter is the one used to run eric itself |
|
2084 @rtype list of str |
|
2085 """ |
|
2086 import json |
|
2087 |
|
2088 sysPath = [] |
|
2089 |
|
2090 getSysPathSkript = os.path.join( |
|
2091 os.path.dirname(__file__), "GetSysPath.py") |
|
2092 args = [getSysPathSkript] |
|
2093 proc = QProcess() |
|
2094 proc.setProcessChannelMode(QProcess.ProcessChannelMode.MergedChannels) |
|
2095 proc.start(interpreter, args) |
|
2096 finished = proc.waitForFinished(30000) |
|
2097 if finished and proc.exitCode() == 0: |
|
2098 text = proc.readAllStandardOutput() |
|
2099 sysPathResult = str(text, "utf-8", "replace").strip() |
|
2100 with contextlib.suppress(TypeError, ValueError): |
|
2101 sysPath = json.loads(sysPathResult) |
|
2102 if "" in sysPath: |
|
2103 sysPath.remove("") |
|
2104 |
|
2105 return sysPath |
|
2106 |
|
2107 ############################################################################### |
|
2108 ## posix compatibility functions below |
|
2109 ############################################################################### |
|
2110 |
|
2111 # None right now |
|
2112 |
|
2113 ############################################################################### |
|
2114 ## win32 compatibility functions below |
|
2115 ############################################################################### |
|
2116 |
|
2117 |
|
2118 def win32_Kill(pid): |
|
2119 """ |
|
2120 Function to provide an os.kill equivalent for Win32. |
|
2121 |
|
2122 @param pid process id (integer) |
|
2123 @return result of the kill (boolean) |
|
2124 """ |
|
2125 import win32api |
|
2126 handle = win32api.OpenProcess(1, 0, pid) |
|
2127 return (0 != win32api.TerminateProcess(handle, 0)) |
|
2128 |
|
2129 |
|
2130 def win32_GetUserName(): |
|
2131 """ |
|
2132 Function to get the user name under Win32. |
|
2133 |
|
2134 @return user name (string) |
|
2135 """ |
|
2136 try: |
|
2137 import win32api |
|
2138 return win32api.GetUserName() |
|
2139 except ImportError: |
|
2140 try: |
|
2141 u = getEnvironmentEntry('USERNAME') |
|
2142 except KeyError: |
|
2143 u = getEnvironmentEntry('username', None) |
|
2144 return u |
|
2145 |
|
2146 |
|
2147 def win32_getRealName(): |
|
2148 """ |
|
2149 Function to get the user's real name (aka. display name) under Win32. |
|
2150 |
|
2151 @return real name of the current user (string) |
|
2152 """ |
|
2153 import ctypes |
|
2154 |
|
2155 GetUserNameEx = ctypes.windll.secur32.GetUserNameExW |
|
2156 NameDisplay = 3 |
|
2157 |
|
2158 size = ctypes.pointer(ctypes.c_ulong(0)) |
|
2159 GetUserNameEx(NameDisplay, None, size) |
|
2160 |
|
2161 nameBuffer = ctypes.create_unicode_buffer(size.contents.value) |
|
2162 GetUserNameEx(NameDisplay, nameBuffer, size) |
|
2163 return nameBuffer.value |