From 3769679237f55dc4de00287a36b5d2313252758a Mon Sep 17 00:00:00 2001 From: PabstMirror Date: Sun, 31 Mar 2019 15:27:51 -0500 Subject: [PATCH] Add tool to verify stringtable entries exist (#6889) * Add tool to verify stringtable entries exist * Update circle.yml * test * Allow running from root directory as well as from inside the tools directory --- addons/common/ACE_Settings.hpp | 2 + .../fnc_cbaSettings_convertHelper.sqf | 2 +- .../functions/fnc_getHitPointString.sqf | 1 + circle.yml | 4 +- tools/check_strings.py | 101 ++++++++++++++++++ 5 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 tools/check_strings.py diff --git a/addons/common/ACE_Settings.hpp b/addons/common/ACE_Settings.hpp index eeee9c2f37..f7ab86e227 100644 --- a/addons/common/ACE_Settings.hpp +++ b/addons/common/ACE_Settings.hpp @@ -1,4 +1,6 @@ class ACE_Settings { + //IGNORE_STRING_WARNING(STR_ACE_Common_SettingName); + //IGNORE_STRING_WARNING(STR_ACE_Common_SettingDescription); /* * class GVAR(sampleSetting) { * value = 1; // Value diff --git a/addons/common/functions/fnc_cbaSettings_convertHelper.sqf b/addons/common/functions/fnc_cbaSettings_convertHelper.sqf index 4c64da8f0f..8c892bcf13 100644 --- a/addons/common/functions/fnc_cbaSettings_convertHelper.sqf +++ b/addons/common/functions/fnc_cbaSettings_convertHelper.sqf @@ -113,7 +113,7 @@ private _settings = configProperties [configFile >> "ACE_Settings", "(isClass _x _output pushBack ""; _output pushBack format ["["]; _output pushBack format [" QGVAR(%1), ""%2"",", _gvarName, _cbaSettingType]; - _output pushBack format [" [LSTRING(), LSTRING()], // %1, %2", _localizedName, _localizedDescription]; + _output pushBack format [" [LSTRING(), LSTRING()], // %1, %2", _localizedName, _localizedDescription]; //IGNORE_STRING_WARNING(str_ace_common_); _output pushBack format [" ""%1"", // %2", ["localize LSTRING()", _category] select _uncat, _category]; _output pushBack format [" %1, // %2", _cbaValueInfo, _cbaValueInfoHint]; _output pushBack format [" %1, // isGlobal", _cbaIsGlobal]; diff --git a/addons/repair/functions/fnc_getHitPointString.sqf b/addons/repair/functions/fnc_getHitPointString.sqf index 8394b4f5ac..7439d35c12 100644 --- a/addons/repair/functions/fnc_getHitPointString.sqf +++ b/addons/repair/functions/fnc_getHitPointString.sqf @@ -33,6 +33,7 @@ if (_track) then { }; // Prepare first part of the string from stringtable +//IGNORE_STRING_WARNING(str_ace_repair_hit); private _text = LSTRING(Hit); // Remove "Hit" from hitpoint name if one exists diff --git a/circle.yml b/circle.yml index 77b01ba205..5cd5f0d72d 100644 --- a/circle.yml +++ b/circle.yml @@ -6,8 +6,8 @@ jobs: steps: - checkout - run: - name: Validate SQF and Config style - command: python tools/sqf_validator.py && python tools/config_style_checker.py + name: Validate SQF and Config style and Stringtable entries + command: python tools/sqf_validator.py && python tools/config_style_checker.py && python tools/check_strings.py linting: docker: diff --git a/tools/check_strings.py b/tools/check_strings.py new file mode 100644 index 0000000000..78ad52b04e --- /dev/null +++ b/tools/check_strings.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# PabstMirror +# Checks all strings are defined, run with -u to return all unused strings + +import fnmatch +import os +import re +import sys + +def getDefinedStrings(filepath): + # print("getDefinedStrings {0}".format(filepath)) + with open(filepath, 'r', encoding="latin-1") as file: + content = file.read() + srch = re.compile('Key ID\=\"(STR_ACE_[_a-zA-Z0-9]*)"', re.IGNORECASE) + modStrings = srch.findall(content) + modStrings = [s.lower() for s in modStrings] + return modStrings + +def getStringUsage(filepath): + selfmodule = (re.search('addons[\W]*([_a-zA-Z0-9]*)', filepath)).group(1) + # print("Checking {0} from {1}".format(filepath,selfmodule)) + fileStrings = [] + + with open(filepath, 'r') as file: + content = file.read() + + srch = re.compile('(STR_ACE_[_a-zA-Z0-9]*)', re.IGNORECASE) + fileStrings = srch.findall(content) + + srch = re.compile('[^E][CL]STRING\(([_a-zA-Z0-9]*)\)', re.IGNORECASE) + modStrings = srch.findall(content) + for localString in modStrings: + fileStrings.append("STR_ACE_{0}_{1}".format(selfmodule, localString)) + + srch = re.compile('E[CL]STRING\(([_a-zA-Z0-9]*),([_a-zA-Z0-9]*)\)') + exStrings = srch.findall(content) + for (exModule, exString) in exStrings: + fileStrings.append("STR_ACE_{0}_{1}".format(exModule, exString)) + + srch = re.compile('IGNORE_STRING_WARNING\([\'"]*([_a-zA-Z0-9]*)[\'"]*\)') + ignoreWarnings = srch.findall(content) + + fileStrings = [s.lower() for s in fileStrings] + return [s for s in fileStrings if s not in (i.lower() for i in ignoreWarnings)] + +def main(argv): + print("### check_strings.py {} ###".format(argv)) + sqf_list = [] + xml_list = [] + + allDefinedStrings = [] + allUsedStrings = [] + + # Allow running from root directory as well as from inside the tools directory + rootDir = "../addons" + if (os.path.exists("addons")): + rootDir = "addons" + + for root, dirnames, filenames in os.walk(rootDir): + for filename in fnmatch.filter(filenames, '*.sqf'): + sqf_list.append(os.path.join(root, filename)) + for filename in fnmatch.filter(filenames, '*.cpp'): + sqf_list.append(os.path.join(root, filename)) + for filename in fnmatch.filter(filenames, '*.hpp'): + sqf_list.append(os.path.join(root, filename)) + for filename in fnmatch.filter(filenames, '*.h'): + sqf_list.append(os.path.join(root, filename)) + + for filename in fnmatch.filter(filenames, '*.xml'): + xml_list.append(os.path.join(root, filename)) + + for filename in xml_list: + allDefinedStrings = allDefinedStrings + getDefinedStrings(filename) + for filename in sqf_list: + allUsedStrings = allUsedStrings + getStringUsage(filename) + + allDefinedStrings = list(sorted(set(allDefinedStrings))) + allUsedStrings = list(sorted(set(allUsedStrings))) + + print("-----------") + countUnusedStrings = 0 + countUndefinedStrings = 0 + for s in allDefinedStrings: + if (not (s in allUsedStrings)): + countUnusedStrings = countUnusedStrings + 1; + if ("-u" in argv): + print("String {} defined but not used".format(s)) + print("-----------") + for s in allUsedStrings: + if (not (s in allDefinedStrings)): + print("String {} not defined".format(s)) + countUndefinedStrings = countUndefinedStrings + 1; + print("-----------") + + print("Defined Strings:{0} Used Strings:{1}".format(len(allDefinedStrings),len(allUsedStrings))) + print("Unused Strings:{0} Undefined Strings:{1}".format(countUnusedStrings,countUndefinedStrings)) + + return countUndefinedStrings + +if __name__ == "__main__": + main(sys.argv)