MayaChemTools

    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("<", "&lt;")
 2220             .replace(">", "&gt;")
 2221             .replace("&", "&amp;")
 2222             .replace('"', "&quot;")
 2223             .replace("'", "&apos;")
 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