## @file # generate UQI (Universal Question Identifier) unicode string for HII question PROMPT string. UQI string can be used to # identify each HII question. # # Copyright (c) 2019, Intel Corporation. All rights reserved. # # SPDX-License-Identifier: BSD-2-Clause-Patent # import re import sys import os import getopt import codecs import fnmatch import logging import argparse # global variable declarations QuestionError = False FileHeader = '//\r\n// FILE auto-generated by UniTool\r\n//\r\n\r\n#langdef uqi "uqi"\r\n\r\n' UqiList = re.compile('^#string[ \t]+([A-Z_0-9]+)[ \t]+#language[ \t]+uqi[ \t\r\n]+"(?:[x\S]{1,2})([0-9a-fA-F]{4,5})"', re.M).findall AllUqis = {} StringDict = {} GlobalVarId = {} Options = {} # Version message __prog__ = 'UniTool' __description__ = 'The script generate UQI unicode string for HII question PROMPT string.\n' __copyright__ = 'Copyright (c) 2019, Intel Corporation. All rights reserved.
' __version__ = '%s Version %s' % (__prog__, '0.1 ') _Usage = "Syntax: %s [-b] [-u] [-l] [-x] [-h] [-d 'rootDirectory1'] [-d 'rootDirectory2'] [-d 'rootDirectory3']... \n[-q e|w]" \ "'rootDirectory0' 'uqiFile'|'uqiFileDirectory' ['excludedDirectory1'] ['excludedDirectory2'] ['excludedDirectory3']...\n" \ % (os.path.basename(sys.argv[0])) # ********************************************************************** # description: Get uni file encoding # # arguments: Filename - name of uni file # # returns: utf-8 or utf-16 # def GetUniFileEncoding(Filename): # # Detect Byte Order Mark at beginning of file. Default to UTF-8 # Encoding = 'utf-8' # # Read file # try: with open(Filename, mode='rb') as UniFile: FileIn = UniFile.read() except: return Encoding if (FileIn.startswith(codecs.BOM_UTF16_BE) or FileIn.startswith(codecs.BOM_UTF16_LE)): Encoding = 'utf-16' return Encoding # rewrite function os.path.walk def Walk(Top, Func, Arg): try: Names = os.listdir(Top) except os.error: return Func(Arg, Top, Names) for Name in Names: Name = os.path.join(Top, Name) if os.path.isdir(Name): Walk(Name, Func, Arg) # ********************************************************************** # description: Parses commandline arguments and options # Calls function processUni to build dictionary of strings # Calls other functions according to user specified options # # arguments: argv - contains all input from command line # - must contain path to root directory # - may contain options -h, -u, -l, -b or -x before path # # returns: none # def main(): ##### Read input arguments and options global AllUqis, UqiList, QuestionError parser = argparse.ArgumentParser(prog=__prog__, description=__description__ + __copyright__, usage=_Usage, conflict_handler='resolve') parser.add_argument('Path', nargs='+', help='the path for files to be converted.It could be directory or file path.') parser.add_argument('-v', '--version', action='version', version=__version__, help="show program's version number and exit") parser.add_argument('-b', '--build', action='store_true', dest='BuildOption', help="Build option returns error if any new UQI needs assigning " \ "based on vfi/vfr/hfr/sd/sdi when no -u option is specified") parser.add_argument('-u', '--updata', action='store_true', dest='UpdateUQIs', help="Create new UQIs that does not already exist in uqiFile for" \ "any string requiring a UQI based on vfi/vfr/hfr/sd/sdi" \ "NOTE: 'uqiFile' cannot be readonly!") parser.add_argument('-l', '--lang', action='store_true', dest='LangOption', help="Language deletion option (keeps only English and uqi)" \ "moves all UQIs to 'uqiFile', NOTE: Uni files cannot be readonly!") parser.add_argument('-x', '--exclude', action='store_true', dest='ExcludeOption', help="Exclude 'rootDirectory'/'excludedDirectory1' &" \ "'rootDirectory'/'excludedDirectory2'... from UQI list build") parser.add_argument('-d', '--dir', action='append', metavar='FILEDIR', dest='DirName', help="Add multiple root directories to process") parser.add_argument('-q', '--question', dest='Question', choices=['w', 'e'], help="Print warning(w) or return error(e) if different HII questions" \ "are referring same string token") Opts = parser.parse_args() Destname = '' DirNameList = [] ExDirList = [] if Opts.Path: DirNameList.append(Opts.Path[0]) Destname = Opts.Path[1] ExDirList = Opts.Path[2:] if Opts.DirName: DirNameList.extend(Opts.DirName) QuestionOption = Opts.Question ExcludeOption = Opts.ExcludeOption BuildOption = Opts.BuildOption UpdateUQIs = Opts.UpdateUQIs LangOption = Opts.LangOption ExPathList = [] if ExDirList: try: for EachExDir in ExDirList: for EachRootDir in DirNameList: if EachExDir == EachRootDir: print("\nERROR: excludedDirectory is same as rootDirectory\n") return ExPathList.append(EachRootDir + os.sep + EachExDir) except: print(_Usage) return global Options Options = {'Destname': Destname, 'DirNameList': DirNameList, 'ExPathList': ExPathList, 'BuildOption': BuildOption, 'UpdateUQIs': UpdateUQIs, 'LangOption': LangOption, 'ExcludeOption': ExcludeOption, 'QuestionOption': QuestionOption} print("UQI file: %s" % Destname) for EachDirName in DirNameList: Walk(EachDirName, processUni, None) if QuestionError: return if os.path.isdir(Options['Destname']): DestFileName = Options['Destname'] + os.sep + 'UqiList.uni' else: DestFileName = Options['Destname'] if os.path.exists(DestFileName) and (DestFileName not in list(AllUqis.keys())): try: Encoding = GetUniFileEncoding(DestFileName) with codecs.open(DestFileName, 'r+', Encoding) as destFile: DestFileBuffer = destFile.read() except IOError as e: print("ERROR: " + e.args[1]) return AllUqis[DestFileName] = UqiList(DestFileBuffer) if BuildOption: ReturnVal = newUqi() if (ReturnVal == 1): print('Please fix UQI ERROR(s) above before proceeding.') else: print("No UQI issues detected\n") return # ********************************************************************** # description: newUqi collects a list of all currently used uqi values in the tree # Halt build if any duplicated string or value in UQI list. # If -u option was specified, creates new UQIs that does not # already exist in uqiFile for any string requiring a UQI. # # arguments: none # # returns: 0 on success # 1 on error - this should cause the build to halt # Syntax = "S" SyntaxRE = re.compile('#string[ \t]+[A-Z_0-9]+[ \t]+#language[ \t]+uqi[ \t\r\n]+"([x\S]{1,2}).*', re.DOTALL).findall def newUqi(): global Options, GlobalVarId, AllUqis, Syntax, SyntaxRE UqiRange = [] UqiStringList = [] CreateUQI = [] ReturnVal = 0 BaseNumSpaces = 47 # Used to line up the UQI values in the resulting uqiFile # Look for duplication in the current UQIs and collect current range of UQIs for path in AllUqis.keys(): for UqiString in AllUqis[path]: # path contains the path and Filename of each uni file # Checks for duplicated strings in UQI list for TempString in UqiStringList: if TempString == UqiString[0]: print("ERROR: UQI string %s was assigned more than once and will cause corruption!" % UqiString[0]) print("Delete one occurrence of the string and rerun tool.") ReturnVal = 1 # halt build UqiStringList.append(UqiString[0]) # Checks for duplicated UQI values in UQI list if int(UqiString[1], 16) in UqiRange: print("ERROR: UQI value %04x was assigned more than once and will cause corruption!" % int(UqiString[1], 16)) print("Delete one occurrance of the UQI and rerun tool to create alternate value.") ReturnVal = 1 # halt build UqiRange.append(int(UqiString[1], 16)) for StringValue in GlobalVarId.keys(): StringFound = False for path in StringDict.keys(): for UniString in StringDict[path]: # path contains the path and Filename of each uni file if (StringValue == UniString): StringFound = True break if not StringFound: print("ERROR: No definition for %s referred by HII question" % (StringValue)) ReturnVal = 1 # halt build # Require a UQI for any string in vfr/vfi files for StringValue in GlobalVarId.keys(): # Ignore strings defined as STRING_TOKEN(0) if (StringValue != "0"): # Check if this string already exists in the UQI list if (StringValue not in UqiStringList) and (StringValue not in CreateUQI): CreateUQI.append(StringValue) if not Options['UpdateUQIs']: print("ERROR: No UQI for %s referred by HII question" % (StringValue)) ReturnVal = 1 # halt build after printing all error messages if (ReturnVal == 1): return ReturnVal # Update uqiFile with necessary UQIs if Options['UpdateUQIs'] and CreateUQI: if os.path.isdir(Options['Destname']): DestFileName = Options['Destname'] + os.sep + 'UqiList.uni' else: DestFileName = Options['Destname'] try: Encoding = GetUniFileEncoding(DestFileName) with codecs.open(DestFileName, 'r+', Encoding) as OutputFile: PlatformUQI = OutputFile.read() except IOError as e: print("ERROR: " + e.args[1]) if (e.args[0] == 2): try: with codecs.open(DestFileName, 'w', Encoding) as OutputFile: print(DestFileName + " did not exist. Creating new file.") PlatformUQI = FileHeader except: print("Error creating " + DestFileName + ".") return 1 if (e.args[1] == "Permission denied"): print( "\n%s is Readonly. You must uncheck the ReadOnly attibute to run the -u option.\n" % DestFileName) return 1 # Determines and sets the UQI number format # TODO: there is probably a more elegant way to do this... SyntaxL = SyntaxRE(PlatformUQI) if len(SyntaxL) != 0: Syntax = SyntaxL[0] # script is reading the file in and writing it back instead of appending because the codecs module # automatically adds a BOM wherever you start writing. This caused build failure. UqiRange.sort() if (UqiRange == []): NextUqi = 0 else: NextUqi = UqiRange[len(UqiRange) - 1] + 1 for StringValue in CreateUQI: print("%s will be assigned a new UQI value" % StringValue) UqiRange.append(NextUqi) # # Lines up the UQI values in the resulting uqiFile # Spaces = " " * (BaseNumSpaces - len(StringValue)) PlatformUQI += '#string %s%s #language uqi \"%s%04x\"\r\n' % (StringValue, Spaces, Syntax, NextUqi) print("#string %s%s #language uqi \"%s%04X\"" % (StringValue, Spaces, Syntax, NextUqi)) NextUqi += 1 with codecs.open(DestFileName, 'r+', Encoding) as OutputFile: OutputFile.seek(0) OutputFile.write(PlatformUQI) return 0 # ********************************************************************** # description: Parses each uni file to collect dictionary of strings # Removes additional languages and overwrites current uni files # if -l option was specified # # arguments: path - directory location of file including file name # Filename - name of file to be modified # # returns: error string if failure occurred; # none if completed sucessfully # # the following are global so that parsefile is quicker FindUniString = re.compile( '^#string[ \t]+([A-Z_0-9]+)(?:[ \t\r\n]+#language[ \t]+[a-zA-Z-]{2,5}[ \t\r\n]+".*"[ \t]*[\r]?[\n]?)*', re.M).findall OtherLang = re.compile( '^#string[ \t]+[A-Z_0-9]+(?:[ \t\r\n]+#language[ \t]+[a-zA-Z-]{2,5}[ \t\r\n]+".*"[ \t]*[\r]?[\n]?)*', re.M).findall EachLang = re.compile('[ \t\r\n]+#language[ \t]+([a-zA-Z-]{2,5})[ \t\r\n]+".*"[ \t]*[\r]?[\n]?').findall UqiStrings = re.compile('^#string[ \t]+[A-Z_0-9]+[ \t]+#language[ \t]+uqi[ \t\r\n]+".*"[ \t]*[\r]?[\n]?', re.M) def parsefile(path, Filename): global Options, StringDict, AllUqis, UqiList, FindUniString, OtherLang, EachLang, UqiStrings FullPath = path + os.sep + Filename try: UniEncoding = GetUniFileEncoding(FullPath) with codecs.open(FullPath, 'r', UniEncoding) as UniFile: Databuffer = UniFile.read() except: print("Error opening " + FullPath + " for reading.") return WriteFile = False if os.path.isdir(Options['Destname']): DestFileName = Options['Destname'] + os.sep + 'UqiList.uni' else: DestFileName = Options['Destname'] if Options['LangOption']: try: UqiEncoding = GetUniFileEncoding(DestFileName) with codecs.open(DestFileName, 'r+', UqiEncoding) as OutputFile: PlatformUQI = OutputFile.read() except IOError as e: print("ERROR: " + e.args[1]) if (e.args[0] == 2): try: with codecs.open(DestFileName, 'w', UqiEncoding) as OutputFile: print(DestFileName + " did not exist. Creating new file.") PlatformUQI = FileHeader except: print("Error creating " + DestFileName + ".") return else: print("Error opening " + DestFileName + " for appending.") return if (Filename != DestFileName.split(os.sep)[-1]): Uqis = re.findall(UqiStrings, Databuffer) if Uqis: for Uqi in Uqis: PlatformUQI += Uqi with codecs.open(DestFileName, 'r+', UqiEncoding) as OutputFile: OutputFile.seek(0) OutputFile.write(PlatformUQI) Databuffer = re.sub(UqiStrings, '', Databuffer) if Uqis: WriteFile = True print("Deleted uqis from %s" % FullPath) stringlist = OtherLang(Databuffer) for stringfound in stringlist: ThisString = EachLang(stringfound) for LanguageFound in ThisString: if ((LanguageFound != 'en') and (LanguageFound != 'en-US') and (LanguageFound != 'eng') and ( LanguageFound != 'uqi')): Databuffer = re.sub(re.escape(stringfound), '', Databuffer) WriteFile = True print("Deleted %s from %s" % (LanguageFound, FullPath)) if (Filename != DestFileName.split(os.sep)[-1]): # adding strings to dictionary StringDict[r'%s' % FullPath] = FindUniString(Databuffer) # adding UQIs to dictionary AllUqis[r'%s' % FullPath] = UqiList(Databuffer) if WriteFile: try: with codecs.open(FullPath, 'w', UniEncoding) as UniFile: UniFile.write(Databuffer) except: print("Error opening " + FullPath + " for writing.") return # ********************************************************************** # description: Searches tree for uni files # Calls parsefile to collect dictionary of strings in each uni file # Calls searchVfiFile for each vfi or vfr file found # # arguments: argument list is built by os.path.walk function call # arg - None # dirname - directory location of files # names - specific files to search in directory # # returns: none # def processUni(args, dirname, names): global Options # Remove excludedDirectory if Options['ExcludeOption']: for EachExDir in Options['ExPathList']: for dir in names: if os.path.join(dirname, dir) == EachExDir: names.remove(dir) for entry in names: FullPath = dirname + os.sep + entry if fnmatch.fnmatch(FullPath, '*.uni'): parsefile(dirname, entry) if fnmatch.fnmatch(FullPath, '*.vf*'): searchVfiFile(FullPath) if fnmatch.fnmatch(FullPath, '*.sd'): searchVfiFile(FullPath) if fnmatch.fnmatch(FullPath, '*.sdi'): searchVfiFile(FullPath) if fnmatch.fnmatch(FullPath, '*.hfr'): searchVfiFile(FullPath) return # ********************************************************************** # description: Compose a dictionary of all strings that may need UQIs assigned # to them and key is the string # # arguments: Filename - name of file to search for strings # # returns: none # # separate regexes for readability StringGroups = re.compile( '^[ \t]*(?:oneof|numeric|checkbox|orderedlist)[ \t]+varid.+?(?:endoneof|endnumeric|endcheckbox|endorderedlist);', re.DOTALL | re.M).findall StringVarIds = re.compile( '[ \t]*(?:oneof|numeric|checkbox|orderedlist)[ \t]+varid[ \t]*=[ \t]*([a-zA-Z_0-9]+\.[a-zA-Z_0-9]+)').findall StringTokens = re.compile('prompt[ \t]*=[ \t]*STRING_TOKEN[ \t]*\(([a-zA-Z_0-9]+)\)').findall def searchVfiFile(Filename): global Options, GlobalVarId, StringGroups, StringVarIds, StringTokens, QuestionError try: with open(Filename, 'r') as VfiFile: Databuffer = VfiFile.read() # Finds specified lines in file VfiStringGroup = StringGroups(Databuffer) # Searches for prompts within specified lines for EachGroup in VfiStringGroup: for EachString in StringTokens(EachGroup): # Ignore strings defined as STRING_TOKEN(0), STRING_TOKEN(STR_EMPTY) or STRING_TOKEN(STR_NULL) if (EachString != "0") and (EachString != "STR_EMPTY") and (EachString != "STR_NULL"): if EachString not in GlobalVarId: GlobalVarId[EachString] = StringVarIds(EachGroup) else: if (GlobalVarId[EachString][0] != StringVarIds(EachGroup)[0]): if Options['QuestionOption']: if Options['QuestionOption'] == "e": QuestionError = True print("ERROR:"), if Options['QuestionOption'] == "w": print("WARNING:"), print("%s referred by different HII questions(%s and %s)" % ( EachString, GlobalVarId[EachString][0], StringVarIds(EachGroup)[0])) except: print("Error opening file at %s for reading." % Filename) if __name__ == '__main__': sys.exit(main())