## @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.<BR>'
|
__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())
|