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