The problem above is caused by merging two Python dicts. When merged dicts have any of the same keys, the second one overwrites the first. I only learned enough Python to be dangerous, but here's a fix:
#!/usr/bin/python3
from zipfile import ZipFile
from glob import glob
from os.path import join, split, splitext, exists, basename, isdir, dirname
import sys
import argparse
from pathlib import Path
from json import load
import re
def dpPrint(text: str, toFile: str = ''):
print(text, flush=True)
if toFile:
try:
with open(toFile, 'a', encoding='utf-8') as f:
f.write(text + '\n')
except Exception as error:
print(f"ERROR, failed to write to {toFile}: {error}", file=sys.stderr, flush=True)
return
def check_name_variation(varName, dependenciesList):
parts = varName.split('.')
if parts[-1].isdigit() or parts[-1] == 'latest':
baseName = '.'.join(parts[:-1])
else:
baseName = varName
return varName in dependenciesList or f'{baseName}.latest' in dependenciesList
def getDependencies(varFile: str) -> tuple[Exception|None, set]:
try:
with ZipFile(varFile) as myzip:
with myzip.open('meta.json') as metaJson:
data = load(metaJson)
myzip.close()
if 'dependencies' not in data:
return None, set()
return None, set(data['dependencies'].keys())
except Exception as error:
return error, set()
# Get all dependencies listed in presets
def getPresetDependencies(customPath: str) -> dict:
allDependencies = {}
presets = sorted(glob(join(customPath, 'Custom', '**/*.vap'), recursive=True))
for vap in presets:
try:
with open(vap, 'r', encoding='utf-8') as f:
pattern = r'"([^"]+):/[^"]*"'
occurences = set(re.findall(pattern, f.read()))
except Exception as error:
dpPrint(f"ERROR, failed to read: {vap}: {error}", args.output)
continue
parts = Path(vap).parts
presetName = '/'.join(parts[parts.index("Custom")+1:])
dependenciesDict = {key: {presetName} for key in occurences}
for key, val in dependenciesDict.items():
if key in allDependencies:
allDependencies[key].update(val)
else:
allDependencies[key] = val
return allDependencies
# Collect all vars, a complete list of all dependencies, and which vars uses which dependency
def getAllVars(directoryPath: str) -> tuple[set, dict]:
allDependencies = {}
vars = glob(join(directoryPath, 'AddonPackages', '**/*.var'), recursive=True)
allVarList = {basename(var) for var in vars}
for varFilename in vars:
if isdir(varFilename):
error, dependencies = getMetaDependencies(varFilename)
else:
error, dependencies = getDependencies(varFilename)
if (isinstance(error, Exception)):
dpPrint(f"ERROR: {varFilename}: {error}", args.output)
continue
if (len(dependencies) == 0):
continue
dependenciesDict = {key: {split(varFilename)[1]} for key in dependencies}
for key, val in dependenciesDict.items():
if key in allDependencies:
allDependencies[key].update(val)
else:
allDependencies[key] = val
return allVarList, allDependencies
# Get all dependencies listed in meta.json stored directly in folders
def getMetaDependencies(folder: str) -> tuple[Exception|None, set]:
filename = join(folder, 'meta.json')
try:
with open(filename, 'r', encoding='utf-8') as metaJson:
data = load(metaJson)
if 'dependencies' not in data:
return None, set()
return None, set(data['dependencies'].keys())
except Exception as error:
return error, set()
argParser = argparse.ArgumentParser(prog='DependencyScanner.py', description='Searches for dependencies inside all vars in a folder and if a name is given, lists whether there a vars that depend on it. If no name is given, it will instead list all vars in the folder which isn\'t a dependency of any other var.')
argParser.add_argument("-p", "--path",
type=Path,
default='.',
help="Path your VaM folder")
argParser.add_argument("-n", "--name",
type=str,
default='',
help="Name of a var file. Can be a partial name")
argParser.add_argument("-o", "--output",
type=Path,
nargs='?',
default=None,
const='.\\SearchVarOutput.txt',
help="File to save the results in (default: '.\\SearchVarOutput.txt')")
args = argParser.parse_args()
# Check the given arguments are valid
if not exists(args.path):
print(f"The given path doesn't exist: {args.path}")
exit(1)
if args.output:
if isdir(args.output):
print(f"The given output is a folder, not a file: {args.output}")
exit(1)
if not isdir(dirname(args.output)) and not basename(args.output) != '':
print(f"The given output file folder path doesn't exist: {args.output}")
exit(1)
open(args.output, 'w', encoding='utf-8').close()
parts = args.name.split('.')
if parts[-1] == 'var':
args.name = '.'.join(parts[:-1])
# Get a list of all vars and a dict of all dependencies from vars
allVarList, allVarDependencies = getAllVars(args.path)
allDepVars = allVarDependencies.keys()
# Get a dict of all dependencies from presets
allPresetDependencies = getPresetDependencies(args.path)
allDepVarsPresets = allPresetDependencies.keys()
# Combine all dependencies
# Incorrect! Must keep separate dicts till later
# allDependencies = allVarDependencies | allPresetDependencies
# allDependencies = dict(list(allVarDependencies.items()) + list(allPresetDependencies.items()))
# Get a list of which vars in the folder that are not used as a dependency
noDependencyVar = [var for var in allVarList if not check_name_variation(var, allDepVars)]
noDependencyPreset = [var for var in allVarList if not check_name_variation(var, allDepVarsPresets)]
noDependency = sorted(list(set(noDependencyVar) & set(noDependencyPreset)), key=str.lower)
# If no name is given, list all vars that aren't used as a dependency
if args.name == '':
dpPrint(f"{len(noDependency)} vars are not used as a dependency:", args.output)
for var in noDependency:
dpPrint("\t" + var, args.output)
exit(0)
# List all vars with the given name that aren't used as a dependency
foundVars = [var for var in noDependency if args.name.lower() in var.lower()]
if len(foundVars) > 0:
dpPrint(f"The following {str(len(foundVars)) + ' vars were' if len(foundVars) > 1 else 'var was'} checked for dependency in other vars:", args.output)
for var in foundVars:
dpPrint("\t" + var, args.output)
print()
# List all vars with the given name that depend on other vars
# Keep separate lists for Var dependencies and Preset dependencies
foundDepAVars = sorted([var for var in allVarDependencies.keys() if args.name.lower() in var.lower()], key=str.lower)
foundDepPVars = sorted([var for var in allPresetDependencies.keys() if args.name.lower() in var.lower()], key=str.lower)
#foundDepVars = foundDepAVars + foundDepPVars
# Loop over both lists to print results
if len(foundDepAVars) > 0:
dpPrint(f"\nThe following {len(foundDepAVars)} iteration{'s' if len(foundDepAVars) > 1 else ''} of '" + args.name + "' has other vars that depend on it:", args.output)
# print all dependencies where the name is a key, or the name is part of the key
for key in foundDepAVars:
if args.name.lower() in key.lower():
if args.name != key:
dpPrint(f"{key} ->", args.output)
for var in sorted(allVarDependencies[key], key=str.lower):
dpPrint("\t" + var, args.output)
if len(foundDepPVars) > 0:
dpPrint(f"\nThe following {len(foundDepPVars)} iteration{'s' if len(foundDepPVars) > 1 else ''} of '" + args.name + "' has presets that depend on it:", args.output)
# print all dependencies where the name is a key, or the name is part of the key
for key in foundDepPVars:
if args.name.lower() in key.lower():
if args.name != key:
dpPrint(f"{key} ->", args.output)
for var in sorted(allPresetDependencies[key], key=str.lower):
dpPrint("\t" + var, args.output)
if args.output:
print(f"\nResults saved to: {args.output}")