Files
JIMRI/scripts/ClassKeysReport.py
T
2026-06-17 14:00:51 +02:00

184 lines
6.3 KiB
Python

##
# Look for non-standard getString and getMessage key arguments.
# These will normally be variables.
# Other exceptions:
# If a key contains special characters, it will be flagged as a variable.
# The valid characters are a-z, A-Z, 0-9 and underscore.
#
# The JMRI source package is required.
#
# Dave Sand copyright 2017
##
import io
import re
import os
from os.path import join
import glob
import jmri
import java
import org.apache.commons.csv
from javax.swing import JFileChooser, JOptionPane
from javax.swing.filechooser import FileNameExtensionFilter
dialogTitle = "Class Keys Report"
print " {}".format(dialogTitle)
keyList = []
# Select a Java program or package directory to be analyzed.
fc = JFileChooser(FileUtil.getProgramPath())
fc.setDialogTitle(dialogTitle)
fc.setFileFilter(FileNameExtensionFilter("Java Program", ["java"]));
fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES)
ret = fc.showOpenDialog(None)
if ret == JFileChooser.APPROVE_OPTION:
selectedItem = fc.getSelectedFile().toString()
else:
print 'No file selected, bye'
quit()
# RegEx patterns. Capture the first word after the start of the match string.
# The first one looks for a word within double quotes.
# The second one looks for a plain word.
# A word contains a-z, A-Z, 0-9 or underscore characters.
reStrKey = re.compile('\W*"(\w+)"\W*[,)]')
reVarKey = re.compile('\W*(\w+)\W')
##
# Determine class for a variable key.
# The first getMessage parameter can be a Locale object.
# variableKey : The current key
# filePath : The current file being processed
# return : The class type: String, Locale or Unknown
##
def findVariableClass(variableKey, filePath):
with io.open(filePath, "r", encoding="utf-8") as chkFile:
for chkLine in chkFile:
for classType in ['String', 'Locale']:
chkClassArg = '.*{}\s+{}'.format(classType, variableKey)
reChkClass = re.compile(chkClassArg)
chkMatch = re.match(chkClassArg, chkLine)
if chkMatch is not None:
return classType
return 'Unknown'
##
# Search for the next word in the line fragment.
# lineFragment : The line fragment to be searched.
# return : A tuple that has the key and key type
##
def findKey(lineFragment):
returnValues = 'None', 'Unknown'
chkStr = re.match(reStrKey, lineFragment)
if chkStr is not None:
returnValues = chkStr.group(1), 'String'
else:
chkVar = re.match(reVarKey, lineFragment)
if chkVar is not None:
returnValues = chkVar.group(1), 'Variable'
return returnValues
##
# Search a line of code for occruances of getString or getMessage.
# Display a message for non-standard key references.
# All keys are added to a list that can be exported as a CSV file.
# classLine : The current line of code
# num : The current line number
# filePath : The current file path
##
def findKeys(classLine, num, filePath):
for searchText in ['Bundle.getMessage(', 'getString(']:
searchType = 'Local' if searchText == 'getString(' else 'Bundle'
searchSize = len(searchText)
idx = 0
while idx >= 0 and idx < len(classLine):
idx = classLine.find(searchText, idx)
if idx == -1:
break
idx += searchSize
key, keyType = findKey(classLine[idx:])
if keyType == 'Variable':
if findVariableClass(key, filePath) == 'Locale':
key, keyType = findKey(classLine[idx + len(key):])
if keyType != 'String':
print " {}, Search Type = {}, Key Type = {}, Key = '{}', \tText = {}".format(num, searchType, keyType, key, classLine.strip())
keyList.append([num, searchType, keyType, key, classLine.strip()])
##
# Read each line of the supplied file and call the parser
# filePath : The full path for the file
##
def doFile(filePath):
keyList.append([filePath])
with io.open(filePath, "r", encoding="utf-8") as classFile:
num = 0
for classLine in classFile:
num += 1
findKeys(classLine, num, filePath)
# Process a single file selection
if os.path.isfile(selectedItem):
print '\n Check {}'.format(selectedItem)
doFile(selectedItem)
# Process package directory selection
if os.path.isdir(selectedItem):
globArgument = join(selectedItem, "*java")
for className in glob.glob(globArgument):
if os.path.basename(className) == 'Bundle.java':
continue
print '\n Check {}'.format(className)
doFile(className)
##
# Create the CSV header record
# csvFile : The output file
##
def writeHeader(csvFile):
csvFile.print("Line #")
csvFile.print("Search Type")
csvFile.print("Key Type")
csvFile.print("Key")
csvFile.print("Line Text / Class File")
csvFile.println()
##
# Create the CSV detail record
# The file path and name go into column 5
# csvFile : The output file
##
def writeDetails(csvFile):
for keyRow in keyList:
if len(keyRow) == 1:
csvFile.print("")
csvFile.print("")
csvFile.print("")
csvFile.print("")
csvFile.print(keyRow[0])
else:
csvFile.print('{}'.format(keyRow[0]))
csvFile.print(keyRow[1])
csvFile.print(keyRow[2])
csvFile.print(keyRow[3])
csvFile.print(keyRow[4])
csvFile.println()
# Provide the option to export the full class key list to an external file.
saveResp = JOptionPane.showConfirmDialog(None, "Do you want to export the full class key list to a CSV file?", dialogTitle, JOptionPane.YES_NO_OPTION)
if saveResp == 0:
fo = JFileChooser(FileUtil.getUserFilesPath())
fo.setDialogTitle(dialogTitle)
fo.setFileFilter(FileNameExtensionFilter("CSV", ["csv"]));
ret = fo.showSaveDialog(None)
if ret == JFileChooser.APPROVE_OPTION:
keyFile = fo.getSelectedFile().toString()
csvFile = org.apache.commons.CSVPrinter(java.io.BufferedWriter(java.io.FileWriter(keyFile)),java.nio.charset.Charset.defaultCharset(),org.apache.commons.csv.CSVFormat.DEFAULT)
writeHeader(csvFile)
writeDetails(csvFile)
csvFile.flush()
csvFile.close()
JOptionPane.showMessageDialog(None,"Class keys export completed",dialogTitle,JOptionPane.INFORMATION_MESSAGE)
print '\n {} Done'.format(dialogTitle)