1 #
2 # File: MiscUtil.py
3 # Author: Manish Sud <msud@san.rr.com>
4 #
5 # Copyright (C) 2026 Manish Sud. All rights reserved.
6 #
7 # This file is part of MayaChemTools.
8 #
9 # MayaChemTools is free software; you can redistribute it and/or modify it under
10 # the terms of the GNU Lesser General Public License as published by the Free
11 # Software Foundation; either version 3 of the License, or (at your option) any
12 # later version.
13 #
14 # MayaChemTools is distributed in the hope that it will be useful, but without
15 # any warranty; without even the implied warranty of merchantability of fitness
16 # for a particular purpose. See the GNU Lesser General Public License for more
17 # details.
18 #
19 # You should have received a copy of the GNU Lesser General Public License
20 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
21 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
22 # Boston, MA, 02111-1307, USA.
23 #
24
25 from __future__ import print_function
26
27 import os
28 import sys
29 import time
30 import re
31 import csv
32 import textwrap
33 import glob
34 import base64
35 import pickle
36 import multiprocessing as mp
37
38 __all__ = [
39 "CheckFileExt",
40 "CheckTextValue",
41 "DoesSMILESFileContainTitleLine",
42 "ExpandFileNames",
43 "GetExamplesTextFromDocOptText",
44 "GetExcelStyleColumnLabel",
45 "GetMayaChemToolsLibDataPath",
46 "GetMayaChemToolsVersion",
47 "GetTextLines",
48 "GetTextLinesWords",
49 "GetWallClockAndProcessorTime",
50 "GetFormattedElapsedTime",
51 "GetFormattedFileSize",
52 "IsEmpty",
53 "IsFloat",
54 "IsInteger",
55 "IsNumber",
56 "JoinWords",
57 "ObjectFromBase64EncodedString",
58 "ObjectToBase64EncodedString",
59 "ParseFileName",
60 "PrintError",
61 "PrintInfo",
62 "PrintWarning",
63 "ProcessOptionConformerGenerator",
64 "ProcessOptionConformerParameters",
65 "ProcessOptionInfileParameters",
66 "ProcessOptionMultiprocessingParameters",
67 "ProcessOptionNameValuePairParameters",
68 "ProcessOptionOutfileParameters",
69 "ProcessOptionPyMOLCubeFileViewParameters",
70 "ProcessOptionSeabornPlotParameters",
71 "ReplaceHTMLEntitiesInText",
72 "ValidateOptionDirPath",
73 "ValidateOptionsDistinctFileNames",
74 "ValidateOptionFileExt",
75 "ValidateOptionFilePath",
76 "ValidateOptionFloatValue",
77 "ValidateOptionIntegerValue",
78 "ValidateOptionNumberValue",
79 "ValidateOptionNumberValues",
80 "ValidateOptionsOutputDirOverwrite",
81 "ValidateOptionsOutputFileOverwrite",
82 "ValidateOptionTextValue",
83 "TruncateText",
84 "WrapText",
85 ]
86
87
88 def CheckFileExt(FileName, FileExts):
89 """Check file type based on the specified file extensions delimited by spaces.
90
91 Arguments:
92 FileName (str): Name of a file.
93 FileExts (str): Space delimited string containing valid file extensions.
94
95 Returns:
96 bool : True, FileName contains a valid file extension; Otherwise, False.
97
98 """
99
100 for FileExt in FileExts.split():
101 if re.search(r"\.%s$" % FileExt, FileName, re.IGNORECASE):
102 return True
103
104 return False
105
106
107 def CheckTextValue(Value, ValidValues):
108 """Check text value based on the specified valid values delimited by spaces.
109
110 Arguments:
111 Value (str): Text value
112 ValidValues (str): Space delimited string containing valid values.
113
114 Returns:
115 bool : True, Value is valid; Otherwise, False.
116
117 """
118
119 ValidValues = re.sub(" ", "|", ValidValues)
120 if re.match("^(%s)$" % ValidValues, Value, re.IGNORECASE):
121 return True
122
123 return False
124
125
126 def GetTextLinesWords(TextFilePath, Delimiter, QuoteChar, IgnoreHeaderLine):
127 """Parse lines in the specified text file into words in a line and return a list containing
128 list of parsed line words.
129
130 Arguments:
131 TextFilePath (str): Text file name including file path.
132 Delimiter (str): Delimiter for parsing text lines.
133 QuoteChar (str): Quote character for line words.
134 IgnoreHeaderLine (bool): A flag indicating whether to ignore first
135 valid data line corresponding to header line.
136
137 Returns:
138 list : A list of lists containing parsed words for lines.
139
140 Notes:
141 The lines starting with # or // are considered comment lines and are
142 ignored during parsing along with any empty lines.
143
144 """
145 if not os.path.exists(TextFilePath):
146 PrintError("The text file file, %s, doesn't exist.\n" % (TextFilePath))
147
148 TextFile = open(TextFilePath, "r")
149 if TextFile is None:
150 PrintError("Couldn't open text file: %s.\n" % (TextFilePath))
151
152 # Collect text lines...
153 TextLines = []
154 FirstValidLine = True
155 for Line in TextFile:
156 Line = Line.rstrip()
157
158 # Ignore empty lines...
159 if not len(Line):
160 continue
161
162 # Ignore comments...
163 if re.match(r"^(#|\/\/)", Line, re.I):
164 continue
165
166 # Ignore header line...
167 if FirstValidLine:
168 FirstValidLine = False
169 if IgnoreHeaderLine:
170 continue
171
172 TextLines.append(Line)
173
174 TextFile.close()
175
176 # Parse text lines...
177 TextLinesWords = []
178
179 TextLinesReader = csv.reader(TextLines, delimiter=Delimiter, quotechar=QuoteChar)
180 for LineWords in TextLinesReader:
181 TextLinesWords.append(LineWords)
182
183 return TextLinesWords
184
185
186 def GetTextLines(TextFilePath):
187 """Read text lines from input file, remove new line characters and return a list containing
188 stripped lines.
189
190 Arguments:
191 TextFilePath (str): Text file name including file path.
192
193 Returns:
194 list : A list lines.
195
196 """
197 TextFile = open(TextFilePath, "r")
198 if TextFile is None:
199 PrintError("Couldn't open text file: %s.\n" % (TextFilePath))
200
201 # Collect text lines...
202 TextLines = [Line.rstrip() for Line in TextFile]
203
204 TextFile.close()
205
206 return TextLines
207
208
209 def DoesSMILESFileContainTitleLine(FileName):
210 """Determine whether the SMILES file contain a title line based on the presence
211 of a string SMILES, Name or ID in the first line.
212
213 Arguments:
214 FileName (str): Name of a file.
215
216 Returns:
217 bool : True, File contains title line; Otherwise, False.
218
219 """
220
221 Infile = open(FileName, "r")
222 if Infile is None:
223 return False
224
225 Line = Infile.readline()
226 Infile.close()
227
228 if re.search("(SMILES|Name|ID)", Line, re.I):
229 return True
230
231 return False
232
233
234 def ExpandFileNames(FilesSpec, Delimiter=","):
235 """Expand files specification using glob module to process any * or ? wild
236 cards in file names and return a list of expanded file names.
237
238 Arguments:
239 FilesSpec (str): Files specifications
240 Delimiter (str): Delimiter for file specifications
241
242 Returns:
243 list : List of expanded file names
244
245 """
246 FileNames = []
247 if not len(FilesSpec):
248 return FileNames
249
250 for FileSpec in FilesSpec.split(Delimiter):
251 FileSpec = FileSpec.strip()
252 if re.search(r"(\*|\?)", FileSpec, re.I):
253 FileNames.extend(glob.glob(FileSpec))
254 else:
255 FileNames.append(FileSpec)
256
257 return FileNames
258
259
260 def GetExamplesTextFromDocOptText(DocOptText):
261 """Get script usage example lines from a docopt doc string. The example text
262 line start from a line containing `Examples:` keyword at the beginning of the line.
263
264 Arguments:
265 DocOptText (str): Doc string containing script usage examples lines starting with
266 a line marked by `Examples:` keyword at the beginning of a line.
267
268 Returns:
269 str : A string containing text lines retrieved from the examples section of
270 DocOptText parameter.
271
272 """
273
274 ExamplesStart = re.compile("^Examples:", re.IGNORECASE)
275 ExamplesEnd = re.compile("^(Author:|See also:|Copyright:)", re.IGNORECASE)
276
277 ExamplesText = "Examples text is not available"
278 ExamplesTextFound = False
279
280 for Line in DocOptText.splitlines():
281 if ExamplesStart.match(Line):
282 ExamplesText = "Examples:"
283 ExamplesTextFound = True
284 continue
285
286 if ExamplesEnd.match(Line):
287 break
288
289 if ExamplesTextFound:
290 ExamplesText += "\n" + Line
291
292 return ExamplesText
293
294
295 def GetExcelStyleColumnLabel(ColNum):
296 """Return Excel style column label for a colum number.
297
298 Arguments:
299 ColNum (int): Column number
300
301 Returns:
302 str : Excel style column label.
303
304 """
305 Letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
306
307 ColLabelList = []
308 while ColNum:
309 ColNum, SubColNum = divmod(ColNum - 1, 26)
310 ColLabelList[:0] = Letters[SubColNum]
311
312 return "".join(ColLabelList)
313
314
315 def GetWallClockAndProcessorTime():
316 """Get wallclock and processor times in seconds.
317
318 Returns:
319 float : Wallclock time.
320 float : Processor time.
321
322 """
323 return (time.time(), _GetProcessorTime())
324
325
326 def _GetProcessorTime():
327 """Get processor time"""
328
329 if sys.version_info[0] >= 3 and sys.version_info[1] >= 3:
330 ProcessorTime = time.process_time()
331 else:
332 ProcessorTime = time.clock()
333
334 return ProcessorTime
335
336
337 def GetMayaChemToolsVersion():
338 """Get version number for MayaChemTools from PackageInfo.csv
339 file in MayaChemTool lib data directory.
340
341 Returns:
342 str : Version number
343
344 """
345 VersionNumber = "NA"
346
347 PackageInfoFilePath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "data", "PackageInfo.csv")
348 if not os.path.exists(PackageInfoFilePath):
349 return VersionNumber
350
351 Delimiter = ","
352 QuoteChar = '"'
353 IgnoreHeaderLine = True
354
355 for LineWords in GetTextLinesWords(PackageInfoFilePath, Delimiter, QuoteChar, IgnoreHeaderLine):
356 KeyName, KeyValue = LineWords
357 if re.match("^VersionNumber$", KeyName, re.I):
358 VersionNumber = KeyValue
359 break
360
361 return VersionNumber
362
363
364 def GetMayaChemToolsLibDataPath():
365 """Get location of MayaChemTools lib data directory.
366
367 Returns:
368 str : Location of MayaChemTools lib data directory.
369
370 Notes:
371 The location of MayaChemTools lib data directory is determined relative to
372 MayaChemTools python lib directory name available through sys.path.
373
374 """
375 MayaChemToolsDataPath = ""
376
377 for PathEntry in sys.path:
378 if re.search("MayaChemTools", PathEntry, re.I) and re.search("Python", PathEntry, re.I):
379 MayaChemToolsDataPath = os.path.join(PathEntry, "..", "data")
380 break
381
382 if not len(MayaChemToolsDataPath):
383 PrintWarning(
384 "MayaChemTools lib directory location doesn't appear to exist in system search path specified by sys.path..."
385 )
386
387 return MayaChemToolsDataPath
388
389
390 def GetFormattedElapsedTime(StartingWallClockTime, StartingProcessorTime):
391 """Get elapsed wallclock and processor times as a string in the following
392 format: Wallclock: %s days, %d hrs, %d mins, %d secs (Process: %d days,
393 %d hrs, %d mins, %.2f secs).
394
395 Arguments:
396 StartingWallClockTime (float): Starting wallclock time in seconds.
397 StartingProcessorTime (float): Starting processor time in seconds.
398
399 Returns:
400 str : Elapsed time formatted as:
401 Wallclock: %s days, %d hrs, %d mins, %d secs (Process: %d days,
402 %d hrs, %d mins, %.2f secs)
403
404 """
405
406 ElapsedWallClockTime = time.time() - StartingWallClockTime
407 ElapsedProcessorTime = _GetProcessorTime() - StartingProcessorTime
408
409 ElapsedTime = "Wallclock: %s (Process: %s)" % (
410 _FormatTimeInSecondsAsText(ElapsedWallClockTime),
411 _FormatTimeInSecondsAsText(ElapsedProcessorTime, Precision=2),
412 )
413
414 return ElapsedTime
415
416
417 def _FormatTimeInSecondsAsText(Seconds, Precision=0):
418 """Get time in seconds in the following format: days, hrs, mins, secs."""
419
420 SecondsInDay = 24 * 60 * 60
421 SecondsInHour = 60 * 60
422 SecondsInMinute = 60
423
424 Days = Seconds // SecondsInDay
425 Hours = (Seconds - Days * SecondsInDay) // SecondsInHour
426 Minutes = (Seconds - Days * SecondsInDay - Hours * SecondsInHour) // SecondsInMinute
427 Seconds = Seconds - Days * SecondsInDay - Hours * SecondsInHour - Minutes * SecondsInMinute
428
429 TimeWords = []
430
431 if Days:
432 TimeWords.append("%d day%s" % (Days, "s" if Days > 1 else ""))
433
434 if Days or Hours:
435 TimeWords.append("%d hr%s" % (Hours, "s" if Hours > 1 else ""))
436
437 if Days or Hours or Minutes:
438 TimeWords.append("%d min%s" % (Minutes, "s" if Minutes > 1 else ""))
439
440 Seconds = "%.*f" % (Precision, Seconds)
441 TimeWords.append("%s sec%s" % (Seconds, "s" if float(Seconds) > 1 else ""))
442
443 return ", ".join(TimeWords)
444
445
446 def GetFormattedFileSize(FileName, Precision=1):
447 """Get file size as a string in the following format: %.*f <bytes, KB, MB,
448 GB>
449
450 Arguments:
451 FileName (str): File path.
452 Precision (int): File size precision.
453
454 Returns:
455 str : File size formatted as: %.2f <bytes, KB, MB, GB>
456
457 """
458
459 Size = os.path.getsize(FileName)
460
461 if Size < 1024:
462 SizeDenominator = 1
463 SizeSuffix = "bytes"
464 elif Size < (1024 * 1024):
465 SizeDenominator = 1024
466 SizeSuffix = "KB"
467 elif Size < (1024 * 1024 * 1024):
468 SizeDenominator = 1024 * 1024
469 SizeSuffix = "MB"
470 elif Size < (1024 * 1024 * 1024 * 1024):
471 SizeDenominator = 1024 * 1024 * 1024
472 SizeSuffix = "GB"
473 else:
474 SizeDenominator = 1
475 SizeSuffix = "bytes"
476
477 Size /= SizeDenominator
478
479 FormattedSize = "%.*f %s" % (Precision, Size, SizeSuffix)
480
481 return FormattedSize
482
483
484 def IsEmpty(Value):
485 """Determine whether the specified value is empty after converting
486 it in to a string and removing all leading and trailing white spaces. A value
487 of type None is considered empty.
488
489 Arguments:
490 Value (str, int or float): Text or a value
491
492 Returns:
493 bool : True, Text string is empty; Otherwsie, False.
494
495 """
496
497 if Value is None:
498 return True
499
500 TextValue = "%s" % Value
501 TextValue = TextValue.strip()
502
503 return False if len(TextValue) else True
504
505
506 def IsFloat(Value):
507 """Determine whether the specified value is a float by converting it
508 into a float.
509
510 Arguments:
511 Value (str, int or float): Text
512
513 Returns:
514 bool : True, Value is a float; Otherwsie, False.
515
516 """
517
518 return IsNumber(Value)
519
520
521 def IsInteger(Value):
522 """Determine whether the specified value is an integer by converting it
523 into an int.
524
525 Arguments:
526 Value (str, int or float): Text
527
528 Returns:
529 bool : True, Value is an integer; Otherwsie, False.
530
531 """
532
533 Status = True
534
535 if Value is None:
536 return False
537
538 try:
539 Value = int(Value)
540 Status = True
541 except ValueError:
542 Status = False
543
544 return Status
545
546
547 def IsNumber(Value):
548 """Determine whether the specified value is a number by converting it
549 into a float.
550
551 Arguments:
552 Value (str, int or float): Text
553
554 Returns:
555 bool : True, Value is a number; Otherwsie, False.
556
557 """
558
559 Status = True
560
561 if Value is None:
562 return Status
563
564 try:
565 Value = float(Value)
566 Status = True
567 except ValueError:
568 Status = False
569
570 return Status
571
572
573 def JoinWords(Words, Delimiter, Quote=False):
574 """Join words in a list using specified delimiter with optional quotes around words.
575
576 Arguments:
577 Words (list): List containing words to join.
578 Delimiter (string): Delimiter for joining words.
579 Quote (bool): Put quotes around words.
580
581 Returns:
582 str : String containing joined words.
583
584 """
585
586 if Quote:
587 JoinedWords = Delimiter.join('"{0}"'.format(Word) for Word in Words)
588 else:
589 JoinedWords = Delimiter.join(Words)
590
591 return JoinedWords
592
593
594 def ObjectToBase64EncodedString(Object):
595 """Encode Python object into base64 encoded string. The object is
596 pickled before encoding.
597
598 Arguments:
599 object: Python object.
600
601 Returns:
602 str : Base64 encode object string or None.
603
604 """
605
606 return None if Object is None else base64.b64encode(pickle.dumps(Object)).decode()
607
608
609 def ObjectFromBase64EncodedString(EncodedObject):
610 """Generate Python object from a bas64 encoded and pickled
611 object string.
612
613 Arguments:
614 str: Base64 encoded and pickled object string.
615
616 Returns:
617 object : Python object or None.
618
619 """
620
621 return None if EncodedObject is None else pickle.loads(base64.b64decode(EncodedObject))
622
623
624 def ParseFileName(FilePath):
625 """Parse specified file path and return file dir, file name, and file extension.
626
627 Arguments:
628 FilePath (str): Name of a file with complete file path.
629
630 Returns:
631 str : File directory.
632 str : File name without file extension.
633 str : File extension.
634
635 """
636 FileDir, FileBaseName = os.path.split(FilePath)
637 FileName, FileExt = os.path.splitext(FileBaseName)
638
639 if re.match(r"^\.", FileExt):
640 FileExt = re.sub(r"^\.", "", FileExt)
641
642 return (FileDir, FileName, FileExt)
643
644
645 def PrintError(Msg, Status=1):
646 """Print message to stderr along with flushing stderr and exit with a specified
647 status. An `Error` prefix is placed before the message.
648
649 Arguments:
650 Msg (str): Text message.
651 Status (int): Exit status.
652
653 """
654
655 PrintInfo("Error: %s" % Msg)
656 sys.exit(Status)
657
658
659 def PrintInfo(Msg=""):
660 """Print message to stderr along with flushing stderr.
661
662 Arguments:
663 Msg (str): Text message.
664
665 """
666
667 print(Msg, sep=" ", end="\n", file=sys.stderr)
668 sys.stderr.flush()
669
670
671 def PrintWarning(msg):
672 """Print message to stderr along with flushing stderr. An `Warning` prefix
673 is placed before the message.
674
675 Arguments:
676 Msg (str): Text message.
677
678 """
679
680 PrintInfo("Warning: %s" % msg)
681
682
683 def ValidateOptionFileExt(OptionName, FileName, FileExts):
684 """Validate file type based on the specified file extensions delimited by spaces.
685
686 Arguments:
687 OptionName (str): Command line option name.
688 FileName (str): Name of a file.
689 FileExts (str): Space delimited string containing valid file extensions.
690
691 Notes:
692 The function exits with an error message for a file name containing
693 invalid file extension.
694
695 """
696
697 if not CheckFileExt(FileName, FileExts):
698 PrintError(
699 'The file name specified , %s, for option "%s" is not valid. Supported file formats: %s\n'
700 % (FileName, OptionName, FileExts)
701 )
702
703
704 def ValidateOptionFilePath(OptionName, FilePath):
705 """Validate presence of the file.
706
707 Arguments:
708 OptionName (str): Command line option name.
709 FilePath (str): Name of a file with complete path.
710
711 Notes:
712 The function exits with an error message for a file path that doesn't exist.
713
714 """
715
716 if not os.path.exists(FilePath):
717 PrintError('The file specified, %s, for option "%s" doesn\'t exist.\n' % (FilePath, OptionName))
718
719
720 def ValidateOptionDirPath(OptionName, DirPath):
721 """Validate directory path.
722
723 Arguments:
724 OptionName (str): Command line option name.
725 FilePath (str): Name of a directory.
726
727 Notes:
728 The function exits with an error message for the path that exists and is
729 not a directory.
730
731 """
732
733 if os.path.exists(DirPath):
734 if not os.path.isdir(DirPath):
735 PrintError('The path specified, %s, for option "%s" is not a directory.\n' % (DirPath, OptionName))
736
737
738 def ValidateOptionFloatValue(OptionName, OptionValue, CmpOpValueMap):
739 """Validate option value using comparison operater and value pairs in specified in
740 a map.
741
742 Arguments:
743 OptionName (str): Command line option name.
744 OptionValue (float or str): Command line option value.
745 CmpOpValueMap (dictionary): Comparison operator key and value pairs to
746 validate values specified in OptionValue.
747
748 Notes:
749 The function exits with an error message for an invalid option values specified
750 in OptionValue.
751
752 Examples:
753
754 ValidateOptionNumberValue("-b, --butinaSimilarityCutoff",
755 Options["--butinaSimilarityCutoff"],
756 {">": 0.0, "<=" : 1.0})
757
758 """
759
760 if not IsFloat(OptionValue):
761 PrintError('The value specified, %s, for option "%s" must be a float.' % (OptionValue, OptionName))
762
763 return ValidateOptionNumberValue(OptionName, float(OptionValue), CmpOpValueMap)
764
765
766 def ValidateOptionIntegerValue(OptionName, OptionValue, CmpOpValueMap):
767 """Validate option value using comparison operater and value pairs in specified in
768 a map.
769
770 Arguments:
771 OptionName (str): Command line option name.
772 OptionValue (int or str): Command line option value.
773 CmpOpValueMap (dictionary): Comparison operator key and value pairs to
774 validate values specified in OptionValue.
775
776 Notes:
777 The function exits with an error message for an invalid option values specified
778 in OptionValue.
779
780 Examples:
781
782 ValidateOptionIntegerValue("--maxConfs", Options["--maxConfs"],
783 {">": 0})
784
785 """
786
787 if not IsInteger(OptionValue):
788 PrintError('The value specified, %s, for option "%s" must be an integer.' % (OptionValue, OptionName))
789
790 return ValidateOptionNumberValue(OptionName, int(OptionValue), CmpOpValueMap)
791
792
793 def ValidateOptionNumberValue(OptionName, OptionValue, CmpOpValueMap):
794 """Validate option value using comparison operater and value pairs in specified in
795 a map.
796
797 Arguments:
798 OptionName (str): Command line option name.
799 OptionValue (int or float): Command line option value.
800 CmpOpValueMap (dictionary): Comparison operator key and value pairs to
801 validate values specified in OptionValue.
802
803 Notes:
804 The function exits with an error message for an invalid option values specified
805 in OptionValue.
806
807 Examples:
808
809 ValidateOptionNumberValue("--maxConfs", int(Options["--maxConfs"]),
810 {">": 0})
811 ValidateOptionNumberValue("-b, --butinaSimilarityCutoff",
812 float(Options["--butinaSimilarityCutoff"]),
813 {">": 0.0, "<=" : 1.0})
814
815 """
816
817 Status = True
818 for CmpOp in CmpOpValueMap:
819 Value = CmpOpValueMap[CmpOp]
820 if re.match("^>$", CmpOp, re.I):
821 if OptionValue <= Value:
822 Status = False
823 break
824 elif re.match("^>=$", CmpOp, re.I):
825 if OptionValue < Value:
826 Status = False
827 break
828 elif re.match("^<$", CmpOp, re.I):
829 if OptionValue >= Value:
830 Status = False
831 break
832 elif re.match("^<=$", CmpOp, re.I):
833 if OptionValue > Value:
834 Status = False
835 break
836 else:
837 PrintError(
838 "The specified comparison operator, %s, for function ValidateOptionNumberValue is not supported\n"
839 % (CmpOp)
840 )
841
842 if not Status:
843 FirstValue = True
844 SupportedValues = ""
845 for CmpOp in CmpOpValueMap:
846 Value = CmpOpValueMap[CmpOp]
847 if FirstValue:
848 FirstValue = False
849 SupportedValues = "%s %s" % (CmpOp, Value)
850 else:
851 SupportedValues = "%s and %s %s" % (SupportedValues, CmpOp, Value)
852
853 PrintError(
854 'The value specified, %s, for option "%s" is not valid. Supported value(s): %s '
855 % (OptionValue, OptionName, SupportedValues)
856 )
857
858
859 def ValidateOptionNumberValues(
860 OptionName, OptionValueString, OptionValueCount, OptionValueDelimiter, OptionValueType, CmpOpValueMap
861 ):
862 """Validate numerical option values using option value string, delimiter, value type,
863 and a specified map containing comparison operator and value pairs.
864
865 Arguments:
866 OptionName (str): Command line option name.
867 OptionValueString (str): Command line option value.
868 OptionValueCount (int): Number of values in OptionValueString.
869 OptionValueDelimiter (str): Delimiter used for values in OptionValueString.
870 OptionValueType (str): Valid number types (integer or float)
871 CmpOpValueMap (dictionary): Comparison operator key and value pairs to
872 validate values specified in OptionValueString.
873
874 Notes:
875 The function exits with an error message for invalid option values specified
876 in OptionValueString
877
878 Examples:
879
880 ValidateOptionNumberValues("-m, --molImageSize",
881 Options["--molImageSize"], 2, ",", "integer", {">": 0})
882
883 """
884 if not CheckTextValue(OptionValueType, "integer float"):
885 PrintError(
886 "The option value type specified, %s, for function ValidateOptionNumberValues is not valid. Supported value: integer float "
887 % (OptionValueType)
888 )
889
890 Values = OptionValueString.split(OptionValueDelimiter)
891 if OptionValueCount > 0 and len(Values) != OptionValueCount:
892 PrintError(
893 'The value specified, %s, for option "%s" is not valid. It must contain %d %s values separated by "%s"'
894 % (OptionValueString, OptionName, OptionValueCount, OptionValueType, OptionValueDelimiter)
895 )
896
897 IsIntergerValue = True
898 if re.match("^float$", OptionValueType, re.I):
899 IsIntergerValue = False
900
901 for Value in Values:
902 if IsIntergerValue:
903 if not IsInteger(Value):
904 PrintError(
905 'The value specified, %s, for option "%s" in string "%s" must be an integer.'
906 % (Value, OptionName, OptionValueString)
907 )
908 Value = int(Value)
909 else:
910 if not IsFloat(Value):
911 PrintError(
912 'The value specified, %s, for option "%s" in string "%s" must be a float.'
913 % (Value, OptionName, OptionValueString)
914 )
915 Value = float(Value)
916 ValidateOptionNumberValue(OptionName, Value, CmpOpValueMap)
917
918
919 def ValidateOptionTextValue(OptionName, OptionValue, ValidValues):
920 """Validate option value based on the valid specified values separated by spaces.
921
922 Arguments:
923 OptionName (str): Command line option name.
924 OptionValue (str): Command line option value.
925 ValidValues (str): Space delimited string containing valid values.
926
927 Notes:
928 The function exits with an error message for an invalid option value.
929
930 """
931
932 if not CheckTextValue(OptionValue, ValidValues):
933 PrintError(
934 'The value specified, %s, for option "%s" is not valid. Supported value(s): %s '
935 % (OptionValue, OptionName, ValidValues)
936 )
937
938
939 def ValidateOptionsOutputFileOverwrite(OptionName, FilePath, OverwriteOptionName, OverwriteStatus):
940 """Validate overwriting of output file.
941
942 Arguments:
943 OptionName (str): Command line option name.
944 FilePath (str): Name of a file with complete file path.
945 OverwriteOptionName (str): Overwrite command line option name.
946 OverwriteStatus (bool): True, overwrite
947
948 Notes:
949 The function exits with an error message for a file that is present and is not allowed
950 to be written as indicated by value of OverwriteStatus.
951
952 """
953
954 if os.path.exists(FilePath):
955 if not OverwriteStatus:
956 if len(OverwriteOptionName) > 4:
957 ShortOverwriteOptionName = OverwriteOptionName[:4]
958 else:
959 ShortOverwriteOptionName = OverwriteOptionName
960
961 PrintError(
962 'The file specified, %s, for option "%s" already exist. Use option "%s" or "%s" and try again.\n'
963 % (FilePath, OptionName, ShortOverwriteOptionName, OverwriteOptionName)
964 )
965
966
967 def ValidateOptionsOutputDirOverwrite(OptionName, DirPath, OverwriteOptionName, OverwriteStatus):
968 """Validate overwriting of output file.
969
970 Arguments:
971 OptionName (str): Command line option name.
972 FilePath (str): Name of a directory.
973 OverwriteOptionName (str): Overwrite command line option name.
974 OverwriteStatus (bool): True, overwrite
975
976 Notes:
977 The function exits with an error message for a directory that is present
978 and is not allowed to be written as indicated by value of OverwriteStatus.
979
980 """
981
982 if os.path.exists(DirPath) and os.path.isdir(DirPath):
983 if not OverwriteStatus:
984 if len(OverwriteOptionName) > 4:
985 ShortOverwriteOptionName = OverwriteOptionName[:4]
986 else:
987 ShortOverwriteOptionName = OverwriteOptionName
988
989 PrintError(
990 'The directory specified, %s, for option "%s" already exist. Use option "%s" or "%s" and try again.\n'
991 % (DirPath, OptionName, ShortOverwriteOptionName, OverwriteOptionName)
992 )
993
994
995 def ValidateOptionsDistinctFileNames(OptionName1, FilePath1, OptionName2, FilePath2):
996 """Validate two distinct file names.
997
998 Arguments:
999 OptionName1 (str): Command line option name.
1000 FilePath1 (str): Name of a file with complete file path.
1001 OptionName2 (str): Command line option name.
1002 FilePath2 (str): Name of a file with complete file path.
1003
1004 Notes:
1005 The function exits with an error message for two non distinct file names.
1006
1007 """
1008
1009 FilePath1Pattern = r"^" + re.escape(FilePath1) + r"$"
1010 if re.match(FilePath1Pattern, FilePath2, re.I):
1011 PrintError(
1012 'The file name specified, %s, for options "%s" and "%s" must be different.\n'
1013 % (FilePath1, OptionName1, OptionName2)
1014 )
1015
1016
1017 def ProcessOptionConformerGenerator(OptionName, OptionValue):
1018 """Process conformer generator option and return a map containing
1019 paramater name and values for generatiing conformers.
1020
1021 Arguments:
1022 ParamOptionName (str): Command line conformer generator option name
1023 ParamOptionValue (str): Command line conformer generator option value
1024
1025 Returns:
1026 dictionary: Conformer generation parameter name and value pairs.
1027
1028 """
1029
1030 ParamsInfo = {}
1031
1032 if re.match("^SDG$", OptionValue, re.I):
1033 ConformerGenerator = "SDG"
1034 SkipConformerGeneration = False
1035 UseExpTorsionAnglePrefs = False
1036 ETVersion = 1
1037 UseBasicKnowledge = False
1038 elif re.match("^KDG$", OptionValue, re.I):
1039 ConformerGenerator = "KDG"
1040 SkipConformerGeneration = False
1041 UseExpTorsionAnglePrefs = False
1042 ETVersion = 1
1043 UseBasicKnowledge = True
1044 elif re.match("^ETDG$", OptionValue, re.I):
1045 ConformerGenerator = "ETDG"
1046 SkipConformerGeneration = False
1047 UseExpTorsionAnglePrefs = True
1048 ETVersion = 1
1049 UseBasicKnowledge = False
1050 elif re.match("^ETKDG$", OptionValue, re.I):
1051 ConformerGenerator = "ETKDG"
1052 SkipConformerGeneration = False
1053 UseExpTorsionAnglePrefs = True
1054 ETVersion = 1
1055 UseBasicKnowledge = True
1056 elif re.match("^ETKDGv2$", OptionValue, re.I):
1057 ConformerGenerator = "ETKDG"
1058 SkipConformerGeneration = False
1059 UseExpTorsionAnglePrefs = True
1060 ETVersion = 2
1061 UseBasicKnowledge = True
1062 elif re.match("^None$", OptionValue, re.I):
1063 ConformerGenerator = "None"
1064 SkipConformerGeneration = True
1065 UseExpTorsionAnglePrefs = None
1066 ETVersion = None
1067 UseBasicKnowledge = None
1068 else:
1069 PrintError(
1070 'The value, %s, specified using "%s" option is not a valid value. Supported values: SDG, KDG, ETDG, ETKDG, ETKDGv2, or None'
1071 % (OptionValue, OptionName)
1072 )
1073
1074 ParamsInfo["ConformerGenerator"] = ConformerGenerator
1075 ParamsInfo["SkipConformerGeneration"] = SkipConformerGeneration
1076 ParamsInfo["UseExpTorsionAnglePrefs"] = UseExpTorsionAnglePrefs
1077 ParamsInfo["ETVersion"] = ETVersion
1078 ParamsInfo["UseBasicKnowledge"] = UseBasicKnowledge
1079
1080 return ParamsInfo
1081
1082
1083 def ProcessOptionConformerParameters(ParamsOptionName, ParamsOptionValue, ParamsDefaultInfo=None):
1084 """Process parameters for conformer generation and return a map containing processed
1085 parameter names and values.
1086
1087 Arguments:
1088 ParamsOptionName (str): Command line conformer generation parameters
1089 option name.
1090 ParamsOptionValue (str): Comma delimited list of parameter name and value pairs.
1091 ParamsDefaultInfo (dict): Default values to override for selected parameters.
1092
1093 Returns:
1094 dictionary: Processed parameter name and value pairs.
1095
1096 Notes:
1097 The parameter name and values specified in ParamsOptionValues are validated before
1098 returning them in a dictionary.
1099
1100 """
1101
1102 ParamsInfo = {
1103 "ConfMethod": "ETKDGv2",
1104 "ForceField": "MMFF",
1105 "ForceFieldMMFFVariant": "MMFF94",
1106 "EnforceChirality": True,
1107 "EmbedRMSDCutoff": 0.5,
1108 "AlignConformers": True,
1109 "MaxConfs": 50,
1110 "MaxConfsTorsions": 50,
1111 "MaxIters": 250,
1112 "RandomSeed": "auto",
1113 "UseTethers": True,
1114 }
1115
1116 # Setup a canonical paramater names...
1117 ValidParamNames = []
1118 CanonicalParamNamesMap = {}
1119 for ParamName in sorted(ParamsInfo):
1120 ValidParamNames.append(ParamName)
1121 CanonicalParamNamesMap[ParamName.lower()] = ParamName
1122
1123 # Update default values...
1124 if ParamsDefaultInfo is not None:
1125 for ParamName in ParamsDefaultInfo:
1126 if ParamName not in ParamsInfo:
1127 PrintError(
1128 'The default parameter name, %s, specified using "%s" to function ProcessOptionConformerParameters is not a valid name. Supported parameter names: %s'
1129 % (ParamName, ParamsDefaultInfo, " ".join(ValidParamNames))
1130 )
1131 ParamsInfo[ParamName] = ParamsDefaultInfo[ParamName]
1132
1133 if re.match("^auto$", ParamsOptionValue, re.I):
1134 # No specific parameters to process except for parameters with possible auto value...
1135 _ProcessOptionConformerAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue)
1136 return ParamsInfo
1137
1138 ParamsOptionValue = ParamsOptionValue.strip()
1139 if not ParamsOptionValue:
1140 PrintError('No valid parameter name and value pairs specified using "%s" option' % ParamsOptionName)
1141
1142 ParamsOptionValueWords = ParamsOptionValue.split(",")
1143 if len(ParamsOptionValueWords) % 2:
1144 PrintError(
1145 'The number of comma delimited paramater names and values, %d, specified using "%s" option must be an even number.'
1146 % (len(ParamsOptionValueWords), ParamsOptionName)
1147 )
1148
1149 # Validate paramater name and value pairs...
1150 for Index in range(0, len(ParamsOptionValueWords), 2):
1151 Name = ParamsOptionValueWords[Index].strip()
1152 Value = ParamsOptionValueWords[Index + 1].strip()
1153
1154 CanonicalName = Name.lower()
1155 if CanonicalName not in CanonicalParamNamesMap:
1156 PrintError(
1157 'The parameter name, %s, specified using "%s" is not a valid name. Supported parameter names: %s'
1158 % (Name, ParamsOptionName, " ".join(ValidParamNames))
1159 )
1160
1161 ParamName = CanonicalParamNamesMap[CanonicalName]
1162 ParamValue = Value
1163
1164 if re.match("^ConfMethod$", ParamName, re.I):
1165 if not re.match("^(SDG|ETDG|KDG|ETKDG|ETKDGv2)$", Value, re.I):
1166 PrintError(
1167 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: SDG, KDG, ETDG, ETKDG or ETKDGv2'
1168 % (Value, Name, ParamsOptionName)
1169 )
1170 ParamValue = Value
1171 elif re.match("^ForceField$", ParamName, re.I):
1172 if not re.match("^(UFF|MMFF)$", Value, re.I):
1173 PrintError(
1174 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: UFF or MMFF'
1175 % (Value, Name, ParamsOptionName)
1176 )
1177 ParamValue = Value
1178 elif re.match("^ForceFieldMMFFVariant$", ParamName, re.I):
1179 if not re.match("^(MMFF94|MMFF94s)$", Value, re.I):
1180 PrintError(
1181 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: MMFF94 or MMFF94s'
1182 % (Value, Name, ParamsOptionName)
1183 )
1184 ParamValue = Value
1185 elif re.match("^(EnforceChirality|AlignConformers|UseTethers)$", ParamName, re.I):
1186 if re.match("^(yes|true)$", Value, re.I):
1187 Value = True
1188 elif re.match("^(no|false)$", Value, re.I):
1189 Value = False
1190 else:
1191 PrintError(
1192 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: yes, no, true, or false'
1193 % (Value, Name, ParamsOptionName)
1194 )
1195 ParamValue = Value
1196 elif re.match("^(MaxConfs|MaxConfsTorsions|MaxIters)$", ParamName, re.I):
1197 if not IsInteger(Value):
1198 PrintError(
1199 'The parameter value, %s, specified for parameter name, %s, using "%s" must be an integer.'
1200 % (Value, Name, ParamsOptionName)
1201 )
1202 Value = int(Value)
1203 if Value <= 0:
1204 PrintError(
1205 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: > 0'
1206 % (Value, Name, ParamsOptionName)
1207 )
1208 ParamValue = Value
1209 elif re.match("^(EmbedRMSDCutoff)$", ParamName, re.I):
1210 if not re.match("^(auto|none)$", Value, re.I):
1211 if not IsFloat(Value):
1212 PrintError(
1213 'The parameter value, %s, specified for parameter name, %s, using "%s" must be a float.'
1214 % (Value, Name, ParamsOptionName)
1215 )
1216 Value = float(Value)
1217 if Value <= 0:
1218 PrintError(
1219 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: > 0'
1220 % (Value, Name, ParamsOptionName)
1221 )
1222 ParamValue = Value
1223 elif re.match("^(RandomSeed)$", ParamName, re.I):
1224 if not re.match("^auto$", Value, re.I):
1225 if not IsInteger(Value):
1226 PrintError(
1227 'The parameter value, %s, specified for parameter name, %s, using "%s" must be an integer.'
1228 % (Value, Name, ParamsOptionName)
1229 )
1230 ParamValue = Value
1231 else:
1232 ParamValue = Value
1233
1234 # Set value...
1235 ParamsInfo[ParamName] = ParamValue
1236
1237 # Handle paramaters with possible auto values...
1238 _ProcessOptionConformerAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue)
1239
1240 return ParamsInfo
1241
1242
1243 def _ProcessOptionConformerAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue):
1244 """Process parameters with possible auto values."""
1245
1246 # Random seed parameter...
1247 ParamValue = "%s" % ParamsInfo["RandomSeed"]
1248 if re.match("^auto$", ParamValue, re.I):
1249 ParamValue = -1
1250 else:
1251 ParamValue = int(ParamValue)
1252 ParamsInfo["RandomSeed"] = ParamValue
1253
1254 # RMSD cutoff parameter...
1255 ParamValue = "%s" % ParamsInfo["EmbedRMSDCutoff"]
1256 if re.match("^(auto|none)$", ParamValue, re.I):
1257 ParamValue = -1.0
1258 else:
1259 ParamValue = float(ParamValue)
1260 ParamsInfo["EmbedRMSDCutoff"] = ParamValue
1261
1262 # Setup derived parameters to facilitate conformer generations and minimization...
1263 UseExpTorsionAnglePrefs = False
1264 UseBasicKnowledge = False
1265 if re.match("^SDG$", ParamsInfo["ConfMethod"], re.I):
1266 ETVersion = 1
1267 UseExpTorsionAnglePrefs = False
1268 UseBasicKnowledge = False
1269 elif re.match("^KDG$", ParamsInfo["ConfMethod"], re.I):
1270 ETVersion = 1
1271 UseExpTorsionAnglePrefs = False
1272 UseBasicKnowledge = True
1273 elif re.match("^ETDG$", ParamsInfo["ConfMethod"], re.I):
1274 ETVersion = 1
1275 UseExpTorsionAnglePrefs = True
1276 UseBasicKnowledge = False
1277 elif re.match("^ETKDG$", ParamsInfo["ConfMethod"], re.I):
1278 ETVersion = 1
1279 UseExpTorsionAnglePrefs = True
1280 UseBasicKnowledge = True
1281 elif re.match("^ETKDGv2$", ParamsInfo["ConfMethod"], re.I):
1282 ETVersion = 2
1283 UseExpTorsionAnglePrefs = True
1284 UseBasicKnowledge = True
1285 else:
1286 ETVersion = None
1287 UseExpTorsionAnglePrefs = None
1288 UseBasicKnowledge = None
1289 ParamsInfo["UseExpTorsionAnglePrefs"] = UseExpTorsionAnglePrefs
1290 ParamsInfo["ETVersion"] = ETVersion
1291 ParamsInfo["UseBasicKnowledge"] = UseBasicKnowledge
1292
1293 if re.match("^UFF$", ParamsInfo["ForceField"], re.I):
1294 UseUFF = True
1295 UseMMFF = False
1296 elif re.match("^MMFF$", ParamsInfo["ForceField"], re.I):
1297 UseUFF = False
1298 UseMMFF = True
1299 else:
1300 UseUFF = None
1301 UseMMFF = None
1302 ParamsInfo["UseUFF"] = UseUFF
1303 ParamsInfo["UseMMFF"] = UseMMFF
1304
1305
1306 def ProcessOptionPyMOLCubeFileViewParameters(ParamsOptionName, ParamsOptionValue, ParamsDefaultInfo=None):
1307 """Process PyMOl parameters for cube file views and return a map containing
1308 processed parameter names and values.
1309
1310 ParamsOptionValue is a comma delimited list of parameter name and value pairs
1311 for setting up PyMOL views.
1312
1313 The supported parameter names along with their default and possible
1314 values are shown below:
1315
1316 ContourColor1, red, ContourColor2, blue,
1317 ContourLevel1, -0.02, ContourLevel2, 0.02,
1318 ContourLevel, 0.02,
1319 ContourLevelAutoAt, 0.5,
1320 ESPRampValues, -1.0 0 1.0,
1321 ESPRampColors, red white blue,
1322 HideHydrogens, yes, DisplayESP, OnSurface,
1323 DisplayMolecule, BallAndStick,
1324 DisplaySphereScale, 0.3, DisplayStickRadius, 0.2,
1325 MeshQuality,2, MeshWidth, 0.5,
1326 SurfaceQualuty, 2, SurfaceTransparency, 0.25,
1327 VolumeColorRamp, auto, VolumeColorRampOpacity, 0.2,
1328 VolumeContourWindowFactor, 0.05
1329
1330 Arguments:
1331 ParamsOptionName (str): Command line PyMOL view option name.
1332 ParamsOptionValues (str): Comma delimited list of parameter name and value pairs.
1333 ParamsDefaultInfo (dict): Default values to override for selected parameters.
1334
1335 Returns:
1336 dictionary: Processed parameter name and value pairs.
1337
1338 """
1339
1340 ParamsInfo = {
1341 "ContourColor1": "red",
1342 "ContourColor2": "blue",
1343 "ContourLevel1": -0.02,
1344 "ContourLevel2": 0.02,
1345 "ContourLevel": 0.02,
1346 "ContourLevelAutoAt": 0.5,
1347 "ESPRampValues": "-1.0 0 1.0",
1348 "ESPRampColors": "red white blue",
1349 "HideHydrogens": True,
1350 "DisplayESP": "OnSurface",
1351 "DisplayMolecule": "BallAndStick",
1352 "DisplaySphereScale": 0.3,
1353 "DisplayStickRadius": 0.2,
1354 "MeshQuality": 2,
1355 "MeshWidth": 0.5,
1356 "SurfaceQuality": 2,
1357 "SurfaceTransparency": 0.25,
1358 "VolumeColorRamp": "auto",
1359 "VolumeColorRampOpacity": 0.2,
1360 "VolumeContourWindowFactor": 0.05,
1361 }
1362
1363 # Setup a canonical paramater names...
1364 ValidParamNames = []
1365 CanonicalParamNamesMap = {}
1366 for ParamName in sorted(ParamsInfo):
1367 ValidParamNames.append(ParamName)
1368 CanonicalParamNamesMap[ParamName.lower()] = ParamName
1369
1370 # Update default values...
1371 if ParamsDefaultInfo is not None:
1372 for ParamName in ParamsDefaultInfo:
1373 if ParamName not in ParamsInfo:
1374 PrintError(
1375 'The default parameter name, %s, specified using "%s" to function ProcessOptionPyMOLViewParametersForCubeFiles not a valid name. Supported parameter names: %s'
1376 % (ParamName, ParamsDefaultInfo, " ".join(ValidParamNames))
1377 )
1378 ParamsInfo[ParamName] = ParamsDefaultInfo[ParamName]
1379
1380 if re.match("^auto$", ParamsOptionValue, re.I):
1381 # No specific parameters to process except for parameters with possible auto value...
1382 _ProcessOptionPyMOLCubeFileViewAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue)
1383 return ParamsInfo
1384
1385 ParamsOptionValue = ParamsOptionValue.strip()
1386 if not ParamsOptionValue:
1387 PrintError('No valid parameter name and value pairs specified using "%s" option' % ParamsOptionName)
1388
1389 ParamsOptionValueWords = ParamsOptionValue.split(",")
1390 if len(ParamsOptionValueWords) % 2:
1391 PrintError(
1392 'The number of comma delimited paramater names and values, %d, specified using "%s" option must be an even number.'
1393 % (len(ParamsOptionValueWords), ParamsOptionName)
1394 )
1395
1396 # Validate paramater name and value pairs...
1397 for Index in range(0, len(ParamsOptionValueWords), 2):
1398 Name = ParamsOptionValueWords[Index].strip()
1399 Value = ParamsOptionValueWords[Index + 1].strip()
1400
1401 CanonicalName = Name.lower()
1402 if CanonicalName not in CanonicalParamNamesMap:
1403 PrintError(
1404 'The parameter name, %s, specified using "%s" is not a valid name. Supported parameter names: %s'
1405 % (Name, ParamsOptionName, " ".join(ValidParamNames))
1406 )
1407
1408 ParamName = CanonicalParamNamesMap[CanonicalName]
1409 ParamValue = Value
1410 ParamValueStr = "%s" % Value
1411
1412 if re.match("^(MeshWidth|SurfaceTransparency|DisplaySphereScale|DisplayStickRadius)$", ParamName, re.I):
1413 if not IsFloat(Value):
1414 PrintError(
1415 'The parameter value, %s, specified for parameter name, %s, using "%s" option must be a float.'
1416 % (Value, Name, ParamsOptionName)
1417 )
1418 Value = float(Value)
1419 if Value <= 0:
1420 PrintError(
1421 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: > 0'
1422 % (Value, Name, ParamsOptionName)
1423 )
1424 ParamValue = Value
1425 elif re.match("^(MeshQuality|SurfaceQuality)$", ParamName, re.I):
1426 if not IsInteger(Value):
1427 PrintError(
1428 'The parameter value, %s, specified for parameter name, %s, using "%s" option must be an integer.'
1429 % (Value, Name, ParamsOptionName)
1430 )
1431 Value = int(Value)
1432 if Value <= 0:
1433 PrintError(
1434 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: > 0'
1435 % (Value, Name, ParamsOptionName)
1436 )
1437 ParamValue = Value
1438 elif re.match("^ContourLevel1$", ParamName, re.I) and not re.match("^auto$", ParamValueStr, re.I):
1439 if not IsFloat(Value):
1440 PrintError(
1441 'The parameter value, %s, specified for parameter name, %s, using "%s" option must be a float.'
1442 % (Value, Name, ParamsOptionName)
1443 )
1444 Value = float(Value)
1445 if Value >= 0:
1446 PrintError(
1447 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: < 0'
1448 % (Value, Name, ParamsOptionName)
1449 )
1450 ParamValue = Value
1451 elif re.match("^ContourLevel2$", ParamName, re.I) and not re.match("^auto$", ParamValueStr, re.I):
1452 if not IsFloat(Value):
1453 PrintError(
1454 'The parameter value, %s, specified for parameter name, %s, using "%s" option must be a float.'
1455 % (Value, Name, ParamsOptionName)
1456 )
1457 Value = float(Value)
1458 if Value <= 0:
1459 PrintError(
1460 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: > 0'
1461 % (Value, Name, ParamsOptionName)
1462 )
1463 ParamValue = Value
1464 elif re.match("^ContourLevel$", ParamName, re.I) and not re.match("^auto$", ParamValueStr, re.I):
1465 if not IsFloat(Value):
1466 PrintError(
1467 'The parameter value, %s, specified for parameter name, %s, using "%s" option must be a float.'
1468 % (Value, Name, ParamsOptionName)
1469 )
1470 ParamValue = float(Value)
1471 elif re.match("^ContourLevelAutoAt$", ParamName, re.I):
1472 if not IsFloat(Value):
1473 PrintError(
1474 'The parameter value, %s, specified for parameter name, %s, using "%s" option must be a float.'
1475 % (Value, Name, ParamsOptionName)
1476 )
1477 Value = float(Value)
1478 if Value <= 0 or Value >= 1:
1479 PrintError(
1480 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: > 0 and < 1'
1481 % (Value, Name, ParamsOptionName)
1482 )
1483 ParamValue = Value
1484 elif re.match("^VolumeColorRampOpacity$", ParamName, re.I):
1485 if not IsFloat(Value):
1486 PrintError(
1487 'The parameter value, %s, specified for parameter name, %s, using "%s" option must be a float.'
1488 % (Value, Name, ParamsOptionName)
1489 )
1490 Value = float(Value)
1491 if Value < 0 or Value > 1:
1492 PrintError(
1493 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: >= 0 and <= 1'
1494 % (Value, Name, ParamsOptionName)
1495 )
1496 ParamValue = Value
1497 elif re.match("^VolumeContourWindowFactor$", ParamName, re.I):
1498 if not IsFloat(Value):
1499 PrintError(
1500 'The parameter value, %s, specified for parameter name, %s, using "%s" option must be a float.'
1501 % (Value, Name, ParamsOptionName)
1502 )
1503 Value = float(Value)
1504 if Value <= 0:
1505 PrintError(
1506 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: > 0'
1507 % (Value, Name, ParamsOptionName)
1508 )
1509 ParamValue = Value
1510 elif re.match("^HideHydrogens$", ParamName, re.I):
1511 if not re.match("^(Yes|No|True|False)$", Value, re.I):
1512 PrintError(
1513 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: Yes No True False'
1514 % (Value, Name, ParamsOptionName)
1515 )
1516 ParamValue = True
1517 if re.match("^(No|False)$", Value, re.I):
1518 ParamValue = False
1519 elif re.match("^DisplayMolecule$", ParamName, re.I):
1520 if re.match("^Sticks$", Value, re.I):
1521 ParamValue = "Sticks"
1522 elif re.match("^BallAndStick$", Value, re.I):
1523 ParamValue = "BallAndStick"
1524 else:
1525 PrintError(
1526 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: Sticks or BallAndStick'
1527 % (Value, Name, ParamsOptionName)
1528 )
1529 elif re.match("^DisplayESP$", ParamName, re.I):
1530 if re.match("^OnTotalDensity$", Value, re.I):
1531 ParamValue = "OnTotalDensity"
1532 elif re.match("^OnSurface$", Value, re.I):
1533 ParamValue = "OnSurface"
1534 else:
1535 PrintError(
1536 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: OnTotalDensity or OnSurface'
1537 % (Value, Name, ParamsOptionName)
1538 )
1539 else:
1540 ParamValue = Value
1541
1542 # Set value...
1543 ParamsInfo[ParamName] = ParamValue
1544
1545 # Handle paramaters with possible auto values...
1546 _ProcessOptionPyMOLCubeFileViewAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue)
1547
1548 return ParamsInfo
1549
1550
1551 def _ProcessOptionPyMOLCubeFileViewAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue):
1552 """Process parameters with possible auto values."""
1553
1554 # Setup ParamsInfo to indicate "auto" values...
1555 ParamNames = ["VolumeColorRamp", "ContourLevel1", "ContourLevel2", "ContourLevel", "ESPRampColors", "ESPRampValues"]
1556 for ParamName in ParamNames:
1557 ParamValue = "%s" % ParamsInfo[ParamName]
1558 ParamValueAuto = True if re.match("^auto$", ParamValue, re.I) else False
1559 ParamNameAuto = "%sAuto" % ParamName
1560 ParamsInfo[ParamNameAuto] = ParamValueAuto
1561
1562
1563 def ProcessOptionMultiprocessingParameters(ParamsOptionName, ParamsOptionValue):
1564 """Process parameters for multiprocessing and return a map containing processed
1565 parameter names and values.
1566
1567 Arguments:
1568 ParamsOptionName (str): Command line multiprocessing parameters option name.
1569 ParamsOptionValue (str): Comma delimited list of parameter name and value pairs.
1570
1571 Returns:
1572 dictionary: Processed parameter name and value pairs.
1573
1574 Notes:
1575 The parameter name and values specified in ParamsOptionValue are validated before
1576 returning them in a dictionary.
1577
1578 """
1579
1580 ParamsInfo = {"ChunkSize": "auto", "InputDataMode": "Lazy", "NumProcesses": "auto"}
1581
1582 if re.match("^auto$", ParamsOptionValue, re.I):
1583 # No specific parameters to process except for parameters with possible auto value...
1584 _ProcessOptionMultiprocessingParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue)
1585 return ParamsInfo
1586
1587 ParamsOptionValue = re.sub(" ", "", ParamsOptionValue)
1588 if not ParamsOptionValue:
1589 PrintError('No valid parameter name and value pairs specified using "%s" option' % ParamsOptionName)
1590
1591 ParamsOptionValueWords = ParamsOptionValue.split(",")
1592 if len(ParamsOptionValueWords) % 2:
1593 PrintError(
1594 'The number of comma delimited paramater names and values, %d, specified using "%s" option must be an even number.'
1595 % (len(ParamsOptionValueWords), ParamsOptionName)
1596 )
1597
1598 # Setup a canonical paramater names...
1599 ValidParamNames = []
1600 CanonicalParamNamesMap = {}
1601 for ParamName in sorted(ParamsInfo):
1602 ValidParamNames.append(ParamName)
1603 CanonicalParamNamesMap[ParamName.lower()] = ParamName
1604
1605 # Validate paramater name and value pairs...
1606 for Index in range(0, len(ParamsOptionValueWords), 2):
1607 Name = ParamsOptionValueWords[Index]
1608 Value = ParamsOptionValueWords[Index + 1]
1609
1610 CanonicalName = Name.lower()
1611 if CanonicalName not in CanonicalParamNamesMap:
1612 PrintError(
1613 'The parameter name, %s, specified using "%s" is not a valid name. Supported parameter names: %s'
1614 % (Name, ParamsOptionName, " ".join(ValidParamNames))
1615 )
1616
1617 ParamName = CanonicalParamNamesMap[CanonicalName]
1618 ParamValue = Value
1619
1620 if re.match("^InputDataMode$", ParamName, re.I):
1621 if re.match("^Lazy$", Value, re.I):
1622 ParamValue = "Lazy"
1623 elif re.match("^InMemory$", Value, re.I):
1624 ParamValue = "InMemory"
1625 else:
1626 PrintError(
1627 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: Lazy or InMemory'
1628 % (Value, Name, ParamsOptionName)
1629 )
1630 elif re.match("^NumProcesses$", ParamName, re.I):
1631 if not re.match("^Auto$", Value, re.I):
1632 Value = int(Value)
1633 if Value <= 0:
1634 PrintError(
1635 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: > 0'
1636 % (Value, Name, ParamsOptionName)
1637 )
1638 if Value > mp.cpu_count():
1639 PrintWarning(
1640 'The parameter value, %s, specified for parameter name, %s, using "%s" option is greater than number of CPUs, %s, returned by mp.cpu_count().'
1641 % (Value, Name, ParamsOptionName, mp.cpu_count())
1642 )
1643 else:
1644 if not re.match("^Auto$", Value, re.I):
1645 Value = int(Value)
1646 if Value <= 0:
1647 PrintError(
1648 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: > 0'
1649 % (Value, Name, ParamsOptionName)
1650 )
1651
1652 # Set value...
1653 ParamsInfo[ParamName] = ParamValue
1654
1655 # Handle paramaters with possible auto values...
1656 _ProcessOptionMultiprocessingParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue)
1657
1658 return ParamsInfo
1659
1660
1661 def _ProcessOptionMultiprocessingParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue):
1662 """Process parameters with possible auto values."""
1663
1664 # NumProcesses parameter...
1665 Value = ParamsInfo["NumProcesses"]
1666 ParamsInfo["NumProcesses"] = mp.cpu_count() if re.match("^auto$", Value, re.I) else int(Value)
1667
1668 # ChunkSize parameter...
1669 Value = ParamsInfo["ChunkSize"]
1670 if re.match("^auto$", Value, re.I):
1671 Value = None if re.match("^InMemory$", ParamsInfo["InputDataMode"], re.I) else 1
1672 else:
1673 Value = int(Value)
1674 ParamsInfo["ChunkSize"] = Value
1675
1676
1677 def ProcessOptionInfileParameters(
1678 ParamsOptionName, ParamsOptionValue, InfileName=None, OutfileName=None, ParamsDefaultInfo=None
1679 ):
1680 """Process parameters for reading input files and return a map containing
1681 processed parameter names and values.
1682
1683 Arguments:
1684 ParamsOptionName (str): Command line input parameters option name.
1685 ParamsOptionValue (str): Comma delimited list of parameter name and value pairs.
1686 InfileName (str): Name of input file.
1687 OutfileName (str): Name of output file.
1688 ParamsDefaultInfo (dict): Default values to override for selected parameters.
1689
1690 Returns:
1691 dictionary: Processed parameter name and value pairs.
1692
1693 Notes:
1694 The parameter name and values specified in ParamsOptionValue are validated before
1695 returning them in a dictionary.
1696
1697 """
1698
1699 ParamsInfo = {
1700 "RemoveHydrogens": True,
1701 "Sanitize": True,
1702 "StrictParsing": True,
1703 "SMILESColumn": 1,
1704 "SMILESNameColumn": 2,
1705 "SMILESDelimiter": " ",
1706 "SMILESTitleLine": "auto",
1707 }
1708
1709 # Update default values...
1710 if ParamsDefaultInfo is not None:
1711 for ParamName in ParamsDefaultInfo:
1712 if ParamName not in ParamsInfo:
1713 ValidParamNames = sorted(ParamsInfo.keys())
1714 PrintError(
1715 'The default parameter name, %s, specified using "%s" to function ProcessOptionInfileParameters is not a valid name. Supported parameter names: %s'
1716 % (ParamName, ParamsDefaultInfo, " ".join(ValidParamNames))
1717 )
1718 ParamsInfo[ParamName] = ParamsDefaultInfo[ParamName]
1719
1720 _ProcessInfileAndOutfileParameters(
1721 "Infile", ParamsInfo, ParamsOptionName, ParamsOptionValue, InfileName, OutfileName
1722 )
1723
1724 return ParamsInfo
1725
1726
1727 def ProcessOptionOutfileParameters(
1728 ParamsOptionName, ParamsOptionValue, InfileName=None, OutfileName=None, ParamsDefaultInfo=None
1729 ):
1730 """Process parameters for writing output files and return a map containing
1731 processed parameter names and values.
1732
1733 Arguments:
1734 ParamsOptionName (str): Command line input parameters option name.
1735 ParamsOptionValue (str): Comma delimited list of parameter name and value pairs.
1736 InfileName (str): Name of input file.
1737 OutfileName (str): Name of output file.
1738 ParamsDefaultInfo (dict): Default values to override for selected parameters.
1739
1740 Returns:
1741 dictionary: Processed parameter name and value pairs.
1742
1743 Notes:
1744 The parameter name and values specified in ParamsOptionValue are validated before
1745 returning them in a dictionary.
1746
1747 The default value of some parameters may depend on type of input file. Consequently,
1748 the input file name is also needed.
1749
1750 """
1751
1752 ParamsInfo = {
1753 "Compute2DCoords": "auto",
1754 "Kekulize": True,
1755 "ForceV3000": False,
1756 "SMILESKekulize": False,
1757 "SMILESDelimiter": " ",
1758 "SMILESIsomeric": True,
1759 "SMILESTitleLine": True,
1760 "SMILESMolName": True,
1761 "SMILESMolProps": False,
1762 }
1763
1764 # Update default values...
1765 if ParamsDefaultInfo is not None:
1766 for ParamName in ParamsDefaultInfo:
1767 if ParamName not in ParamsInfo:
1768 ValidParamNames = sorted(ParamsInfo.keys())
1769 PrintError(
1770 'The default parameter name, %s, specified using "%s" to function ProcessOptionOutfileParameters is not a valid name. Supported parameter names: %s'
1771 % (ParamName, ParamsDefaultInfo, " ".join(ValidParamNames))
1772 )
1773 ParamsInfo[ParamName] = ParamsDefaultInfo[ParamName]
1774
1775 _ProcessInfileAndOutfileParameters(
1776 "Outfile", ParamsInfo, ParamsOptionName, ParamsOptionValue, InfileName, OutfileName
1777 )
1778
1779 return ParamsInfo
1780
1781
1782 def _ProcessInfileAndOutfileParameters(Mode, ParamsInfo, ParamsOptionName, ParamsOptionValue, InfileName, OutfileName):
1783 """Process specified infile and outfile paramaters."""
1784 if re.match("^auto$", ParamsOptionValue, re.I):
1785 # No specific parameters to process except for parameters with possible auto value...
1786 _ProcessInfileAndOutfileAutoParameters(
1787 Mode, ParamsInfo, ParamsOptionName, ParamsOptionValue, InfileName, OutfileName
1788 )
1789 return
1790
1791 ParamsOptionValue = re.sub(" ", "", ParamsOptionValue)
1792 if not ParamsOptionValue:
1793 PrintError('No valid parameter name and value pairs specified using "%s" option' % ParamsOptionName)
1794
1795 ParamsOptionValueWords = ParamsOptionValue.split(",")
1796 if len(ParamsOptionValueWords) % 2:
1797 PrintError(
1798 'The number of comma delimited paramater names and values, %d, specified using "%s" option must be an even number.'
1799 % (len(ParamsOptionValueWords), ParamsOptionName)
1800 )
1801
1802 # Setup a canonical paramater names...
1803 ValidParamNames = []
1804 CanonicalParamNamesMap = {}
1805 for ParamName in sorted(ParamsInfo):
1806 ValidParamNames.append(ParamName)
1807 CanonicalParamNamesMap[ParamName.lower()] = ParamName
1808
1809 # Validate paramater name and value pairs...
1810 for Index in range(0, len(ParamsOptionValueWords), 2):
1811 Name = ParamsOptionValueWords[Index]
1812 Value = ParamsOptionValueWords[Index + 1]
1813
1814 CanonicalName = Name.lower()
1815 if CanonicalName not in CanonicalParamNamesMap:
1816 PrintError(
1817 'The parameter name, %s, specified using "%s" is not a valid name. Supported parameter names: %s'
1818 % (Name, ParamsOptionName, " ".join(ValidParamNames))
1819 )
1820
1821 ParamName = CanonicalParamNamesMap[CanonicalName]
1822 ParamValue = Value
1823
1824 if re.match(
1825 "^(Sanitize|StrictParsing|RemoveHydrogens|Kekulize|ForceV3000|SMILESKekulize|SMILESIsomeric)$",
1826 ParamName,
1827 re.I,
1828 ):
1829 if not re.match("^(Yes|No|True|False)$", Value, re.I):
1830 PrintError(
1831 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: Yes No True False'
1832 % (Value, Name, ParamsOptionName)
1833 )
1834 ParamValue = True
1835 if re.match("^(No|False)$", Value, re.I):
1836 ParamValue = False
1837 elif re.match("^SMILESTitleLine$", ParamName, re.I):
1838 if re.match("^Infile$", Mode, re.I):
1839 if not re.match("^(Yes|No|True|False|Auto)$", Value, re.I):
1840 PrintError(
1841 'The parameter value, %s, specified for paramater name, %s, using "%s" option is not a valid value. Supported values: Yes No True False Auto'
1842 % (Value, Name, ParamsOptionName)
1843 )
1844 elif re.match("^Outfile$", Mode, re.I):
1845 if not re.match("^(Yes|No|True|False)$", Value, re.I):
1846 PrintError(
1847 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: Yes No True False'
1848 % (Value, Name, ParamsOptionName)
1849 )
1850 ParamValue = True
1851 if re.match("^(No|False)$", Value, re.I):
1852 ParamValue = False
1853 elif re.match("^(SMILESMolName|SMILESMolProps)$", ParamName, re.I):
1854 if re.match("^Outfile$", Mode, re.I):
1855 if not re.match("^(Yes|No|True|False)$", Value, re.I):
1856 PrintError(
1857 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: Yes No True False'
1858 % (Value, Name, ParamsOptionName)
1859 )
1860 ParamValue = True
1861 if re.match("^(No|False)$", Value, re.I):
1862 ParamValue = False
1863 else:
1864 PrintError(
1865 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value during mode %s.'
1866 % (Value, Name, ParamsOptionName, Mode)
1867 )
1868 elif re.match("^SMILESDelimiter$", ParamName, re.I):
1869 if not re.match("^(space|tab|comma)$", Value, re.I):
1870 PrintError(
1871 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: space tab comma'
1872 % (Value, Name, ParamsOptionName)
1873 )
1874 ParamValue = " "
1875 if re.match("^tab$", Value, re.I):
1876 ParamValue = "\t"
1877 elif re.match("^comma$", Value, re.I):
1878 ParamValue = ","
1879 elif re.match("^Compute2DCoords$", ParamName, re.I):
1880 # No need to set the value. It would be processed later to handle "auto" value...
1881 if not re.match("^(Yes|No|True|False|Auto)$", Value, re.I):
1882 PrintError(
1883 'The parameter value, %s, specified for paramater name, %s, using "%s" option is not a valid value. Supported values: Yes No True False Auto'
1884 % (Value, Name, ParamsOptionName)
1885 )
1886 else:
1887 ParamValue = int(Value)
1888 if ParamValue <= 0:
1889 PrintError(
1890 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: > 0'
1891 % (Value, Name, ParamsOptionName)
1892 )
1893
1894 # Set value...
1895 ParamsInfo[ParamName] = ParamValue
1896
1897 # Handle paramaters with possible auto values...
1898 _ProcessInfileAndOutfileAutoParameters(
1899 Mode, ParamsInfo, ParamsOptionName, ParamsOptionValue, InfileName, OutfileName
1900 )
1901
1902
1903 def _ProcessInfileAndOutfileAutoParameters(
1904 Mode, ParamsInfo, ParamsOptionName, ParamsOptionValue, InfileName, OutfileName
1905 ):
1906 """Process parameters with possible auto values."""
1907 if re.match("^Infile$", Mode, re.I):
1908 # SMILESTitleLine parameter...
1909 Value = ParamsInfo["SMILESTitleLine"]
1910 ParamValue = False
1911 if re.match("^auto$", Value, re.I):
1912 if InfileName is not None:
1913 if CheckFileExt(InfileName, "smi csv tsv txt"):
1914 ParamValue = DoesSMILESFileContainTitleLine(InfileName)
1915 elif re.match("^(Yes|True)$", Value, re.I):
1916 ParamValue = True
1917 ParamsInfo["SMILESTitleLine"] = ParamValue
1918 elif re.match("^Outfile$", Mode, re.I):
1919 # Compute2DCoords parameter...
1920 Value = ParamsInfo["Compute2DCoords"]
1921 ParamValue = False
1922 if re.match("^auto$", Value, re.I):
1923 if InfileName is not None:
1924 if CheckFileExt(InfileName, "smi csv tsv txt"):
1925 ParamValue = True
1926 if OutfileName is not None:
1927 if CheckFileExt(OutfileName, "smi csv tsv txt"):
1928 # No need to compute 2D coords for SMILES file...
1929 ParamValue = False
1930 elif re.match("^(Yes|True)$", Value, re.I):
1931 ParamValue = True
1932 ParamsInfo["Compute2DCoords"] = ParamValue
1933
1934 # SetSMILESMolProps parameter...
1935 SetSMILESMolProps = False
1936 if OutfileName is not None:
1937 SetSMILESMolProps = True if (ParamsInfo["SMILESMolProps"] and CheckFileExt(OutfileName, "smi")) else False
1938 ParamsInfo["SetSMILESMolProps"] = SetSMILESMolProps
1939
1940
1941 def ProcessOptionSeabornPlotParameters(ParamsOptionName, ParamsOptionValue, ParamsDefaultInfo=None):
1942 """Process parameters for generating Seaborn plots and return a map containing
1943 processed parameter names and values.
1944
1945 Arguments:
1946 ParamsOptionName (str): Command line seaborn parameters option name.
1947 ParamsOptionValue (str): Comma delimited list of parameter name and value pairs.
1948 ParamsDefaultValues (dict): Default values for selected parameters.
1949
1950 Returns:
1951 dictionary: Processed parameter name and value pairs.
1952
1953 Notes:
1954 The parameter name and values specified in ParamsOptionValue are validated before
1955 returning them in a dictionary.
1956
1957 """
1958 # The default width and height in Matplotlib is 6.4 and 4.8 and maps to aspect ratio of 1.3...
1959 ParamsInfo = {
1960 "Type": "auto",
1961 "OutExt": "svg",
1962 "Width": "auto",
1963 "Height": "auto",
1964 "Title": "auto",
1965 "XLabel": "auto",
1966 "YLabel": "auto",
1967 "TitleWeight": "bold",
1968 "LabelWeight": "bold",
1969 "Style": "darkgrid",
1970 "Palette": "deep",
1971 "Font": "sans-serif",
1972 "FontScale": 1,
1973 "Context": "notebook",
1974 }
1975
1976 # Setup a canonical paramater names...
1977 ValidParamNames = []
1978 CanonicalParamNamesMap = {}
1979 for ParamName in sorted(ParamsInfo):
1980 ValidParamNames.append(ParamName)
1981 CanonicalParamNamesMap[ParamName.lower()] = ParamName
1982
1983 # Update default values...
1984 if ParamsDefaultInfo is not None:
1985 for ParamName in ParamsDefaultInfo:
1986 if ParamName not in ParamsInfo:
1987 PrintError(
1988 'The default parameter name, %s, specified using "%s" to function ProcessOptionSeabornPlotParameters is not a valid name. Supported parameter names: %s'
1989 % (ParamName, ParamsDefaultInfo, " ".join(ValidParamNames))
1990 )
1991 ParamsInfo[ParamName] = ParamsDefaultInfo[ParamName]
1992
1993 if re.match("^auto$", ParamsOptionValue, re.I):
1994 # No specific parameters to process except for parameters with possible auto value...
1995 _ProcessOptionSeabornPlotParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue, ParamsDefaultInfo)
1996 return ParamsInfo
1997
1998 ParamsOptionValue = ParamsOptionValue.strip()
1999 if not ParamsOptionValue:
2000 PrintError('No valid parameter name and value pairs specified using "%s" option' % ParamsOptionName)
2001
2002 ParamsOptionValueWords = ParamsOptionValue.split(",")
2003 if len(ParamsOptionValueWords) % 2:
2004 PrintError(
2005 'The number of comma delimited paramater names and values, %d, specified using "%s" option must be an even number.'
2006 % (len(ParamsOptionValueWords), ParamsOptionName)
2007 )
2008
2009 # Validate paramater name and value pairs...
2010 for Index in range(0, len(ParamsOptionValueWords), 2):
2011 Name = ParamsOptionValueWords[Index].strip()
2012 Value = ParamsOptionValueWords[Index + 1].strip()
2013
2014 CanonicalName = Name.lower()
2015 if CanonicalName not in CanonicalParamNamesMap:
2016 PrintError(
2017 'The parameter name, %s, specified using "%s" is not a valid name. Supported parameter names: %s'
2018 % (Name, ParamsOptionName, " ".join(ValidParamNames))
2019 )
2020
2021 ParamName = CanonicalParamNamesMap[CanonicalName]
2022 ParamValue = Value
2023
2024 if re.match("^Style$", ParamName, re.I):
2025 if not re.match("^(darkgrid|whitegrid|dark|white|ticks)$", Value):
2026 PrintError(
2027 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: darkgrid, whitegrid, dark, white or ticks'
2028 % (Value, Name, ParamsOptionName)
2029 )
2030 elif re.match("^Palette$", ParamName, re.I):
2031 if not re.match("^(deep|muted|pastel|dark|bright|colorblind)$", Value):
2032 PrintError(
2033 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: deep, muted, pastel, dark, bright or colorblind'
2034 % (Value, Name, ParamsOptionName)
2035 )
2036 elif re.match("^Context$", ParamName, re.I):
2037 if not re.match("^(notebook|paper|talk|poster)$", Value):
2038 PrintError(
2039 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: notebook, paper, talk or poster'
2040 % (Value, Name, ParamsOptionName)
2041 )
2042 elif re.match("^(Width|Height)$", ParamName, re.I):
2043 if not re.match("^auto$", ParamValue, re.I):
2044 Value = float(Value)
2045 if Value <= 0:
2046 PrintError(
2047 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: > 0'
2048 % (Value, Name, ParamsOptionName)
2049 )
2050 ParamValue = Value
2051 elif re.match("^(FontScale)$", ParamName, re.I):
2052 Value = float(Value)
2053 if Value <= 0:
2054 PrintError(
2055 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: > 0'
2056 % (Value, Name, ParamsOptionName)
2057 )
2058 ParamValue = Value
2059 # Set value...
2060 ParamsInfo[ParamName] = ParamValue
2061
2062 # Handle paramaters with possible auto values...
2063 _ProcessOptionSeabornPlotParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue, ParamsDefaultInfo)
2064
2065 return ParamsInfo
2066
2067
2068 def _ProcessOptionSeabornPlotParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue, ParamsDefaultInfo=None):
2069 """Process parameters with possible auto values."""
2070
2071 if ParamsDefaultInfo is None:
2072 return
2073
2074 for ParamName in ParamsInfo:
2075 ParamValue = "%s" % ParamsInfo[ParamName]
2076 if re.match("^auto$", ParamValue, re.I):
2077 if ParamName in ParamsDefaultInfo:
2078 ParamsInfo[ParamName] = ParamsDefaultInfo[ParamName]
2079
2080
2081 def ProcessOptionNameValuePairParameters(ParamsOptionName, ParamsOptionValue, ParamsDefaultInfo):
2082 """Process name and value parameter pairs for an option and return a map
2083 containing processed parameter names and values.
2084
2085 Arguments:
2086 ParamsOptionName (str): Command line option name for name and value
2087 parameter pairs.
2088 ParamsOptionValue (str): Comma delimited list of parameter name and
2089 value parameter pairs.
2090 ParamsDefaultInfo (dict): A dictionary containing a list of parameter
2091 type and default value pairs keyed by parameter name. Supported
2092 parameter types: bool, int, float, file, and str.
2093
2094 Returns:
2095 dictionary: Processed parameter name and value pairs.
2096
2097 Notes:
2098 The parameter names and values specified in ParamsOptionValue are validated before
2099 returning them in a dictionary.
2100
2101 Examples:
2102
2103 ParamsDefaultInfo = {"Cleanup": ["bool", True], "RemoveFragments":
2104 ["bool", True], "Neutralize": ["bool", True],
2105 "CanonicalizeTautomer": ["bool", True]}
2106 ProcessOptionNameValuePairParameters("--methodologyParams",
2107 Options["--methodologyParams"], ParamsDefaultInfo)
2108
2109 """
2110
2111 # Process parameters default informaton....
2112 ParamsValueInfo = {}
2113 ParamsTypeInfo = {}
2114 for ParamName in ParamsDefaultInfo:
2115 (ParamType, ParamValue) = ParamsDefaultInfo[ParamName]
2116 ParamsTypeInfo[ParamName] = ParamType
2117 ParamsValueInfo[ParamName] = ParamValue
2118
2119 if re.match("^auto$", ParamsOptionValue, re.I):
2120 return ParamsValueInfo
2121
2122 # Setup a canonical paramater names...
2123 ValidParamNames = []
2124 CanonicalParamNamesMap = {}
2125 for ParamName in sorted(ParamsValueInfo):
2126 ValidParamNames.append(ParamName)
2127 CanonicalParamNamesMap[ParamName.lower()] = ParamName
2128
2129 ParamsOptionValue = ParamsOptionValue.strip()
2130 if not ParamsOptionValue:
2131 PrintError('No valid parameter name and value pairs specified using "%s" option' % ParamsOptionName)
2132
2133 ParamsOptionValueWords = ParamsOptionValue.split(",")
2134 if len(ParamsOptionValueWords) % 2:
2135 PrintError(
2136 'The number of comma delimited paramater names and values, %d, specified using "%s" option must be an even number.'
2137 % (len(ParamsOptionValueWords), ParamsOptionName)
2138 )
2139
2140 # Validate paramater name and value pairs...
2141 for Index in range(0, len(ParamsOptionValueWords), 2):
2142 Name = ParamsOptionValueWords[Index].strip()
2143 Value = ParamsOptionValueWords[Index + 1].strip()
2144
2145 CanonicalName = Name.lower()
2146 if CanonicalName not in CanonicalParamNamesMap:
2147 PrintError(
2148 'The parameter name, %s, specified using "%s" is not a valid name. Supported parameter names: %s'
2149 % (Name, ParamsOptionName, " ".join(ValidParamNames))
2150 )
2151
2152 ParamName = CanonicalParamNamesMap[CanonicalName]
2153 ParamType = ParamsTypeInfo[ParamName]
2154 ParamValue = Value
2155
2156 if re.match("^(bool|boolean)$", ParamType, re.I):
2157 if re.match("^(yes|true)$", Value, re.I):
2158 Value = True
2159 elif re.match("^(no|false)$", Value, re.I):
2160 Value = False
2161 else:
2162 PrintError(
2163 'The parameter value, %s, specified for parameter name, %s, using "%s" option is not a valid value. Supported values: yes, no, true, or false'
2164 % (Value, Name, ParamsOptionName)
2165 )
2166 ParamValue = Value
2167 elif re.match("^(int|integer)$", ParamType, re.I):
2168 if not IsInteger(Value):
2169 PrintError(
2170 'The parameter value, %s, specified for parameter name, %s, using "%s" option must be an integer.'
2171 % (Value, Name, ParamsOptionName)
2172 )
2173 ParamValue = int(Value)
2174 elif re.match("^float$", ParamType, re.I):
2175 if not IsFloat(Value):
2176 PrintError(
2177 'The parameter value, %s, specified for parameter name, %s, using "%s" option must be a float.'
2178 % (Value, Name, ParamsOptionName)
2179 )
2180 ParamValue = float(Value)
2181 elif re.match("^file$", ParamType, re.I):
2182 if not os.path.exists(Value):
2183 PrintError(
2184 'The file, %s, specified for parameter name, %s, using "%s" option doen\'t exist.'
2185 % (Value, Name, ParamsOptionName)
2186 )
2187 ParamValue = Value
2188 elif re.match("^(str|string)$", ParamType, re.I):
2189 ParamValue = Value
2190 else:
2191 # Default to string values...
2192 PrintWarning(
2193 'The parameter type, %s, specified for parameter name, %s, using "%s" option is not supported by function ProcessOptionNameValuePairParameters. It\'s being treated as a string type...'
2194 % (ParamType, Name, ParamsOptionName)
2195 )
2196 ParamValue = Value
2197
2198 # Set value...
2199 ParamsValueInfo[ParamName] = ParamValue
2200
2201 return ParamsValueInfo
2202
2203
2204 def ReplaceHTMLEntitiesInText(Text):
2205 """Check and replace the followng HTML entities to their respective code
2206 for display in a browser: < (less than), > (greater than), & (ampersand),
2207 " (double quote), and ' (single quote).
2208
2209 Arguments:
2210 Text (str): Text value.
2211
2212 Returns:
2213 str : Modifed text value.
2214
2215 """
2216
2217 if re.search("""(<|>|&|"|')""", Text):
2218 return (
2219 Text.replace("<", "<")
2220 .replace(">", ">")
2221 .replace("&", "&")
2222 .replace('"', """)
2223 .replace("'", "'")
2224 )
2225 else:
2226 return Text
2227
2228
2229 def TruncateText(Text, Width, TrailingChars="..."):
2230 """Truncate text using specified width along with appending any trailing
2231 characters.
2232
2233 Arguments:
2234 Text (string): Input text.
2235 Width (int): Max number of characters before truncating text.
2236 Delimiter (string): Trailing characters to append or None.
2237
2238 Returns:
2239 str : Truncated text
2240
2241 """
2242
2243 if len(Text) < Width:
2244 return Text
2245
2246 TruncatedText = (Text[:Width] + TrailingChars) if not IsEmpty(TrailingChars) else Text[:Width]
2247
2248 return TruncatedText
2249
2250
2251 def WrapText(Text, Delimiter, Width):
2252 """Wrap text using specified delimiter and width.
2253
2254 Arguments:
2255 Text (string): Input text
2256 Delimiter (string): Delimiter for wrapping text
2257 Width (int): Max number of characters before wrapping text
2258
2259 Returns:
2260 str : Wrapped text
2261
2262 """
2263 WrappedText = Delimiter.join(textwrap.wrap(Text, width=Width))
2264
2265 return WrappedText