217 lines
7.9 KiB
Python
217 lines
7.9 KiB
Python
##
|
|
# Identify keys within a bundle properties file that are not being used.
|
|
# The JMRI source package is required.
|
|
#
|
|
# Dave Sand copyright 2017
|
|
##
|
|
|
|
import io
|
|
import os
|
|
from os.path import join
|
|
import glob
|
|
from time import time
|
|
|
|
import jmri
|
|
import java
|
|
from javax.swing import JFileChooser, JOptionPane
|
|
from javax.swing.filechooser import FileNameExtensionFilter
|
|
|
|
dialogTitle = "Bundle Keys Report"
|
|
|
|
##
|
|
# Select a properties file to be anaylzed. This will normally be the
|
|
# default (English) file.
|
|
##
|
|
fc = JFileChooser(FileUtil.getProgramPath())
|
|
fc.setDialogTitle(dialogTitle)
|
|
fc.setFileFilter(FileNameExtensionFilter("Bundle Properties", ["properties"]));
|
|
ret = fc.showOpenDialog(None)
|
|
if ret == JFileChooser.APPROVE_OPTION:
|
|
selectedBundle = fc.getSelectedFile().toString()
|
|
startTime = time()
|
|
else:
|
|
print "No file selected, bye"
|
|
quit()
|
|
|
|
# set up path info
|
|
bundleFile = os.path.basename(selectedBundle)
|
|
fullPath = os.path.dirname(selectedBundle)
|
|
splitPath = fullPath.split(os.sep + "src" + os.sep)
|
|
jmriPath = splitPath[0]
|
|
bundlePath = splitPath[1]
|
|
dotPath = ".".join(bundlePath.split(os.sep))
|
|
testPath = join(jmriPath, "test", bundlePath)
|
|
|
|
print dialogTitle + "\n"
|
|
print "Bundle Property File: '{}'\n".format(bundleFile)
|
|
|
|
print "Full path: {}".format(fullPath)
|
|
print "Test path: {}".format(testPath)
|
|
print "JMRI path: {}".format(jmriPath)
|
|
print "Bundle path: {}".format(bundlePath)
|
|
print "Java format: {}".format(dotPath)
|
|
|
|
cntUse = 0
|
|
cntNot = 0
|
|
notUsedList = []
|
|
bundleType = "Standard" # Specified in Bundle.java, follow the tree structure
|
|
|
|
##
|
|
# Check Bundle.java
|
|
# If the selected file is not the specified bundle for this package,
|
|
# display a warning dialog. Set the bundleType variable to Single
|
|
##
|
|
try:
|
|
with io.open(join(fullPath, "Bundle.java"), "r", encoding="utf-8") as javaFile:
|
|
javaContent = javaFile.read()
|
|
checkName = dotPath + "." + bundleFile[:-11]
|
|
if javaContent.find(checkName) == -1:
|
|
bundleType = "Single"
|
|
JOptionPane.showMessageDialog(None,
|
|
"The selected property file is not in the bundle hierarchy.\nA single level search will be performed.",
|
|
dialogTitle, JOptionPane.INFORMATION_MESSAGE)
|
|
except IOError:
|
|
JOptionPane.showMessageDialog(None,
|
|
"Bundle.java does not exist.\nUsing a single level search.",
|
|
dialogTitle, JOptionPane.INFORMATION_MESSAGE)
|
|
bundleType = "Single"
|
|
|
|
##
|
|
# If a class references the current bundle outside of the bundle inheritance tree,
|
|
# see if any of the unused keys where used. If so, they can be removed from
|
|
# the unused list.
|
|
# chkPath : The full path to the class being checked
|
|
##
|
|
def checkKeys(chkPath):
|
|
remCnt = 0
|
|
notUsedWork = notUsedList[:]
|
|
|
|
with io.open(chkPath, "r", encoding="utf-8") as chkFile:
|
|
chkLine = chkFile.read()
|
|
for cKey in notUsedWork:
|
|
ck = '"{}"'.format(cKey)
|
|
if chkLine.find(ck) != -1:
|
|
try:
|
|
notUsedList.remove(cKey)
|
|
print " {} removed".format(cKey)
|
|
remCnt += 1
|
|
except ValueError:
|
|
print "Error removing {} from not list".format(cKey)
|
|
|
|
print " {} keys removed\n".format(remCnt)
|
|
|
|
##
|
|
# Follow the file tree recursively
|
|
# Search each java or python source file for occurrences of a key
|
|
# key : The current bundle key or package name
|
|
# path : The starting path for the file recursion
|
|
# mode : Key = quiet, return on match; Reference = Print matches
|
|
# return : True as soon as a match is found in Key mode; otherwise False
|
|
##
|
|
def searchFiles(key, path, mode):
|
|
if mode == "Key":
|
|
k = '"{}"'.format(key)
|
|
else:
|
|
k = key
|
|
for root, dirNames, fileNames in os.walk(path):
|
|
for fileName in fileNames:
|
|
if fileName[-5:] == ".java" or fileName[-3:] == ".py":
|
|
with io.open(join(root, fileName), "r", encoding="utf-8") as reFile:
|
|
reLine = reFile.read()
|
|
if reLine.find(k) != -1:
|
|
if mode == "Key":
|
|
return True
|
|
if reLine.find("public class Bundle extends") == -1:
|
|
print " {}".format(join(root, fileName))
|
|
checkKeys(join(root, fileName))
|
|
if bundleType == "Single" and mode == "Key":
|
|
return False
|
|
return False
|
|
|
|
##
|
|
# Phase 1
|
|
# For each key in the property file, search the directory tree for
|
|
# any usage. The normal case is that any uses will be at or below the
|
|
# directory that contains the property file.
|
|
##
|
|
print "\n---- Bundle Key Not Used List ----"
|
|
with io.open(selectedBundle, "r", encoding="utf-8") as propertyFile:
|
|
for propertyLine in propertyFile:
|
|
splitLine = propertyLine.split("=")
|
|
if len(splitLine) > 1:
|
|
propertyKey = splitLine[0].strip()
|
|
if searchFiles(propertyKey, fullPath, "Key"):
|
|
cntUse += 1
|
|
else:
|
|
# Not used in the src path, try the test path
|
|
if searchFiles(propertyKey, testPath, "Key"):
|
|
cntUse += 1
|
|
else:
|
|
cntNot += 1
|
|
print " {}".format(propertyKey)
|
|
notUsedList.append(propertyKey)
|
|
print "\nSummary: {} used, {} not used".format(cntUse, cntNot)
|
|
midTime = time()
|
|
|
|
##
|
|
# Phase 2
|
|
# Find any external references to the property file. This covers those
|
|
# cases where bundle inheritance was not used.
|
|
# The proper Bundle.java should always be listed.
|
|
##
|
|
bundleName = bundleFile.split(".")
|
|
bundleArg = ".".join([dotPath, bundleName[0]])
|
|
print "\n---- Find Bundle References :: {}".format(bundleArg)
|
|
searchFiles(bundleArg, jmriPath, "References")
|
|
searchFiles(bundleArg, join(FileUtil.getProgramPath(), "jython"), "References")
|
|
|
|
print "Final notUsedList size = {}".format(len(notUsedList))
|
|
endTime = time()
|
|
|
|
print "\nTiming"
|
|
print (" Phase 1: {}").format(midTime-startTime)
|
|
print (" Phase 2: {}").format(endTime-midTime)
|
|
print (" Total: {}").format(endTime-startTime)
|
|
|
|
##
|
|
# Provide the option to export the unused key list to an external file.
|
|
##
|
|
saveResp = JOptionPane.showConfirmDialog(None, "Do you want to save the unused key list?", dialogTitle, JOptionPane.YES_NO_OPTION)
|
|
if saveResp == 0:
|
|
fo = JFileChooser(FileUtil.getUserFilesPath())
|
|
fo.setDialogTitle(dialogTitle)
|
|
ret = fo.showSaveDialog(None)
|
|
if ret == JFileChooser.APPROVE_OPTION:
|
|
keyFile = fo.getSelectedFile().toString()
|
|
with io.open(keyFile, "w", encoding="utf-8") as outputFile:
|
|
outputFile.write("\n".join(notUsedList))
|
|
|
|
##
|
|
# Provide the option to update the property files.
|
|
# All of the files for the current bundle name will be updated.
|
|
# A backup will be created for each file before it is updated. 4 copies of the backup will be retained.
|
|
# The unused keys will be converted to comments with the NotUsed tag.
|
|
# Note: If the Java tree is managed by Git, the backups will create new file entries.
|
|
# These backup files should not be included in a commit.
|
|
##
|
|
updateResp = JOptionPane.showConfirmDialog(None, "Do you want to update the property files?", dialogTitle, JOptionPane.YES_NO_OPTION)
|
|
if updateResp == 0:
|
|
globArgument = join(fullPath, bundleFile[:-11] + "*properties")
|
|
print globArgument
|
|
for propName in glob.glob(globArgument):
|
|
print "---- Update {}".format(propName)
|
|
FileUtil.backup(FileUtil.getFile(propName))
|
|
with io.open(propName, "r+", encoding="utf-8") as ioFile:
|
|
propLines = ioFile.readlines()
|
|
ioFile.seek(0)
|
|
ioFile.truncate()
|
|
for propLine in propLines:
|
|
splitLine = propLine.split("=")
|
|
if len(splitLine) > 1:
|
|
checkKey = splitLine[0].strip()
|
|
if notUsedList.count(checkKey) > 0:
|
|
propLine = "#NotUsed " + propLine
|
|
ioFile.write(propLine)
|
|
|
|
print "\n{} Done".format(dialogTitle)
|