SDL_GameControllerDB/check.py
2018-01-08 22:58:55 -05:00

336 lines
10 KiB
Python
Executable file

#!/usr/bin/env python
import sys
# if sys.version_info[0] != 3:
# print("This script requires Python 3.")
# exit(1)
from os import path
import string
import collections
import shutil
import argparse
import csv
import re
FILE_HEADER = "# Game Controller DB for SDL in 2.0.6 format\n\
# Source: https://github.com/gabomdq/SDL_GameControllerDB\n"
mappings_dict = {
"Windows": {},
"Mac OS X": {},
"Linux": {},
}
class Mapping:
BUTTON_REGEXES = {
"+a": re.compile(r"^[0-9]+\~?$"),
"-a": re.compile(r"^[0-9]+\~?$"),
"a": re.compile(r"^[0-9]+\~?$"),
"b": re.compile(r"^[0-9]+$"),
"h": re.compile(r"^[0-9]+\.(0|1|2|4|8|3|6|12|9)$"),
}
def __init__(self, mappingstring, linenumber):
self.guid = ""
self.name = ""
self.platform = ""
self.linenumber = 0
self.__keys = {
"+leftx": "",
"+lefty": "",
"+rightx": "",
"+righty"
"-leftx": "",
"-lefty": "",
"-rightx": "",
"-righty": "",
"a": "",
"b": "",
"back": "",
"dpdown": "",
"dpleft": "",
"dpright": "",
"dpup": "",
"guide": "",
"leftshoulder": "",
"leftstick": "",
"lefttrigger": "",
"leftx": "",
"lefty": "",
"rightshoulder": "",
"rightstick": "",
"righttrigger": "",
"rightx": "",
"righty": "",
"start": "",
"x": "",
"y": "",
}
self.linenumber = linenumber
reader = csv.reader([mappingstring], skipinitialspace=True)
mapping = next(reader)
mapping = list(filter(None, mapping))
self.set_guid(mapping[0])
mapping.pop(0)
self.set_name(mapping[0])
mapping.pop(0)
self.set_platform(mapping)
self.set_keys(mapping)
# Remove empty mappings.
self.__keys = {k:v for (k,v) in self.__keys.items() if v is not ""}
def set_guid(self, guid):
if guid == "xinput":
self.guid = guid
return
if len(guid) != 32:
raise ValueError("GUID length must be 32.", guid)
hex_digits = set(string.hexdigits)
if not all(c in hex_digits for c in guid):
raise ValueError("GUID malformed.", guid)
self.guid = guid
def set_name(self, name):
name = re.sub(r" +", " ", name)
self.name = name
def set_platform(self, mapping):
platform_kv = next((x for x in mapping if "platform:" in x), None)
if platform_kv == None:
raise ValueError("Required 'platform' field not found.")
platform = platform_kv.split(':')[1]
if platform not in mappings_dict.keys():
raise ValueError("Invalid platform.", platform)
self.platform = platform
index = mapping.index(platform_kv)
mapping.pop(index)
def set_keys(self, mapping):
throw = False
error_msg = ""
for kv in mapping:
button_key, button_val = kv.split(':')
if not button_key in self.__keys:
raise ValueError("Unrecognized key.", button_key)
# Gather duplicates.
if self.__keys[button_key] is not "":
throw = True
error_msg += "%s (was %s:%s), " \
% (kv, button_key, self.__keys[button_key])
continue
for butt,regex in self.BUTTON_REGEXES.items():
if not button_val.startswith(butt):
continue
val = button_val.replace(butt, "")
if not regex.match(val):
raise ValueError("Invalid value.", butt, val)
self.__keys[button_key] = button_val
break
if throw:
raise ValueError("Duplicate keys detected.", error_msg)
def __str__(self):
ret = "Mapping {\n guid: %s\n name: %s\n platform: %s\n" \
% (self.guid, self.name, self.platform)
ret += " Keys {\n"
for key,val in self.__keys.items():
ret += " %s: %s\n" % (key, val)
ret += " }\n}"
return ret
def serialize(self):
ret = "%s,%s," % (self.guid, self.name)
sorted_keys = sorted(self.__keys.items())
for key,val in sorted_keys:
ret += "%s:%s," % (key, val)
ret += "platform:%s," % (self.platform)
return ret
# https://hg.libsdl.org/SDL/rev/20855a38e048
def convert_guids(filename):
global current_line
global current_lineno
input_file = open(filename, 'r')
out_file = open("gamecontrollerdb_converted.txt", 'w')
for lineno, line in enumerate(input_file):
current_line = line
current_lineno = lineno + 1
if line.startswith('#') or line == '\n':
out_file.write(line)
continue
splitted = line[:-1].split(',', 2)
guid = splitted[0]
if get_platform(splitted[2]) == "Windows":
if guid[20:32] != "504944564944":
out_file.write(line)
continue
guid = guid[:20] + "000000000000"
guid = guid[:16] + guid[4:8] + guid[20:]
guid = guid[:8] + guid[:4] + guid[12:]
guid = "03000000" + guid[8:]
guid = guid.lower()
elif get_platform(splitted[2]) == "Mac OS X":
if guid[4:16] != "000000000000" or guid[20:32] != "000000000000":
out_file.write(line)
continue
guid = guid[:20] + "000000000000"
guid = guid[:8] + guid[:4] + guid[12:]
guid = "03000000" + guid[8:]
guid = guid.lower()
else:
out_file.write(line)
continue
out = line.replace(splitted[0], guid)
out_file.write(out)
print("\nConverted :\t" + splitted[0] + "\nTo :\t\t" + guid)
out_file.close()
input_file.close()
shutil.copyfile(input_file.name, ".bak." + input_file.name)
shutil.move("gamecontrollerdb_converted.txt", "gamecontrollerdb.txt")
def add_missing_platforms(filename):
global current_line
global current_lineno
input_file = open(filename, 'r')
out_file = open("gamecontrollerdb_platforms.txt", 'w')
for lineno, line in enumerate(input_file):
current_line = line
current_lineno = lineno + 1
if line.startswith('#') or line == '\n':
out_file.write(line)
continue
splitted = line[:-1].split(',', 2)
guid = splitted[0]
platform = get_platform(splitted[2])
if platform:
out_file.write(line)
continue
out = line[0:-1]
if guid[20:32] == "504944564944":
print("Adding Windows platform to #" + str(lineno) + " :\n" + line)
out += "platform:Windows,"
elif guid[4:16] == "000000000000" and guid[20:32] == "000000000000":
print("Adding Mac OS X platform to #" + str(lineno) + " :\n" + line)
out += "platform:Mac OS X,"
else:
print("Adding Linux platform to #" + str(lineno) + " :\n" + line)
out += "platform:Linux,"
out += "\n"
out_file.write(out)
out_file.close()
input_file.close()
shutil.copyfile(input_file.name, ".bak." + input_file.name)
shutil.move("gamecontrollerdb_platforms.txt", "gamecontrollerdb.txt")
def main():
parser = argparse.ArgumentParser()
parser.add_argument("input_file", help="database file to check, \
ex. gamecontrollerdb.txt")
parser.add_argument("--format", help="sorts, formats and removes duplicates",
action="store_true")
# Disable misc tools until refactor
# parser.add_argument("--convert_guids", help="convert Windows and macOS \
# GUIDs to the newer SDL 2.0.5 format. Skips checks!",
# action="store_true")
# parser.add_argument("--add_missing_platform", help="adds a platform \
# field if it is missing (on older pre 2.0.5 entries). Skips checks!",
# action="store_true")
args = parser.parse_args()
# Misc tools.
# if args.convert_guids:
# print("Converting GUIDs to SDL 2.0.5 format.")
# convert_guids(args.input_file)
# return
#
# if args.add_missing_platform:
# print("Adding missing platforms.")
# add_missing_platforms(args.input_file)
# return
# Tests.
print("Applying checks.")
global mappings_dict # { "platform": { "guid": Mapping }}
success = True
input_file = open(args.input_file, mode="r")
for lineno, line in enumerate(input_file):
if line.startswith('#') or line == '\n':
continue
try:
mapping = Mapping(line, lineno + 1)
except ValueError as e:
print("Error at line #" + str(lineno + 1))
print(e.args)
print("\nIn mapping")
print(line)
success = False
continue
if mapping.guid in mappings_dict[mapping.platform]:
print("Duplicate detected at line #" + str(lineno + 1))
prev_mapping = mappings_dict[mapping.platform][mapping.guid]
print("Previous mapping at line #" + str(prev_mapping.linenumber))
print("\nIn mapping")
print(line)
success = False
continue
mappings_dict[mapping.platform][mapping.guid] = mapping
input_file.close()
if success:
print("No mapping errors found.")
else:
sys.exit(1)
if args.format:
print("\nFormatting db.")
out_filename = path.splitext(input_file.name)[0] + "_format.txt"
out_file = open(out_filename, 'w')
out_file.write(FILE_HEADER)
for platform,p_dict in mappings_dict.items():
out_file.write("\n")
out_file.write("# " + platform + "\n")
sorted_p_dict = sorted(p_dict.items(),
key=lambda x: x[1].name.lower())
for guid,mapping in sorted_p_dict:
out_file.write(mapping.serialize() + "\n")
out_file.close()
backup_filename = (path.join(path.split(input_file.name)[0],
".bak." + path.split(input_file.name)[1]))
shutil.copyfile(input_file.name, backup_filename)
shutil.move(out_filename, input_file.name)
if __name__ == "__main__":
main()