#!/usr/bin/env python import argparse import os import re import sys import time import xml.etree.ElementTree class Actor: def __init__(self, mod_name, vfs_path): self.mod_name = mod_name self.vfs_path = vfs_path self.name = os.path.basename(vfs_path) self.textures = [] self.material = '' def read(self, physical_path): try: tree = xml.etree.ElementTree.parse(physical_path) except xml.etree.ElementTree.ParseError as err: sys.stderr.write('Error in "%s": %s\n' % (physical_path, err.msg)) return False root = tree.getroot() for element in root.findall('.//material'): self.material = element.text for element in root.findall('.//texture'): self.textures.append(element.get('name')) return True class Material: def __init__(self, mod_name, vfs_path): self.mod_name = mod_name self.vfs_path = vfs_path self.name = os.path.basename(vfs_path) self.required_textures = [] def read(self, physical_path): try: root = xml.etree.ElementTree.parse(physical_path).getroot() except xml.etree.ElementTree.ParseError as err: sys.stderr.write('Error in "%s": %s\n' % (physical_path, err.msg)) return False for element in root.findall('.//required_texture'): texture_name = element.get('name') self.required_textures.append(texture_name) return True class Validator: def __init__(self, vfs_root, mods=None): if mods is None: mods = ['mod', 'public'] self.vfs_root = vfs_root self.mods = mods self.materials = {} self.invalid_materials = {} self.actors = [] def get_physical_path(self, mod_name, vfs_path): return os.path.realpath(os.path.join(self.vfs_root, mod_name, vfs_path)) def find_mod_files(self, mod_name, vfs_path, pattern): physical_path = self.get_physical_path(mod_name, vfs_path) result = [] if not os.path.isdir(physical_path): return result for file_name in os.listdir(physical_path): if file_name == '.git' or file_name == '.svn': continue vfs_file_path = os.path.join(vfs_path, file_name) physical_file_path = os.path.join(physical_path, file_name) if os.path.isdir(physical_file_path): result += self.find_mod_files(mod_name, vfs_file_path, pattern) elif os.path.isfile(physical_file_path) and pattern.match(file_name): result.append({ 'mod_name': mod_name, 'vfs_path': vfs_file_path }) return result def find_all_mods_files(self, vfs_path, pattern): result = [] for mod_name in reversed(self.mods): result += self.find_mod_files(mod_name, vfs_path, pattern) return result def find_materials(self, vfs_path): material_files = self.find_all_mods_files(vfs_path, re.compile(r'.*\.xml')) for material_file in material_files: material_name = os.path.basename(material_file['vfs_path']) if material_name in self.materials: continue material = Material(material_file['mod_name'], material_file['vfs_path']) if material.read(self.get_physical_path(material_file['mod_name'], material_file['vfs_path'])): self.materials[material_name] = material else: self.invalid_materials[material_name] = material def find_actors(self, vfs_path): actor_files = self.find_all_mods_files(vfs_path, re.compile(r'.*\.xml')) for actor_file in actor_files: actor = Actor(actor_file['mod_name'], actor_file['vfs_path']) if actor.read(self.get_physical_path(actor_file['mod_name'], actor_file['vfs_path'])): self.actors.append(actor) def run(self): start_time = time.time() sys.stdout.write('Collecting list of files to check\n') self.find_materials(os.path.join('art', 'materials')) self.find_actors(os.path.join('art', 'actors')) for actor in self.actors: if not actor.material: continue if actor.material not in self.materials and actor.material not in self.invalid_materials: sys.stderr.write('Error in "%s": unknown material "%s"' % ( self.get_physical_path(actor.mod_name, actor.vfs_path), actor.material )) if actor.material not in self.materials: continue material = self.materials[actor.material] for required_texture in material.required_textures: if required_texture in actor.textures: continue sys.stderr.write('Error in "%s": actor does not contain required texture "%s" from "%s"\n' % ( self.get_physical_path(actor.mod_name, actor.vfs_path), required_texture, material.name )) for texture in actor.textures: if texture in material.required_textures: continue sys.stderr.write('Warning in "%s": actor contains unnecessary texture "%s" from "%s"\n' % ( self.get_physical_path(actor.mod_name, actor.vfs_path), texture, material.name )) finish_time = time.time() sys.stdout.write('Total execution time: %.3f seconds.\n' % (finish_time - start_time)) if __name__ == '__main__': script_dir = os.path.dirname(os.path.realpath(__file__)) default_root = os.path.join(script_dir, '..', '..', '..', 'binaries', 'data', 'mods') parser = argparse.ArgumentParser(description='Actors/materials validator.') parser.add_argument('-r', '--root', action='store', dest='root', default=default_root) parser.add_argument('-m', '--mods', action='store', dest='mods', default='mod,public') args = parser.parse_args() validator = Validator(args.root, args.mods.split(',')) validator.run()