From nobody Wed May 1 14:22:36 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of groups.io designates 66.175.222.12 as permitted sender) client-ip=66.175.222.12; envelope-from=bounce+27952+42980+1787277+3901457@groups.io; helo=web01.groups.io; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zoho.com: domain of groups.io designates 66.175.222.12 as permitted sender) smtp.mailfrom=bounce+27952+42980+1787277+3901457@groups.io; dmarc=fail(p=none dis=none) header.from=intel.com ARC-Seal: i=1; a=rsa-sha256; t=1561705137; cv=none; d=zoho.com; s=zohoarc; b=Kfg1in5V1z0VfUVQdLoJAk0SPglgRW+jkXy50HvXQ7uAJW+evAXviHAdlCJL4Mb8mpVxE2AUj/M1jhmbtCfWR0UC5mWmUxnfLbouBSJAfuYc+rOX9E4aHV2IYtaUlrnwpMw1ewKxr4VMCr6ZEKmcdm/ZRgMLuw94sEJEF7kwdww= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1561705137; h=Content-Type:Cc:Date:From:List-Id:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:Sender:Subject:To:ARC-Authentication-Results; bh=grlE4QcAdUIeOjnfLEKc2KxcNOcaXLdYb0IBIeN9CNk=; b=c/oLzAHDC5SYIKOYVVkffE5mWRN5m5gNzCIGn9TfGJT89SFg71PwMnvnF1izCHnA50keLXqUM03XF5Yubcyd/P6jfElG46+dghyP14l76Pq1r9haI89XNtj9AofSmwtc3eStzdNXNic39a1ogXQP36/zc+yBhOw5JhvsAdKvUvs= ARC-Authentication-Results: i=1; mx.zoho.com; dkim=pass; spf=pass (zoho.com: domain of groups.io designates 66.175.222.12 as permitted sender) smtp.mailfrom=bounce+27952+42980+1787277+3901457@groups.io; dmarc=fail header.from= (p=none dis=none) header.from= Received: from web01.groups.io (web01.groups.io [66.175.222.12]) by mx.zohomail.com with SMTPS id 1561705137627643.1279107217416; Thu, 27 Jun 2019 23:58:57 -0700 (PDT) Return-Path: X-Received: from mga14.intel.com (mga14.intel.com [192.55.52.115]) by groups.io with SMTP; Thu, 27 Jun 2019 23:58:56 -0700 X-Amp-Result: UNKNOWN X-Amp-Original-Verdict: FILE UNKNOWN X-Amp-File-Uploaded: False X-Received: from fmsmga006.fm.intel.com ([10.253.24.20]) by fmsmga103.fm.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 27 Jun 2019 23:58:55 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.63,426,1557212400"; d="dat'59?scan'59,208,59";a="361422652" X-Received: from fmsmsx105.amr.corp.intel.com ([10.18.124.203]) by fmsmga006.fm.intel.com with ESMTP; 27 Jun 2019 23:58:55 -0700 X-Received: from fmsmsx113.amr.corp.intel.com (10.18.116.7) by FMSMSX105.amr.corp.intel.com (10.18.124.203) with Microsoft SMTP Server (TLS) id 14.3.439.0; Thu, 27 Jun 2019 23:58:55 -0700 X-Received: from shsmsx108.ccr.corp.intel.com (10.239.4.97) by FMSMSX113.amr.corp.intel.com (10.18.116.7) with Microsoft SMTP Server (TLS) id 14.3.439.0; Thu, 27 Jun 2019 23:58:54 -0700 X-Received: from shsmsx101.ccr.corp.intel.com ([169.254.1.87]) by SHSMSX108.ccr.corp.intel.com ([169.254.8.236]) with mapi id 14.03.0439.000; Fri, 28 Jun 2019 14:58:52 +0800 From: "Fan, ZhijuX" To: "devel@edk2.groups.io" CC: "Gao, Liming" , "Feng, Bob C" , Ard Biesheuvel , "Leif Lindholm" , "Kinney, Michael D" Subject: [edk2-devel] [edk2-platform patch V6] Platform/Intel:Add UniTool into edk2-platforms/Platform/Intel/Tools Thread-Topic: [edk2-platform patch V6] Platform/Intel:Add UniTool into edk2-platforms/Platform/Intel/Tools Thread-Index: AdUtfrFWT2ovNpnuTOW6wMJo7FrFOw== Date: Fri, 28 Jun 2019 06:58:52 +0000 Message-ID: Accept-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: dlp-product: dlpe-windows dlp-version: 11.0.600.7 dlp-reaction: no-action x-originating-ip: [10.239.127.40] MIME-Version: 1.0 Precedence: Bulk List-Unsubscribe: Sender: devel@edk2.groups.io List-Id: Mailing-List: list devel@edk2.groups.io; contact devel+owner@edk2.groups.io Reply-To: devel@edk2.groups.io,zhijux.fan@intel.com Content-Type: multipart/mixed; boundary="_000_FAD0D7E0AE0FA54D987F6E72435CAFD50AF8532ESHSMSX101ccrcor_" Content-Language: en-US DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=groups.io; q=dns/txt; s=20140610; t=1561705136; bh=tw9DGDvgaCVoZ3Ab9bJPeMMSWIGXDSNlkeeHe/BB3U4=; h=CC:Content-Type:Date:From:Reply-To:Subject:To; b=YD0fK3SjUMmYfzjGG3ZaJJY/EtjpYspgCSXPQUqbJg0ZE3ZmbUNnvS8FvWTGDDfsl2L YrqfVhHoxYpk4zqBmYJfQJgmIvoXFjXaCJgFntWwLuJuB7z+iQMI1zYq52ReuhsQRPln1 tJU8wsYSx/0AThy+/1adWcadhU+8hH7mlMI= X-Zoho-Virus-Status: 1 X-ZohoMail-DKIM: pass (identity @groups.io) --_000_FAD0D7E0AE0FA54D987F6E72435CAFD50AF8532ESHSMSX101ccrcor_ Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" BZ:https://bugzilla.tianocore.org/show_bug.cgi?id=3D1855 UniTool is one python script to generate UQI (Universal Question Identifier) unicode string for HII question PROMPT string. UQI string can be used to identify each HII question. The scripts function will sync up UQI definitions with uni files based on vfi/vfr/hfr/sd/sdi in the tree. This script can be run in both Py2 and Py3. Cc: Liming Gao Cc: Bob Feng Cc: Ard Biesheuvel Cc: Leif Lindholm Cc: Michael D Kinney Signed-off-by: Zhiju.Fan Reviewed-by: Bob Feng --- Updata FileHeader in script Platform/Intel/Tools/UniTool/README.txt | 41 +++ Platform/Intel/Tools/UniTool/UniTool.py | 488 ++++++++++++++++++++++++++++= ++++ 2 files changed, 529 insertions(+) create mode 100644 Platform/Intel/Tools/UniTool/README.txt create mode 100644 Platform/Intel/Tools/UniTool/UniTool.py diff --git a/Platform/Intel/Tools/UniTool/README.txt b/Platform/Intel/Tools= /UniTool/README.txt new file mode 100644 index 0000000000..69da4aca24 --- /dev/null +++ b/Platform/Intel/Tools/UniTool/README.txt @@ -0,0 +1,41 @@ + +How to use UniTool +----------------- +The usage of the tool is: +UniTool.py [-b] [-u] [-l] [-x] [-h] [-d 'rootDirectory1'] [-d 'rootDirecto= ry2'] [-d 'rootDirectory3']... [-q e|w] + 'rootDirectory0' 'uqiFile'|'uqiFileDirectory' ['excludedDirecto= ry1'] ['excludedDirectory2'] ['excludedDirectory3']... + +Function will sync up UQI definitions with uni files based on vfi/vfr/hfr/= sd/sdi in the tree. + +Required Arguments: + 'rootdirectory0' path to root directory + 'uqiFileDirectory' path to UQI file(UqiList.uni) + 'uqiFile' UQI file + +Options: + -h Show this help + -b Build option returns error if any new UQI needs as= signing + based on vfi/vfr/hfr/sd/sdi when no -u option is s= pecified + -u Create new UQIs that does not already exist in uqi= File for + any string requiring a UQI based on vfi/vfr/hfr/sd= /sdi + NOTE: 'uqiFile' cannot be readonly! + -l Language deletion option (keeps only English and u= qi) + moves all UQIs to 'uqiFile' + NOTE: Uni files cannot be readonly! + -x Exclude 'rootDirectory'/'excludedDirectory1' & + 'rootDirectory'/'excludedDirectory2'... from UQI l= ist build + NOTE: Cannot be the same as rootDirectory + -d Add multiple root directories to process + -q Print warning(w) or return error(e) if different H= II questions + are referring same string token + +Return error if any duplicated UQI string or value in UQI list or if no de= finition +for any string referred by HII question when -b or -u is specified + +NOTE: Options must be specified before parameters + +Notice +----------------- +- "S0000" style will be used if uqiFile needs to be new created. + Use the same uqi style if uqiFile is existed. For example, + if the exist UqiFile use "\x0000" style, "\x0000" will be used. diff --git a/Platform/Intel/Tools/UniTool/UniTool.py b/Platform/Intel/Tools= /UniTool/UniTool.py new file mode 100644 index 0000000000..9b51827cdd --- /dev/null +++ b/Platform/Intel/Tools/UniTool/UniTool.py @@ -0,0 +1,488 @@ +## @file +# generate UQI (Universal Question Identifier) unicode string for HII ques= tion 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 =3D False +FileHeader =3D '//\r\n// FILE auto-generated by UniTool\r\n//\r\n\r\n#lan= gdef uqi "uqi"\r\n\r\n' +UqiList =3D 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 =3D {} +StringDict =3D {} +GlobalVarId =3D {} +Options =3D {} + +# Version message +__prog__ =3D 'UniTool' +__description__ =3D 'The script generate UQI unicode string for HII questi= on PROMPT string.\n' +__copyright__ =3D 'Copyright (c) 2019, Intel Corporation. All rights reser= ved.
' +__version__ =3D '%s Version %s' % (__prog__, '0.1 ') +_Usage =3D "Syntax: %s [-b] [-u] [-l] [-x] [-h] [-d 'rootDirectory1'] [-d= 'rootDirectory2'] [-d 'rootDirectory3']... \n[-q e|w]" \ + "'rootDirectory0' 'uqiFile'|'uqiFileDirectory' ['excludedDirector= y1'] ['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 =3D 'utf-8' + + # + # Read file + # + try: + with open(Filename, mode=3D'rb') as UniFile: + FileIn =3D UniFile.read() + except: + return Encoding + + if (FileIn.startswith(codecs.BOM_UTF16_BE) or FileIn.startswith(codecs= .BOM_UTF16_LE)): + Encoding =3D 'utf-16' + + return Encoding + + +# rewrite function os.path.walk +def Walk(Top, Func, Arg): + try: + Names =3D os.listdir(Top) + except os.error: + return + Func(Arg, Top, Names) + for Name in Names: + Name =3D 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 =3D argparse.ArgumentParser(prog=3D__prog__, + description=3D__description__ + __cop= yright__, + usage=3D_Usage, + conflict_handler=3D'resolve') + parser.add_argument('Path', nargs=3D'+', + help=3D'the path for files to be converted.It coul= d be directory or file path.') + parser.add_argument('-v', '--version', action=3D'version', version=3D_= _version__, + help=3D"show program's version number and exit") + parser.add_argument('-b', '--build', action=3D'store_true', dest=3D'Bu= ildOption', + help=3D"Build option returns error if any new UQI = needs assigning " \ + "based on vfi/vfr/hfr/sd/sdi when no -u optio= n is specified") + parser.add_argument('-u', '--updata', action=3D'store_true', dest=3D'U= pdateUQIs', + help=3D"Create new UQIs that does not already exis= t 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=3D'store_true', dest=3D'Lan= gOption', + help=3D"Language deletion option (keeps only Engli= sh and uqi)" \ + "moves all UQIs to 'uqiFile', NOTE: Uni files= cannot be readonly!") + parser.add_argument('-x', '--exclude', action=3D'store_true', dest=3D'= ExcludeOption', + help=3D"Exclude 'rootDirectory'/'excludedDirectory= 1' &" \ + "'rootDirectory'/'excludedDirectory2'... from= UQI list build") + parser.add_argument('-d', '--dir', action=3D'append', metavar=3D'FILED= IR', dest=3D'DirName', + help=3D"Add multiple root directories to process") + parser.add_argument('-q', '--question', dest=3D'Question', choices=3D[= 'w', 'e'], + help=3D"Print warning(w) or return error(e) if dif= ferent HII questions" \ + "are referring same string token") + Opts =3D parser.parse_args() + Destname =3D '' + DirNameList =3D [] + ExDirList =3D [] + if Opts.Path: + DirNameList.append(Opts.Path[0]) + Destname =3D Opts.Path[1] + ExDirList =3D Opts.Path[2:] + if Opts.DirName: + DirNameList.extend(Opts.DirName) + QuestionOption =3D Opts.Question + ExcludeOption =3D Opts.ExcludeOption + BuildOption =3D Opts.BuildOption + UpdateUQIs =3D Opts.UpdateUQIs + LangOption =3D Opts.LangOption + ExPathList =3D [] + + if ExDirList: + try: + for EachExDir in ExDirList: + for EachRootDir in DirNameList: + if EachExDir =3D=3D EachRootDir: + print("\nERROR: excludedDirectory is same as rootD= irectory\n") + return + ExPathList.append(EachRootDir + os.sep + EachExDir) + except: + print(_Usage) + return + + global Options + Options =3D {'Destname': Destname, 'DirNameList': DirNameList, 'ExPath= List': ExPathList, 'BuildOption': BuildOption, + 'UpdateUQIs': UpdateUQIs, 'LangOption': LangOption, 'Exclud= eOption': 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 =3D Options['Destname'] + os.sep + 'UqiList.uni' + else: + DestFileName =3D Options['Destname'] + if os.path.exists(DestFileName) and (DestFileName not in list(AllUqis.= keys())): + try: + Encoding =3D GetUniFileEncoding(DestFileName) + with codecs.open(DestFileName, 'r+', Encoding) as destFile: + DestFileBuffer =3D destFile.read() + except IOError as e: + print("ERROR: " + e.args[1]) + return + AllUqis[DestFileName] =3D UqiList(DestFileBuffer) + if BuildOption: + ReturnVal =3D newUqi() + if (ReturnVal =3D=3D 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 =3D "S" +SyntaxRE =3D 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 =3D [] + UqiStringList =3D [] + CreateUQI =3D [] + ReturnVal =3D 0 + BaseNumSpaces =3D 47 # Used to line up the UQI values in the resultin= g uqiFile + + # Look for duplication in the current UQIs and collect current range o= f UQIs + for path in AllUqis.keys(): + for UqiString in AllUqis[path]: # path contains the path and File= name of each uni file + # Checks for duplicated strings in UQI list + for TempString in UqiStringList: + if TempString =3D=3D UqiString[0]: + print("ERROR: UQI string %s was assigned more than onc= e and will cause corruption!" % UqiString[0]) + print("Delete one occurrence of the string and rerun t= ool.") + ReturnVal =3D 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 a= nd will cause corruption!" % int(UqiString[1], + = 16)) + print("Delete one occurrance of the UQI and rerun tool to = create alternate value.") + ReturnVal =3D 1 # halt build + UqiRange.append(int(UqiString[1], 16)) + + for StringValue in GlobalVarId.keys(): + StringFound =3D False + for path in StringDict.keys(): + for UniString in StringDict[path]: # path contains the path a= nd Filename of each uni file + if (StringValue =3D=3D UniString): + StringFound =3D True + break + if not StringFound: + print("ERROR: No definition for %s referred by HII question" %= (StringValue)) + ReturnVal =3D 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 !=3D "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 =3D 1 # halt build after printing all error= messages + + if (ReturnVal =3D=3D 1): + return ReturnVal + + # Update uqiFile with necessary UQIs + if Options['UpdateUQIs'] and CreateUQI: + if os.path.isdir(Options['Destname']): + DestFileName =3D Options['Destname'] + os.sep + 'UqiList.uni' + else: + DestFileName =3D Options['Destname'] + try: + Encoding =3D GetUniFileEncoding(DestFileName) + with codecs.open(DestFileName, 'r+', Encoding) as OutputFile: + PlatformUQI =3D OutputFile.read() + except IOError as e: + print("ERROR: " + e.args[1]) + if (e.args[0] =3D=3D 2): + try: + with codecs.open(DestFileName, 'w', Encoding) as Outpu= tFile: + print(DestFileName + " did not exist. Creating ne= w file.") + PlatformUQI =3D FileHeader + except: + print("Error creating " + DestFileName + ".") + return 1 + if (e.args[1] =3D=3D "Permission denied"): + print( + "\n%s is Readonly. You must uncheck the ReadOnly atti= bute 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 =3D SyntaxRE(PlatformUQI) + if len(SyntaxL) !=3D 0: + Syntax =3D SyntaxL[0] + + # script is reading the file in and writing it back instead of app= ending because the codecs module + # automatically adds a BOM wherever you start writing. This caused= build failure. + UqiRange.sort() + if (UqiRange =3D=3D []): + NextUqi =3D 0 + else: + NextUqi =3D 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 =3D " " * (BaseNumSpaces - len(StringValue)) + PlatformUQI +=3D '#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 +=3D 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 fil= es +# 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 =3D 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 =3D 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 =3D re.compile('[ \t\r\n]+#language[ \t]+([a-zA-Z-]{2,5})[ \t\r\n= ]+".*"[ \t]*[\r]?[\n]?').findall + +UqiStrings =3D re.compile('^#string[ \t]+[A-Z_0-9]+[ \t]+#language[ \t]+uq= i[ \t\r\n]+".*"[ \t]*[\r]?[\n]?', re.M) + + +def parsefile(path, Filename): + global Options, StringDict, AllUqis, UqiList, FindUniString, OtherLang= , EachLang, UqiStrings + + FullPath =3D path + os.sep + Filename + + try: + UniEncoding =3D GetUniFileEncoding(FullPath) + with codecs.open(FullPath, 'r', UniEncoding) as UniFile: + Databuffer =3D UniFile.read() + except: + print("Error opening " + FullPath + " for reading.") + return + WriteFile =3D False + + if os.path.isdir(Options['Destname']): + DestFileName =3D Options['Destname'] + os.sep + 'UqiList.uni' + else: + DestFileName =3D Options['Destname'] + + if Options['LangOption']: + try: + UqiEncoding =3D GetUniFileEncoding(DestFileName) + with codecs.open(DestFileName, 'r+', UqiEncoding) as OutputFil= e: + PlatformUQI =3D OutputFile.read() + except IOError as e: + print("ERROR: " + e.args[1]) + if (e.args[0] =3D=3D 2): + try: + with codecs.open(DestFileName, 'w', UqiEncoding) as Ou= tputFile: + print(DestFileName + " did not exist. Creating ne= w file.") + PlatformUQI =3D FileHeader + except: + print("Error creating " + DestFileName + ".") + return + else: + print("Error opening " + DestFileName + " for appending.") + return + + if (Filename !=3D DestFileName.split(os.sep)[-1]): + Uqis =3D re.findall(UqiStrings, Databuffer) + if Uqis: + for Uqi in Uqis: + PlatformUQI +=3D Uqi + with codecs.open(DestFileName, 'r+', UqiEncoding) as Outpu= tFile: + OutputFile.seek(0) + OutputFile.write(PlatformUQI) + Databuffer =3D re.sub(UqiStrings, '', Databuffer) + if Uqis: + WriteFile =3D True + print("Deleted uqis from %s" % FullPath) + stringlist =3D OtherLang(Databuffer) + for stringfound in stringlist: + ThisString =3D EachLang(stringfound) + for LanguageFound in ThisString: + if ((LanguageFound !=3D 'en') and (LanguageFound !=3D = 'en-US') and (LanguageFound !=3D 'eng') and ( + LanguageFound !=3D 'uqi')): + Databuffer =3D re.sub(re.escape(stringfound), '', = Databuffer) + WriteFile =3D True + print("Deleted %s from %s" % (LanguageFound, FullP= ath)) + if (Filename !=3D DestFileName.split(os.sep)[-1]): + # adding strings to dictionary + StringDict[r'%s' % FullPath] =3D FindUniString(Databuffer) + # adding UQIs to dictionary + AllUqis[r'%s' % FullPath] =3D 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 un= i 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) =3D=3D EachExDir: + names.remove(dir) + + for entry in names: + FullPath =3D 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 assi= gned +# to them and key is the string +# +# arguments: Filename - name of file to search for strings +# +# returns: none +# + +# separate regexes for readability +StringGroups =3D re.compile( + '^[ \t]*(?:oneof|numeric|checkbox|orderedlist)[ \t]+varid.+?(?:endoneo= f|endnumeric|endcheckbox|endorderedlist);', + re.DOTALL | re.M).findall +StringVarIds =3D re.compile( + '[ \t]*(?:oneof|numeric|checkbox|orderedlist)[ \t]+varid[ \t]*=3D[ \t]= *([a-zA-Z_0-9]+\.[a-zA-Z_0-9]+)').findall +StringTokens =3D re.compile('prompt[ \t]*=3D[ \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 =3D VfiFile.read() + + # Finds specified lines in file + VfiStringGroup =3D 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 !=3D "0") and (EachString !=3D "STR_EMPTY")= and (EachString !=3D "STR_NULL"): + if EachString not in GlobalVarId: + GlobalVarId[EachString] =3D StringVarIds(EachGroup) + else: + if (GlobalVarId[EachString][0] !=3D StringVarIds(E= achGroup)[0]): + if Options['QuestionOption']: + if Options['QuestionOption'] =3D=3D "e": + QuestionError =3D True + print("ERROR:"), + if Options['QuestionOption'] =3D=3D "w": + print("WARNING:"), + print("%s referred by different HII questi= ons(%s and %s)" % ( + EachString, GlobalVarId[EachString][0]= , StringVarIds(EachGroup)[0])) + except: + print("Error opening file at %s for reading." % Filename) + + +if __name__ =3D=3D '__main__': + sys.exit(main()) --=20 2.14.1.windows.1 -=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#42980): https://edk2.groups.io/g/devel/message/42980 Mute This Topic: https://groups.io/mt/32239557/1787277 Group Owner: devel+owner@edk2.groups.io Unsubscribe: https://edk2.groups.io/g/devel/unsub [importer@patchew.org] -=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D- --_000_FAD0D7E0AE0FA54D987F6E72435CAFD50AF8532ESHSMSX101ccrcor_ Content-Disposition: attachment; filename="winmail.dat" Content-Transfer-Encoding: base64 Content-Type: application/ms-tnef; name="winmail.dat" eJ8+IjQWAQaQCAAEAAAAAAABAAEAAQeQBgAIAAAA5AQAAAAAAADoAAEJgAEAIQAAADdFRUE1ODg3 NTlDN0VCNDI4REREOUEwN0IzN0U0OTRDAG0HAQ2ABAACAAAAAgACAAEFgAMADgAAAOMHBgAcAAYA OgA0AAUAhQEBIIADAA4AAADjBwYAHAAGADoANAAFAIUBAQiABwAYAAAASVBNLk1pY3Jvc29mdCBN YWlsLk5vdGUAMQgBBIABAF0AAABbZWRrMi1wbGF0Zm9ybSBwYXRjaCBWNl0gUGxhdGZvcm0vSW50 ZWw6QWRkIFVuaVRvb2wgaW50byBlZGsyLXBsYXRmb3Jtcy9QbGF0Zm9ybS9JbnRlbC9Ub29scwCN IQELgAEAIQAAADdFRUE1ODg3NTlDN0VCNDI4REREOUEwN0IzN0U0OTRDAG0HAQOQBgAEMQAANAAA AAIBfwABAAAASAAAADxGQUQwRDdFMEFFMEZBNTREOTg3RjZFNzI0MzVDQUZENTBBRjg1MzJFQFNI U01TWDEwMS5jY3IuY29ycC5pbnRlbC5jb20+AAsAHw4BAAAAAgEJEAEAAAC6JgAAtiYAABRuAABM WkZ101iFwWEACmZiaWQEAABjY8BwZzEyNTIA/gND8HRleHQB9wKkA+MCAARjaArAc2V0MCDvB20C gwBQEU0yCoAGtAKAln0KgAjIOwliMTkOwL8JwxZyCjIWcQKAFWIqCbBzCfAEkGF0BbIOUANgc6Jv AYAgRXgRwW4YMF0GUnYEkBe2AhByAMB0fQhQbhoxECAFwAWgG2RkmiADUiAQIheyXHYIkOR3awuA ZDUdUwTwB0ANF3AwCnEX8mJrbWsGcwGQACAgQk1fQuBFR0lOfQr8AfEL8REfsFo6aAJAcHM6wC8v YnVnegMQC2AkLnQHMG5vBaFlLgEFsGcvc2hvd19BIlEuY2dpPw3QPagxODUd4GwLgGUKgaUlFFUD AFRvBvAgBADGIAIgGeBweXQj0AOgGwT1GJAgGDUZ4FVRSSwgKCXhGjFzB0AgUdsKUB9gaQIgJQVJ AQACMOMGkAiRKSB1AwAFoAEAPScwdAUQGcAccAWxSEkFKJBxKXUgUFJPTfRQVCuFLihiJQUrlR5Q 9QOgYhngdRIAHGAnsQ3QUSpzeSBlANBoLDsuuSUFVGgrcQUDBCBmKxDeYyzDA/AioCcweTMAKwDu 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 96T/pgih4t+wms8WfZh5f1dA9uGV4H05qQ/XXn05W/+YeMbSaBqaGazv11m6kFQgn6zfqd+jwa7v r/RbMMbQ/78RsI+aZLexqP+6zw3+gKY/D2+7H7s/vE+9VxkCImU+Ir3vw6/XWYCrjfFUcg+AsMK/ x7/b/0VSUk/8UjrfsGU7yy+/z8Dfweged8Kf0B/Iv9zgV0FS3k51Qco/09/RXyWFEP3gP9mhbNH3 ghTg2ZL2ckhJ+EkgcYC1jtDWAU8i1gD6Kd3gJbYg0x/ab7K4mHh/fRu2/X7MuO2x6wxhWUB0/88v yXbGA4PCmOLsM0uh1gH7WkaNcS7ZEnrXeD/ZdQ4x3F9fAOLn0BkCJ+fQPHDL2YDn0Cfhi3N5IYAM YN2DgCjowoiAseYt+1DZZXAyLjE07CD4MHExb5p3IYAx2WXZZX192bAB7jAAAB8AQgABAAAAGAAA AEYAYQBuACwAIABaAGgAaQBqAHUAWAAAAB8AZQABAAAAKgAAAHoAaABpAGoAdQB4AC4AZgBhAG4A QABpAG4AdABlAGwALgBjAG8AbQAAAAAAHwBkAAEAAAAKAAAAUwBNAFQAUAAAAAAAAgFBAAEAAABk AAAAAAAAAIErH6S+oxAZnW4A3QEPVAIAAACARgBhAG4ALAAgAFoAaABpAGoAdQBYAAAAUwBNAFQA UAAAAHoAaABpAGoAdQB4AC4AZgBhAG4AQABpAG4AdABlAGwALgBjAG8AbQAAAB8AAl0BAAAAKgAA AHoAaABpAGoAdQB4AC4AZgBhAG4AQABpAG4AdABlAGwALgBjAG8AbQAAAAAAHwDlXwEAAAAyAAAA cwBpAHAAOgB6AGgAaQBqAHUAeAAuAGYAYQBuAEAAaQBuAHQAZQBsAC4AYwBvAG0AAAAAAB8AGgwB AAAAGAAAAEYAYQBuACwAIABaAGgAaQBqAHUAWAAAAB8AHwwBAAAAKgAAAHoAaABpAGoAdQB4AC4A ZgBhAG4AQABpAG4AdABlAGwALgBjAG8AbQAAAAAAHwAeDAEAAAAKAAAAUwBNAFQAUAAAAAAAAgEZ DAEAAABkAAAAAAAAAIErH6S+oxAZnW4A3QEPVAIAAACARgBhAG4ALAAgAFoAaABpAGoAdQBYAAAA UwBNAFQAUAAAAHoAaABpAGoAdQB4AC4AZgBhAG4AQABpAG4AdABlAGwALgBjAG8AbQAAAB8AAV0B AAAAKgAAAHoAaABpAGoAdQB4AC4AZgBhAG4AQABpAG4AdABlAGwALgBjAG8AbQAAAAAAHwD4PwEA AAAYAAAARgBhAG4ALAAgAFoAaABpAGoAdQBYAAAAHwAjQAEAAAAqAAAAegBoAGkAagB1AHgALgBm AGEAbgBAAGkAbgB0AGUAbAAuAGMAbwBtAAAAAAAfACJAAQAAAAoAAABTAE0AVABQAAAAAAACAfk/ AQAAAGQAAAAAAAAAgSsfpL6jEBmdbgDdAQ9UAgAAAIBGAGEAbgAsACAAWgBoAGkAagB1AFgAAABT AE0AVABQAAAAegBoAGkAagB1AHgALgBmAGEAbgBAAGkAbgB0AGUAbAAuAGMAbwBtAAAAHwAJXQEA AAAqAAAAegBoAGkAagB1AHgALgBmAGEAbgBAAGkAbgB0AGUAbAAuAGMAbwBtAAAAAAALAEA6AQAA AB8AGgABAAAAEgAAAEkAUABNAC4ATgBvAHQAZQAAAAAAAwDxPwkEAAALAEA6AQAAAAMA/T/kBAAA AgELMAEAAAAQAAAAfupYh1nH60KN3ZoHs35JTAMAFwABAAAAQAA5AACenPF+LdUBQAAIMCHw8/F+ LdUBHwAAgIYDAgAAAAAAwAAAAAAAAEYBAAAAHgAAAGEAYwBjAGUAcAB0AGwAYQBuAGcAdQBhAGcA ZQAAAAAAAQAAAAwAAABlAG4ALQBVAFMAAAAfADcAAQAAALoAAABbAGUAZABrADIALQBwAGwAYQB0 AGYAbwByAG0AIABwAGEAdABjAGgAIABWADYAXQAgAFAAbABhAHQAZgBvAHIAbQAvAEkAbgB0AGUA bAA6AEEAZABkACAAVQBuAGkAVABvAG8AbAAgAGkAbgB0AG8AIABlAGQAawAyAC0AcABsAGEAdABm AG8AcgBtAHMALwBQAGwAYQB0AGYAbwByAG0ALwBJAG4AdABlAGwALwBUAG8AbwBsAHMAAAAAAB8A PQABAAAAAgAAAAAAAAADADYAAAAAAAIBcQABAAAAFgAAAAHVLX6xVk9qLzaZ7kzlusDCaOxaxTsA AB8AcAABAAAAugAAAFsAZQBkAGsAMgAtAHAAbABhAHQAZgBvAHIAbQAgAHAAYQB0AGMAaAAgAFYA NgBdACAAUABsAGEAdABmAG8AcgBtAC8ASQBuAHQAZQBsADoAQQBkAGQAIABVAG4AaQBUAG8AbwBs ACAAaQBuAHQAbwAgAGUAZABrADIALQBwAGwAYQB0AGYAbwByAG0AcwAvAFAAbABhAHQAZgBvAHIA bQAvAEkAbgB0AGUAbAAvAFQAbwBvAGwAcwAAAAAAHwA1EAEAAACQAAAAPABGAEEARAAwAEQANwBF ADAAQQBFADAARgBBADUANABEADkAOAA3AEYANgBFADcAMgA0ADMANQBDAEEARgBEADUAMABBAEYA OAA1ADMAMgBFAEAAUwBIAFMATQBTAFgAMQAwADEALgBjAGMAcgAuAGMAbwByAHAALgBpAG4AdABl AGwALgBjAG8AbQA+AAAAAwDeP59OAABAAAcw9cvs8X4t1QECAQsAAQAAABAAAAB+6liHWcfrQo3d mgezfklMAwAmAAAAAAACAUcAAQAAADMAAABjPVVTO2E9TUNJO3A9SW50ZWw7bD1TSFNNU1gxMDEt MTkwNjI4MDY1ODUyWi0xNDgyMgAAAgEQMAEAAABGAAAAAAAAACZ3vTk77DhJpKYVPcule0IHAPrQ 1+CuD6VNmH9uckNcr9UAAABEEV4AAKaTaDaIdmxLp0rT60eaTGQAAAlUj9MAAAAAHwD6PwEAAAAY AAAARgBhAG4ALAAgAFoAaABpAGoAdQBYAAAAAwAJWQEAAABAAACACCAGAAAAAADAAAAAAAAARgAA AAC/hQAA8C3D8H4t1QELAACACCAGAAAAAADAAAAAAAAARgAAAACChQAAAAAAAAMAAIAIIAYAAAAA AMAAAAAAAABGAAAAAOuFAAAJBAAAHwAAgIYDAgAAAAAAwAAAAAAAAEYBAAAAGAAAAGQAbABwAC0A cAByAG8AZAB1AGMAdAAAAAEAAAAaAAAAZABsAHAAZQAtAHcAaQBuAGQAbwB3AHMAAAAAAB8AAICG AwIAAAAAAMAAAAAAAABGAQAAABgAAABkAGwAcAAtAHYAZQByAHMAaQBvAG4AAAABAAAAFgAAADEA MQAuADAALgA2ADAAMAAuADcAAAAAAB8AAICGAwIAAAAAAMAAAAAAAABGAQAAABoAAABkAGwAcAAt AHIAZQBhAGMAdABpAG8AbgAAAAAAAQAAABQAAABuAG8ALQBhAGMAdABpAG8AbgAAAAMADTT9PwAA HwAAgIYDAgAAAAAAwAAAAAAAAEYBAAAAIAAAAHgALQBtAHMALQBoAGEAcwAtAGEAdAB0AGEAYwBo AAAAAQAAAAIAAAAAAAAAHwAAgIYDAgAAAAAAwAAAAAAAAEYBAAAAIgAAAHgALQBvAHIAaQBnAGkA bgBhAHQAaQBuAGcALQBpAHAAAAAAAAEAAAAgAAAAWwAxADAALgAyADMAOQAuADEAMgA3AC4ANAAw AF0AAAAH1Q== --_000_FAD0D7E0AE0FA54D987F6E72435CAFD50AF8532ESHSMSX101ccrcor_--