MayaChemTools

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