1#!/usr/bin/env python3 2# CheckMentioned.py 3# Check that various elements are documented and localized. 4# Find all the properties used in SciTE source files and check that they 5# are mentioned in scite/doc/SciTEDoc.html and scite/src/SciTEGlobal.properties. 6# Find all the strings used in the UI and check that there is a localization key for each. 7# List any properties that are set in .properties files for different languages. 8# Requires Python 3.6 or later 9 10import os, re, string, stat 11 12srcRoot = os.path.join("..", "..", "scite") 13srcDir = os.path.join(srcRoot, "src") 14docFileName = os.path.join(srcRoot, "doc", "SciTEDoc.html") 15propsFileName = os.path.join(srcDir, "SciTEGlobal.properties") 16localeFileName = os.path.join(srcRoot, "win32", "locale.properties") 17resourceFileName = os.path.join(srcRoot, "win32", "SciTERes.rc") 18 19identCharacters = "_*." + string.ascii_letters + string.digits 20 21# These properties are for debugging or for optionally attached features or are archaic 22# and kept to preserve compatibility. 23# lexerpath.*.lpeg is a special case for LPEG lexers associated with Scintillua projects. 24knownDebugOptionalAndArchaicProperties = { 25 "ext.lua.debug.traceback", # Debug 26 "asynchronous.sleep", # Debug 27 "dwell.period", # Debug 28 "bookmark.pixmap", # Debug 29 "lexerpath.*.lpeg", # Option for Scintillua 30 "ipc.director.name", # Archaic 31 "two.phase.draw", # Archaic 32 "translation.encoding", # Used in translations 33} 34 35# These properties are either set by SciTE and used (not set) in property files or 36# should only be located in known lexer-specific property files. 37knownOutputAndLexerProperties = { 38 "find.directory", 39 "find.what", 40 "xml.auto.close.tags", 41 "indent.python.colon", 42 "ScaleFactor", 43} 44knownOutputAndLexerProperties.update(knownDebugOptionalAndArchaicProperties) 45 46# Convert all punctuation characters except '_', '*', and '.' into spaces. 47def depunctuate(s): 48 return "".join(ch if ch in identCharacters else " " for ch in s) 49 50def grabQuoted(s): 51 parts = s.split('"') 52 if len(parts) >= 3: 53 return parts[1] 54 return "" 55 56def stripComment(s): 57 if "//" in s: 58 return s[:s.find("//")] 59 return s 60 61def keyOfLine(line): 62 if '=' in line: 63 line = line.strip() 64 if line[0] == "#": 65 line = line[1:] 66 line = line[:line.find("=")] 67 line = line.strip() 68 return line 69 else: 70 return None 71 72# Find all source and properties files 73 74sourcePaths = [] 75for filename in os.listdir(srcRoot): 76 dirname = os.path.join(srcRoot, filename) 77 if stat.S_ISDIR(os.stat(dirname)[stat.ST_MODE]): 78 for src in os.listdir(dirname): 79 if ".cxx" in src and ".bak" not in src: 80 sourcePaths.append(os.path.join(dirname, src)) 81 82propertiesPaths = [] 83for src in os.listdir(srcDir): 84 if ".properties" in src and \ 85 "Embedded" not in src and \ 86 "SciTE.properties" not in src and \ 87 ".bak" not in src: 88 propertiesPaths.append(os.path.join(srcDir, src)) 89 90# Read files to find properties and check against other files 91 92propertyNames = set() 93for sourcePath in sourcePaths: 94 with open(sourcePath, encoding="windows-1252") as srcFile: 95 for srcLine in srcFile: 96 srcLine = stripComment(srcLine).strip() 97 # "[ .\)]Get.*(.*\".*\"" 98 if re.search('[ .(]Get[a-zA-Z]*\(\".*\"', srcLine): 99 parts = srcLine.split('\"') 100 if len(parts) > 1 and "GetTranslationToAbout" not in srcLine: 101 propertyName = parts[1] 102 if propertyName and propertyName != "1": 103 propertyNames.add(propertyName) 104 105propertiesInDoc = set() 106with open(docFileName, encoding="windows-1252") as docFile: 107 for docLine in docFile: 108 for word in depunctuate(docLine).split(): 109 if word in propertyNames: 110 propertiesInDoc.add(word) 111 112propertiesInGlobal = set() 113with open(propsFileName, encoding="windows-1252") as propsFile: 114 for propLine in propsFile: 115 if propLine: 116 key = keyOfLine(propLine) 117 if key: 118 if key in propertyNames: 119 propertiesInGlobal.add(key) 120 121localeSet = set() 122with open(localeFileName, encoding="windows-1252") as localeFile: 123 for line in localeFile: 124 if not line.startswith("#"): 125 line = line.strip().strip("=") 126 localeSet.add(line.lower()) 127 128resourceSet = set() 129with open(resourceFileName, encoding="windows-1252") as resourceFile: 130 for line in resourceFile: 131 line = line.strip() 132 if "VIRTKEY" not in line and \ 133 "VALUE" not in line and \ 134 "1234567" not in line and \ 135 not line.startswith("BLOCK") and \ 136 not line.startswith("FONT") and \ 137 not line.startswith("ICON") and \ 138 not line.startswith("ID") and \ 139 "#include" not in line: 140 line = grabQuoted(line) 141 if line: 142 if '\\t' in line: 143 line = line[:line.find('\\t')] 144 line = line.replace('&','') 145 line = line.replace('...','') 146 if len(line) > 2: 147 resourceSet.add(line) 148 149propertyToFiles = {} 150for propPath in propertiesPaths: 151 with open(propPath, encoding="windows-1252") as propsFile: 152 for propLine in propsFile: 153 if propLine and not propLine.startswith("#"): 154 key = keyOfLine(propLine) 155 if key: 156 if key not in propertyToFiles: 157 propertyToFiles[key] = set() 158 propertyToFiles[key].add(propPath) 159 160# Warn about problems 161 162print(f"# Not mentioned in {docFileName}") 163for identifier in sorted(propertyNames - propertiesInDoc - knownDebugOptionalAndArchaicProperties): 164 print(identifier) 165 166print(f"\n# Not mentioned in {propsFileName}") 167for identifier in sorted(propertyNames - propertiesInGlobal - knownOutputAndLexerProperties): 168 if not identifier.endswith("."): 169 print(identifier) 170 171print("\n# Missing localization of resource") 172for l in sorted(resourceSet): 173 if l.lower() not in localeSet: 174 print(l) 175 176print("\n# Duplicate properties") 177for property, files in sorted(propertyToFiles.items()): 178 if len(files) > 1: 179 print(property + " " + (", ".join(files))) 180