       1 #!/bin/env python
       2 # File: MiscUtil.py
       3 # Author: Manish Sud <msud@san.rr.com>
       4 #
       5 # Copyright (C) 2018 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
      35 __all__ = ["CheckFileExt", "CheckTextValue", "DoesSMILESFileContainTitleLine", "ExpandFileNames", "GetExamplesTextFromDocOptText",  "GetExcelStyleColumnLabel", "GetMayaChemToolsLibDataPath", "GetTextLines", "GetTextLinesWords", "GetWallClockAndProcessorTime", "GetFormattedElapsedTime", "GetFormattedFileSize", "IsEmpty", "IsFloat", "IsInteger", "IsNumber", "JoinWords", "ParseFileName", "PrintError", "PrintInfo", "PrintWarning",  "ProcessOptionInfileParameters", "ProcessOptionOutfileParameters", "ReplaceHTMLEntitiesInText", "ValidateOptionsDistinctFileNames", "ValidateOptionFileExt", "ValidateOptionFilePath", "ValidateOptionFloatValue", "ValidateOptionIntegerValue", "ValidateOptionNumberValue", "ValidateOptionNumberValues", "ValidateOptionsOutputFileOverwrite", "ValidateOptionTextValue",  "TruncateText", "WrapText"]
      36
      37 def CheckFileExt(FileName, FileExts):
      38     """Check file type based on the specified file extensions delimited by spaces.
      39
      40     Arguments:
      41         FileName (str): Name of a file.
      42         FileExts (str): Space delimited string containing valid file extensions.
      43
      44     Returns:
      45         bool : True, FileName contains a valid file extension; Otherwise, False.
      46
      47     """
      48
      49     for FileExt in FileExts.split():
      50         if re.search(r"\.%s$" % FileExt, FileName, re.IGNORECASE):
      51             return True
      52
      53     return False
      54
      55 def CheckTextValue(Value, ValidValues):
      56     """Check text value based on the specified valid values delimited by spaces.
      57
      58     Arguments:
      59         Value (str): Text value
      60         ValidValues (str): Space delimited string containing valid values.
      61
      62     Returns:
      63         bool : True, Value is valid; Otherwise, False.
      64
      65     """
      66
      67     ValidValues = re.sub(' ', '|', ValidValues)
      68     if re.match("^(%s)$" % ValidValues, Value, re.IGNORECASE):
      69         return True
      70
      71     return False
      72
      73 def GetTextLinesWords(TextFilePath, Delimiter, QuoteChar, IgnoreHeaderLine):
      74     """Parse lines in the specified text file into words in a line and return a list containing
      75     list of parsed line words.
      76
      77     Arguments:
      78         TextFilePath (str): Text file name including file path.
      79         Delimiter (str): Delimiter for parsing text lines.
      80         QuoteChar (str): Quote character for line words.
      81         IgnoreHeaderLine (bool): A flag indicating whether to ignore first
      82             valid data line corresponding to header line.
      83
      84     Returns:
      85         list : A list of lists containing parsed words for lines.
      86
      87     Notes:
      88         The lines starting with # or // are considered comment lines and are
      89         ignored during parsing along with any empty lines.
      90
      91     """
      92     TextFile = open(TextFilePath, "r")
      93     if TextFile is None:
      94         PrintError("Couldn't open text file: %s.\n" % (TextFilePath))
      95
      96     # Collect text lines...
      97     TextLines = []
      98     FirstValidLine = True
      99     for Line in TextFile:
     100         Line = Line.rstrip()
     101
     102         # Ignore empty lines...
     103         if not len(Line):
     104             continue
     105
     106         # Ignore comments...
     107         if re.match("^(#|\/\/)", Line, re.I):
     108             continue
     109
     110         # Ignore header line...
     111         if FirstValidLine:
     112             FirstValidLine = False
     113             if IgnoreHeaderLine:
     114                 continue
     115
     116         TextLines.append(Line)
     117
     118     TextFile.close()
     119
     120     # Parse text lines...
     121     TextLinesWords = []
     122
     123     TextLinesReader = csv.reader(TextLines, delimiter = Delimiter, quotechar = QuoteChar)
     124     for LineWords in TextLinesReader:
     125         TextLinesWords.append(LineWords)
     126
     127     return TextLinesWords
     128
     129 def GetTextLines(TextFilePath):
     130     """Read text lines from input file, remove new line characters  and return a list containing
     131     stripped lines.
     132
     133     Arguments:
     134         TextFilePath (str): Text file name including file path.
     135
     136     Returns:
     137         list : A list lines.
     138
     139     """
     140     TextFile = open(TextFilePath, "r")
     141     if TextFile is None:
     142         PrintError("Couldn't open text file: %s.\n" % (TextFilePath))
     143
     144     # Collect text lines...
     145     TextLines = [Line.rstrip() for Line in TextFile]
     146
     147     TextFile.close()
     148
     149     return TextLines
     150
     151 def DoesSMILESFileContainTitleLine(FileName):
     152     """Determine whether the SMILES file contain a title line based on the  presence
     153     of a string SMILES, Name or ID in the first line.
     154
     155     Arguments:
     156         FileName (str): Name of a file.
     157
     158     Returns:
     159         bool : True, File contains title line; Otherwise, False.
     160
     161     """
     162
     163     Infile = open(FileName, "r")
     164     if Infile is None:
     165         return False
     166
     167     Line = Infile.readline()
     168     Infile.close()
     169
     170     if re.search("(SMILES|Name|ID)", Line, re.I):
     171         return True
     172
     173     return False
     174
     175 def ExpandFileNames(FilesSpec, Delimiter = ","):
     176     """Expand files specification using glob module to process any * or ? wild
     177     cards in file names and return a list of expanded file names.
     178
     179     Arguments:
     180         FilesSpec (str): Files specifications
     181         Delimiter (str): Delimiter for file specifications
     182
     183     Returns:
     184         list : List of expanded file names
     185
     186     """
     187     FileNames = []
     188     if not len(FilesSpec):
     189         return FileNames
     190
     191     for FileSpec in FilesSpec.split(Delimiter):
     192         FileSpec = FileSpec.strip()
     193         if re.search("(\*|\?)", FileSpec, re.I):
     194             FileNames.extend(glob.glob(FileSpec))
     195         else:
     196             FileNames.append(FileSpec)
     197
     198     return FileNames
     199
     200 def GetExamplesTextFromDocOptText(DocOptText):
     201     """Get script usage example lines from a docopt doc string. The example text
     202     line start from a line containing `Examples:`  keyword at the beginning of the line.
     203
     204     Arguments:
     205         DocOptText (str): Doc string containing script usage examples lines starting with
     206             a line marked by `Examples:` keyword at the beginning of a line.
     207
     208     Returns:
     209         str : A string containing text lines retrieved from the examples section of
     210             DocOptText parameter.
     211
     212     """
     213
     214     ExamplesStart = re.compile("^Examples:", re.IGNORECASE)
     215     ExamplesEnd = re.compile("^(Author:|See also:|Copyright:)", re.IGNORECASE)
     216
     217     ExamplesText = 'Examples text is not available';
     218     ExamplesTextFound = False
     219
     220     for Line in DocOptText.splitlines():
     221         if ExamplesStart.match(Line):
     222             ExamplesText = 'Examples:';
     223             ExamplesTextFound = True
     224             continue
     225
     226         if ExamplesEnd.match(Line):
     227             break
     228
     229         if ExamplesTextFound:
     230             ExamplesText += "\n" + Line;
     231
     232     return ExamplesText
     233
     234 def GetExcelStyleColumnLabel(ColNum):
     235     """Return Excel style column label for a colum number.
     236
     237     Arguments:
     238         ColNum (int): Column number
     239
     240     Returns:
     241         str : Excel style column label.
     242
     243     """
     244     Letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
     245
     246     ColLabelList = []
     247     while ColNum:
     248         ColNum, SubColNum = divmod(ColNum - 1, 26)
     249         ColLabelList[:0] = Letters[SubColNum]
     250
     251     return ''.join(ColLabelList)
     252
     253 def GetWallClockAndProcessorTime():
     254     """Get wallclock and processor times in seconds.
     255
     256     Returns:
     257         float : Wallclock time.
     258         float : Processor time.
     259
     260     """
     261     return (time.time(), time.clock())
     262
     263 def GetMayaChemToolsLibDataPath():
     264     """Get location of MayaChemTools lib data directory.
     265
     266     Returns:
     267         str : Location of MayaChemTools lib data directory.
     268
     269     Notes:
     270         The location of MayaChemTools lib data directory is determined relative to
     271         MayaChemTools python lib directory name available through sys.path.
     272
     273     """
     274     MayaChemToolsDataPath = ""
     275
     276     for PathEntry in sys.path:
     277         if re.search("MayaChemTools", PathEntry, re.I) and re.search("Python", PathEntry, re.I):
     278             MayaChemToolsDataPath = os.path.join( PathEntry, "..",  "data")
     279             break
     280         else:
     281             PrintInfo("PathEntry didn't match")
     282
     283     if not len(MayaChemToolsDataPath):
     284         PrintWarning("MayaChemTools lib directory location doesn't appear to exist in system search path specified by sys.path...")
     285
     286     return MayaChemToolsDataPath
     287
     288 def GetFormattedElapsedTime(StartingWallClockTime, StartingProcessorTime):
     289     """Get elapsed wallclock and processor times  as a string in the following
     290     format: %d wallclock secs ( %.2f process secs).
     291
     292     Arguments:
     293         StartingWallClockTime (float): Starting wallclock time in seconds.
     294         StartingProcessorTime (float): Starting processor time in seconds.
     295
     296     Returns:
     297         str : Elapsed time formatted as: %d wallclock secs ( %.2f process secs)
     298
     299     """
     300
     301     ElapsedWallClockTime = time.time() - StartingWallClockTime
     302     ElapsedProcessorTime = time.clock() - StartingProcessorTime
     303
     304     ElapsedTime = "%d wallclock secs ( %.2f process secs)" % (ElapsedWallClockTime, ElapsedProcessorTime)
     305
     306     return ElapsedTime
     307
     308 def GetFormattedFileSize(FileName, Precision = 1):
     309     """Get file size  as a string in the following format: %.*f <bytes, KB, MB,
     310     GB>
     311
     312     Arguments:
     313         FileName (str): File path.
     314         Precision (int): File size precision.
     315
     316     Returns:
     317         str : File size formatted as: %.2f <bytes, KB, MB, GB>
     318
     319     """
     320
     321     Size = os.path.getsize(FileName)
     322
     323     if Size < 1024:
     324         SizeDenominator = 1
     325         SizeSuffix = "bytes"
     326     elif Size < (1024*1024):
     327         SizeDenominator = 1024
     328         SizeSuffix = "KB"
     329     elif Size < (1024*1024*1024):
     330         SizeDenominator = 1024*1024
     331         SizeSuffix = "MB"
     332     elif Size < (1024*1024*1024*1024):
     333         SizeDenominator = 1024*1024*1024
     334         SizeSuffix = "GB"
     335     else:
     336         SizeDenominator = 1
     337         SizeSuffix = "bytes"
     338
     339     Size /= SizeDenominator
     340
     341     FormattedSize = "%.*f %s" % (Precision, Size, SizeSuffix)
     342
     343     return FormattedSize
     344
     345 def IsEmpty(Value):
     346     """Determine whether the specified value is empty after converting
     347     it in to a string and removing all leading and trailing white spaces. A  value
     348     of type None is considered empty.
     349
     350     Arguments:
     351         Value (str, int or float): Text or a value
     352
     353     Returns:
     354         bool : True, Text string is empty; Otherwsie, False.
     355
     356     """
     357
     358     if Value is None:
     359         return True
     360
     361     TextValue = "%s" % Value
     362     TextValue = TextValue.strip()
     363
     364     return False if len(TextValue) else True
     365
     366 def IsFloat(Value):
     367     """Determine whether the specified value is a float by converting it
     368     into a float.
     369
     370     Arguments:
     371         Value (str, int or float): Text
     372
     373     Returns:
     374         bool : True, Value is a float; Otherwsie, False.
     375
     376     """
     377
     378     return IsNumber(Value)
     379
     380 def IsInteger(Value):
     381     """Determine whether the specified value is an integer by converting it
     382     into an int.
     383
     384     Arguments:
     385         Value (str, int or float): Text
     386
     387     Returns:
     388         bool : True, Value is an integer; Otherwsie, False.
     389
     390     """
     391
     392     Status = True
     393
     394     if Value is None:
     395         return False
     396
     397     try:
     398         Value = int(Value)
     399         Status = True
     400     except ValueError:
     401         Status = False
     402
     403     return Status
     404
     405 def IsNumber(Value):
     406     """Determine whether the specified value is a number by converting it
     407     into a float.
     408
     409     Arguments:
     410         Value (str, int or float): Text
     411
     412     Returns:
     413         bool : True, Value is a number; Otherwsie, False.
     414
     415     """
     416
     417     Status = True
     418
     419     if Value is None:
     420         return Status
     421
     422     try:
     423         Value = float(Value)
     424         Status = True
     425     except ValueError:
     426         Status = False
     427
     428     return Status
     429
     430 def JoinWords(Words, Delimiter, Quote = False):
     431     """Join words in a list using specified delimiter with optional quotes around words.
     432
     433     Arguments:
     434         Words (list): List containing words to join.
     435         Delimiter (string): Delimiter for joining words.
     436         Quote (boolean): Put quotes around words.
     437
     438     Returns:
     439         str : String containing joined words.
     440
     441     """
     442
     443     if Quote:
     444         JoinedWords = Delimiter.join('"{0}"'.format(Word) for Word in Words)
     445     else:
     446         JoinedWords = Delimiter.join(Words)
     447
     448     return JoinedWords
     449
     450 def ParseFileName(FilePath):
     451     """Parse specified file path and return file dir, file name, and file extension.
     452
     453     Arguments:
     454         FilePath (str): Name of a file with complete file path.
     455
     456     Returns:
     457         str : File directory.
     458         str : File name without file extension.
     459         str : File extension.
     460
     461     """
     462     FileDir, FileBaseName = os.path.split(FilePath)
     463     FileName, FileExt = os.path.splitext(FileBaseName)
     464
     465     if re.match("^\.", FileExt):
     466         FileExt = re.sub("^\.", "", FileExt)
     467
     468     return (FileDir, FileName, FileExt)
     469
     470 def PrintError(Msg, Status=1):
     471     """Print message to stderr along with flushing stderr and exit with a specified
     472     status. An `Error` prefix is placed before the message.
     473
     474     Arguments:
     475         Msg (str): Text message.
     476         Status (int): Exit status.
     477
     478     """
     479
     480     PrintInfo("Error: %s" % Msg)
     481     sys.exit(Status)
     482
     483 def PrintInfo(Msg=''):
     484     """Print message to stderr along with flushing stderr.
     485
     486     Arguments:
     487         Msg (str): Text message.
     488
     489     """
     490
     491     print(Msg, sep=' ', end='\n', file=sys.stderr)
     492     sys.stderr.flush()
     493
     494 def PrintWarning(msg):
     495     """Print message to stderr along with flushing stderr. An `Warning` prefix
     496     is placed before the message.
     497
     498     Arguments:
     499         Msg (str): Text message.
     500
     501     """
     502
     503     PrintInfo("Warning: %s" % msg)
     504
     505 def ValidateOptionFileExt(OptionName, FileName, FileExts):
     506     """Validate file type based on the specified file extensions delimited by spaces.
     507
     508     Arguments:
     509         OptionName (str): Command line option name.
     510         FileName (str): Name of a file.
     511         FileExts (str): Space delimited string containing valid file extensions.
     512
     513     Notes:
     514         The function exits with an error message for a file name containing
     515         invalid file extension.
     516
     517     """
     518
     519     if not CheckFileExt(FileName, FileExts):
     520         PrintError("The file name specified , %s, for option \"%s\" is not valid. Supported file formats: %s\n" % (FileName, OptionName, FileExts))
     521
     522 def ValidateOptionFilePath(OptionName, FilePath):
     523     """Validate presence of the file.
     524
     525     Arguments:
     526         OptionName (str): Command line option name.
     527         FilePath (str): Name of a file with complete path.
     528
     529     Notes:
     530         The function exits with an error message for a file path that doesn't exist.
     531
     532     """
     533
     534     if not os.path.exists(FilePath):
     535         PrintError("The file specified, %s, for option \"%s\" doesn't exist.\n" % (FilePath, OptionName))
     536
     537 def ValidateOptionFloatValue(OptionName, OptionValue, CmpOpValueMap):
     538     """Validate option value using comparison operater and value pairs in specified in
     539     a map.
     540
     541     Arguments:
     542         OptionName (str): Command line option name.
     543         OptionValue (float or str): Command line option value.
     544         CmpOpValueMap (dictionary): Comparison operator key and value pairs to
     545             validate values specified in OptionValue.
     546
     547     Notes:
     548         The function exits with an error message for an invalid option values specified
     549         in OptionValue.
     550
     551     Examples:
     552
     553         ValidateOptionNumberValue("-b, --butinaSimilarityCutoff",
     554             Options["--butinaSimilarityCutoff"],
     555             {">": 0.0, "<=" : 1.0})
     556
     557     """
     558
     559     if not IsFloat(OptionValue):
     560         PrintError("The value specified, %s, for option \"%s\" must be a float." % (OptionValue, OptionName))
     561
     562     return ValidateOptionNumberValue(OptionName, float(OptionValue), CmpOpValueMap)
     563
     564 def ValidateOptionIntegerValue(OptionName, OptionValue, CmpOpValueMap):
     565     """Validate option value using comparison operater and value pairs in specified in
     566     a map.
     567
     568     Arguments:
     569         OptionName (str): Command line option name.
     570         OptionValue (int or str): Command line option value.
     571         CmpOpValueMap (dictionary): Comparison operator key and value pairs to
     572             validate values specified in OptionValue.
     573
     574     Notes:
     575         The function exits with an error message for an invalid option values specified
     576         in OptionValue.
     577
     578     Examples:
     579
     580         ValidateOptionIntegerValue("--maxConfs", Options["--maxConfs"],
     581             {">": 0})
     582
     583     """
     584
     585     if not IsInteger(OptionValue):
     586         PrintError("The value specified, %s, for option \"%s\" must be an integer." % (OptionValue, OptionName))
     587
     588     return ValidateOptionNumberValue(OptionName, int(OptionValue), CmpOpValueMap)
     589
     590 def ValidateOptionNumberValue(OptionName, OptionValue, CmpOpValueMap):
     591     """Validate option value using comparison operater and value pairs in specified in
     592     a map.
     593
     594     Arguments:
     595         OptionName (str): Command line option name.
     596         OptionValue (int or float): Command line option value.
     597         CmpOpValueMap (dictionary): Comparison operator key and value pairs to
     598             validate values specified in OptionValue.
     599
     600     Notes:
     601         The function exits with an error message for an invalid option values specified
     602         in OptionValue.
     603
     604     Examples:
     605
     606         ValidateOptionNumberValue("--maxConfs", int(Options["--maxConfs"]),
     607             {">": 0})
     608         ValidateOptionNumberValue("-b, --butinaSimilarityCutoff",
     609             float(Options["--butinaSimilarityCutoff"]),
     610             {">": 0.0, "<=" : 1.0})
     611
     612     """
     613
     614     Status = True
     615     for CmpOp in CmpOpValueMap:
     616         Value = CmpOpValueMap[CmpOp]
     617         if re.match("^>$", CmpOp, re.I):
     618             if OptionValue <= Value:
     619                 Status = False
     620                 break
     621         elif re.match("^>=$", CmpOp, re.I):
     622             if OptionValue < Value:
     623                 Status = False
     624                 break
     625         elif re.match("^<$", CmpOp, re.I):
     626             if OptionValue >= Value:
     627                 Status = False
     628                 break
     629         elif re.match("^<=$", CmpOp, re.I):
     630             if OptionValue > Value:
     631                 Status = False
     632                 break
     633         else:
     634             PrintError("The specified comparison operator, %s, for function MiscUtil.ValidateOptionNumberValue is not supported\n" % (CmpOp))
     635
     636     if not Status:
     637         FirstValue = True
     638         SupportedValues = ""
     639         for CmpOp in CmpOpValueMap:
     640             Value = CmpOpValueMap[CmpOp]
     641             if FirstValue:
     642                 FirstValue = False
     643                 SupportedValues = "%s %s" % (CmpOp, Value)
     644             else:
     645                 SupportedValues = "%s and %s %s" % (SupportedValues, CmpOp, Value)
     646
     647         PrintError("The value specified, %s, for option \"%s\" is not valid. Supported value(s): %s " % (OptionValue, OptionName, SupportedValues))
     648
     649 def ValidateOptionNumberValues(OptionName, OptionValueString, OptionValueCount, OptionValueDelimiter, OptionValueType, CmpOpValueMap):
     650     """Validate numerical option values using option value string, delimiter, value type,
     651     and a specified map containing comparison operator and value pairs.
     652
     653     Arguments:
     654         OptionName (str): Command line option name.
     655         OptionValueString (str): Command line option value.
     656         OptionValueCount (int): Number of values in OptionValueString.
     657         OptionValueDelimiter (str): Delimiter used for values in OptionValueString.
     658         OptionValueType (str): Valid number types (integer or float)
     659         CmpOpValueMap (dictionary): Comparison operator key and value pairs to
     660             validate values specified in OptionValueString.
     661
     662     Notes:
     663         The function exits with an error message for invalid option values specified
     664         in OptionValueString
     665
     666     Examples:
     667
     668         ValidateOptionNumberValues("-m, --molImageSize",
     669             Options["--molImageSize"], 2, ",", "integer", {">": 0})
     670
     671     """
     672     if not CheckTextValue(OptionValueType, "integer float"):
     673         PrintError("The option value type specified, %s, for function MiscUtil.ValidateOptionNumberValues  is not valid. Supported value: integer float " % (OptionValueType))
     674
     675     Values = OptionValueString.split(OptionValueDelimiter)
     676     if len(Values) != OptionValueCount:
     677         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))
     678
     679     IsIntergerValue = True
     680     if re.match("^float$", OptionValueType, re.I):
     681         IsIntergerValue = False
     682
     683     for Value in Values:
     684         if IsIntergerValue:
     685             if not IsInteger(Value):
     686                 PrintError("The value specified, %s, for option \"%s\" in string \"%s\" must be an integer." % (Value, OptionName, OptionValueString))
     687             Value = int(Value)
     688         else:
     689             if not IsFloat(Value):
     690                 PrintError("The value specified, %s, for option \"%s\" in string \"%s\" must be a float." % (Value, OptionName, OptionValueString))
     691             Value = float(Value)
     692         ValidateOptionNumberValue(OptionName, Value, CmpOpValueMap)
     693
     694 def ValidateOptionTextValue(OptionName, OptionValue, ValidValues):
     695     """Validate option value based on the valid specified values separated by spaces.
     696
     697     Arguments:
     698         OptionName (str): Command line option name.
     699         OptionValue (str): Command line option value.
     700         ValidValues (str): Space delimited string containing valid values.
     701
     702     Notes:
     703         The function exits with an error message for an invalid option value.
     704
     705     """
     706
     707     if not CheckTextValue(OptionValue, ValidValues):
     708         PrintError("The value specified, %s, for option \"%s\" is not valid. Supported value(s): %s " % (OptionValue, OptionName, ValidValues))
     709
     710 def ValidateOptionsOutputFileOverwrite(OptionName, FilePath, OverwriteOptionName, OverwriteStatus):
     711     """Validate overwriting of output file.
     712
     713     Arguments:
     714         OptionName (str): Command line option name.
     715         FilePath (str): Name of a file with complete file path.
     716         OverwriteOptionName (str): Overwrite command line option name.
     717         OverwriteStatus (bool): True, overwrite
     718
     719     Notes:
     720         The function exits with an error message for a file that is present and is not allowed
     721         to be written as indicated by value of OverwriteStatus.
     722
     723     """
     724
     725     if os.path.exists(FilePath):
     726         if not OverwriteStatus:
     727             if len(OverwriteOptionName) > 4:
     728                 ShortOverwriteOptionName = OverwriteOptionName[:4]
     729             else:
     730                 ShortOverwriteOptionName = OverwriteOptionName
     731
     732             PrintError("The file specified, %s, for option \"%s\" already exist. Use option \"%s\" or \"%s\" and try again.\n" % (FilePath, OptionName, ShortOverwriteOptionName, OverwriteOptionName))
     733
     734 def ValidateOptionsDistinctFileNames(OptionName1, FilePath1, OptionName2, FilePath2):
     735     """Validate two distinct file names.
     736
     737     Arguments:
     738         OptionName1 (str): Command line option name.
     739         FilePath1 (str): Name of a file with complete file path.
     740         OptionName2 (str): Command line option name.
     741         FilePath2 (str): Name of a file with complete file path.
     742
     743     Notes:
     744         The function exits with an error message for two non distinct file names.
     745
     746     """
     747
     748     FilePath1Pattern = r"^" + re.escape(FilePath1) + r"$";
     749     if re.match(FilePath1Pattern, FilePath2, re.I):
     750         PrintError("The file name specified, %s, for options \"%s\" and \"%s\" must be different.\n" % (FilePath1, OptionName1, OptionName2))
     751
     752 def ProcessOptionInfileParameters(ParamsOptionName, ParamsOptionValue, InfileName = None, OutfileName = None):
     753     """Process parameters for reading input files and return a map containing
     754     processed parameter names and values.
     755
     756     Arguments:
     757         ParamsOptionName (str): Command line input parameters option name.
     758         ParamsOptionValues (str): Comma delimited list of parameter name and value pairs.
     759         InfileName (str): Name of input file.
     760         OutfileName (str): Name of output file.
     761
     762     Returns:
     763         dictionary: Processed parameter name and value pairs.
     764
     765     Notes:
     766         The parameter name and values specified in ParamsOptionValues are validated before
     767         returning them in a dictionary.
     768
     769     """
     770
     771     ParamsInfo = {'RemoveHydrogens': True, 'Sanitize' : True, 'StrictParsing': True,  'SMILESColumn': 1, 'SMILESNameColumn' : 2, 'SMILESDelimiter': ' ', 'SMILESTitleLine': 'auto'}
     772     _ProcessInfileAndOutfileParameters('Infile', ParamsInfo, ParamsOptionName, ParamsOptionValue, InfileName, OutfileName,)
     773
     774     return ParamsInfo
     775
     776 def ProcessOptionOutfileParameters(ParamsOptionName, ParamsOptionValue, InfileName = None, OutfileName = None):
     777     """Process parameters for writing output files and return a map containing
     778     processed parameter names and values.
     779
     780     Arguments:
     781         ParamsOptionName (str): Command line input parameters option name.
     782         ParamsOptionValues (str): Comma delimited list of parameter name and value pairs.
     783         InfileName (str): Name of input file.
     784         OutfileName (str): Name of output file.
     785
     786     Returns:
     787         dictionary: Processed parameter name and value pairs.
     788
     789     Notes:
     790         The parameter name and values specified in ParamsOptionValues are validated before
     791         returning them in a dictionary.
     792
     793         The default value of some parameters may depend on type of input file. Consequently,
     794         the input file name is also needed.
     795
     796     """
     797     ParamsInfo = {'Compute2DCoords' : 'auto', 'Kekulize': False, 'SMILESDelimiter': ' ', 'SMILESIsomeric': True,  'SMILESTitleLine': True}
     798     _ProcessInfileAndOutfileParameters('Outfile', ParamsInfo, ParamsOptionName, ParamsOptionValue, InfileName, OutfileName)
     799
     800     return ParamsInfo
     801
     802 def _ProcessInfileAndOutfileParameters(Mode, ParamsInfo, ParamsOptionName, ParamsOptionValue, InfileName, OutfileName):
     803     """Process specified infile and outfile paramaters.
     804
     805     """
     806     if re.match("^auto$", ParamsOptionValue, re.I):
     807         # No specific parameters to process except for parameters with possible auto value...
     808         _ProcessInfileAndOutfileAutoParameters(Mode, ParamsInfo, ParamsOptionName, ParamsOptionValue, InfileName, OutfileName)
     809         return
     810
     811     ParamsOptionValue = re.sub(" ", "", ParamsOptionValue)
     812     if not ParamsOptionValue:
     813         PrintError("No valid parameter name and value pairs specified using \"%s\" option" % ParamsOptionName)
     814
     815     ParamsOptionValueWords = ParamsOptionValue.split(",")
     816     if len(ParamsOptionValueWords) % 2:
     817         PrintError("The number of comma delimited paramater names and values, %d, specified using \"%s\" option must be an even number." % (len(ParamsOptionValueWords), ParamsOptionName))
     818
     819     # Setup a canonical paramater names...
     820     ValidParamNames = []
     821     CanonicalParamNamesMap = {}
     822     for ParamName in sorted(ParamsInfo):
     823         ValidParamNames.append(ParamName)
     824         CanonicalParamNamesMap[ParamName.lower()] = ParamName
     825
     826     # Validate paramater name and value pairs...
     827     for Index in range(0, len(ParamsOptionValueWords), 2):
     828         Name = ParamsOptionValueWords[Index]
     829         Value = ParamsOptionValueWords[Index + 1]
     830
     831         CanonicalName = Name.lower()
     832         if  not CanonicalName in CanonicalParamNamesMap:
     833             PrintError("The parameter name, %s, specified using \"%s\" is not a valid name. Supported parameter names: %s" % (Name, ParamsOptionName, " ".join(ValidParamNames)))
     834
     835         ParamName = CanonicalParamNamesMap[CanonicalName]
     836         ParamValue = Value
     837
     838         if re.match("^(Sanitize|StrictParsing|RemoveHydrogens|Kekulize|SMILESIsomeric)$", ParamName, re.I):
     839             if not re.match("^(Yes|No|True|False)$", Value, re.I):
     840                 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))
     841             ParamValue = True
     842             if re.match("^(No|False)$", Value, re.I):
     843                 ParamValue = False
     844         elif re.match("^SMILESTitleLine$", ParamName, re.I):
     845             if re.match("^Infile$", Mode, re.I):
     846                 if not re.match("^(Yes|No|True|False|Auto)$", Value, re.I):
     847                     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))
     848             elif re.match("^Outfile$", Mode, re.I):
     849                 if not re.match("^(Yes|No|True|False)$", Value, re.I):
     850                     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))
     851                 ParamValue = True
     852                 if re.match("^(No|False)$", Value, re.I):
     853                     ParamValue = False
     854         elif re.match("^SMILESDelimiter$", ParamName, re.I):
     855             if not re.match("^(space|tab|comma)$", Value, re.I):
     856                 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))
     857             ParamValue = " "
     858             if re.match("^tab$", Value, re.I):
     859                 ParamValue = "\t"
     860             elif re.match("^comma$", Value, re.I):
     861                 ParamValue = ","
     862         elif re.match("^Compute2DCoords$", ParamName, re.I):
     863             # No need to set the value. It would be processed later to handle "auto" value...
     864             if not re.match("^(Yes|No|True|False|Auto)$", Value, re.I):
     865                 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))
     866         else:
     867             ParamValue = int(Value)
     868             if ParamValue <= 0:
     869                 PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is not a valid value. Supported values: > 0" % (Value, Name, ParamsOptionName))
     870
     871         # Set value...
     872         ParamsInfo[ParamName] = ParamValue
     873
     874     # Handle paramaters with possible auto values...
     875     _ProcessInfileAndOutfileAutoParameters(Mode, ParamsInfo, ParamsOptionName, ParamsOptionValue, InfileName, OutfileName)
     876
     877 def _ProcessInfileAndOutfileAutoParameters(Mode, ParamsInfo, ParamsOptionName, ParamsOptionValue, InfileName, OutfileName):
     878     """Process parameters with possible auto values.
     879
     880     """
     881     if re.match("^Infile$", Mode, re.I):
     882         # SMILESTitleLine parameter...
     883         Value = ParamsInfo["SMILESTitleLine"]
     884         ParamValue = False
     885         if re.match("^auto$", Value, re.I):
     886             if InfileName is not None:
     887                 if CheckFileExt(InfileName, "smi csv tsv txt"):
     888                     ParamValue = DoesSMILESFileContainTitleLine(InfileName)
     889         elif re.match("^(Yes|True)$", Value, re.I):
     890             ParamValue = True
     891         ParamsInfo["SMILESTitleLine"] = ParamValue
     892     elif re.match("^Outfile$", Mode, re.I):
     893         # Compute2DCoords parameter...
     894         Value = ParamsInfo["Compute2DCoords"]
     895         ParamValue = False
     896         if re.match("^auto$", Value, re.I):
     897             if InfileName is not None:
     898                 if CheckFileExt(InfileName, "smi csv tsv txt"):
     899                     ParamValue = True
     900             if OutfileName is not None:
     901                 if CheckFileExt(OutfileName, "smi csv tsv txt"):
     902                     # No need to compute 2D coords for SMILES file...
     903                     ParamValue = False
     904         elif re.match("^(Yes|True)$", Value, re.I):
     905             ParamValue = True
     906         ParamsInfo["Compute2DCoords"] = ParamValue
     907
     908 def ReplaceHTMLEntitiesInText(Text):
     909     """Check and replace the followng HTML entities to their respective code
     910     for display in a browser: < (less than), > (greater than), & (ampersand),
     911     " (double quote),  and ' (single quote).
     912
     913     Arguments:
     914         Text (str): Text value.
     915
     916     Returns:
     917         str : Modifed text value.
     918
     919     """
     920
     921     if re.search("""(<|>|&|"|')""", Text):
     922         return Text.replace("<", "&lt;").replace(">", "&gt;").replace("&", "&amp;").replace('"', "&quot;").replace("'","&apos;")
     923     else:
     924         return Text
     925
     926 def TruncateText(Text, Width, TrailingChars = "..."):
     927     """Truncate text using specified width along with appending any trailing
     928     characters.
     929
     930     Arguments:
     931         Text (string): Input text.
     932         Width (int): Max number of characters before truncating text.
     933         Delimiter (string): Trailing characters to append or None.
     934
     935     Returns:
     936         str : Truncated text
     937
     938     """
     939
     940     if len(Text) < Width:
     941         return Text
     942
     943     TruncatedText = (Text[:Width] + TrailingChars) if not IsEmpty(TrailingChars) else Text[:Width]
     944
     945     return TruncatedText
     946
     947 def WrapText(Text, Delimiter, Width):
     948     """Wrap text using specified delimiter and width.
     949
     950     Arguments:
     951         Text (string): Input text
     952         Delimiter (string): Delimiter for wrapping text
     953         Width (int): Max number of characters before wrapping text
     954
     955     Returns:
     956         str : Wrapped text
     957
     958     """
     959     WrappedText = Delimiter.join(textwrap.wrap(Text, width = Width))
     960
     961     return WrappedText