pgfw/pgfw/Configuration.py

505 lines
21 KiB
Python

import argparse
from os import sep, getcwd
from os.path import join, exists, basename, dirname, expanduser
from sys import argv, version_info
from re import match
from pprint import pformat
if version_info[0] >= 3:
from configparser import RawConfigParser
else:
from ConfigParser import RawConfigParser
class Configuration(RawConfigParser):
default_project_file_rel_path = "config"
default_resource_paths = [".", "resource"]
def __init__(self, project_file_rel_path=None, resource_path=None, type_declarations=None):
RawConfigParser.__init__(self)
self.project_file_rel_path = project_file_rel_path
self.resource_path = resource_path
self.modifiable = {}
self.order = []
self.set_type_declarations(type_declarations)
self.set_defaults()
self.read_project_config_file()
self.modify_defaults()
self.print_debug(self)
def set_type_declarations(self, type_declarations):
if type_declarations is None:
type_declarations = TypeDeclarations()
self.type_declarations = type_declarations
def translate_path(self, path):
new = ""
if path and path[0] == sep:
new += sep
return expanduser("{0}{1}".format(new, join(*path.split(sep))))
def set_defaults(self):
add_section = self.add_section
set_option = self.set
section = "setup"
add_section(section)
set_option(section, "package-root", basename(getcwd()), False)
set_option(section, "additional-packages", "", False)
set_option(section, "title", "", False)
set_option(section, "classifiers", "", False)
set_option(section, "resource-search-path", "./, resource/", False)
set_option(section, "installation-dir", "/usr/local/share/games/", False)
set_option(section, "changelog", "changelog", False)
set_option(section, "description-file", "", False)
set_option(section, "init-script", "", False)
set_option(section, "version", "", False)
set_option(section, "summary", "", False)
set_option(section, "license", "", False)
set_option(section, "platforms", "", False)
set_option(section, "contact-name", "", False)
set_option(section, "contact-email", "", False)
set_option(section, "url", "", False)
set_option(section, "requirements", "", False)
set_option(section, "main-object", "pgfw/Game.py", False)
set_option(section, "resource-path-identifier", "resource_path", False)
set_option(section, "special-char-placeholder", "_", False)
set_option(section, "whitespace-placeholder", "-", False)
set_option(section, "windows-dist-path", "dist/win/", False)
set_option(section, "windows-icon-path", "", False)
set_option(section, "boolean-true-lowercase", "yes, true, t, 1", False)
set_option(section, "osx-includes", "", False)
section = "display"
add_section(section)
set_option(section, "dimensions", "480, 360", False)
set_option(section, "frame-duration", "40", False)
set_option(section, "wait-duration", "2", False)
set_option(section, "caption", "", False)
set_option(section, "centered", "yes", False)
set_option(section, "icon-path", "", False)
set_option(section, "skip-frames", "no", False)
set_option(section, "fullscreen", "no", False)
set_option(section, "windowed-flag", "wi", False)
set_option(section, "show-framerate", "no", False)
set_option(section, "framerate-display-flag", "fr", False)
set_option(section, "framerate-text-size", "16", False)
set_option(section, "framerate-text-color", "0, 0, 0", False)
set_option(section, "framerate-text-background", "255, 255, 255", False)
set_option(section, "framerate-position", "-1, 0", False)
set_option(section, "use-framebuffer", "no", False)
section = "input"
add_section(section)
set_option(section, "release-suffix", "-release", False)
set_option(section, "confirm-quit", "no", False)
section = "sprite"
add_section(section)
set_option(section, "transparent-color", "magenta", False)
section = "screen-captures"
add_section(section)
set_option(section, "rel-path", "caps", False)
set_option(section, "file-name-format", "%Y%m%d%H%M%S", False)
set_option(section, "file-extension", "png", False)
section = "video-recordings"
add_section(section)
set_option(section, "enable", "no", False)
set_option(section, "rel-path", "vids", False)
set_option(section, "directory-name-format", "%Y%m%d%H%M%S", False)
set_option(section, "file-extension", "png", False)
set_option(section, "frame-format", "RGB", False)
set_option(section, "framerate", "40", False)
set_option(section, "temp-directory", "", False)
set_option(section, "record-audio", "yes", False)
set_option(section, "filename-digits", "6", False)
section = "mouse"
add_section(section)
set_option(section, "visible", "yes", False)
set_option(section, "double-click-time-limit", ".5", False)
section = "keys"
add_section(section)
set_option(section, "up", "K_UP", False)
set_option(section, "right", "K_RIGHT", False)
set_option(section, "down", "K_DOWN", False)
set_option(section, "left", "K_LEFT", False)
set_option(section, "capture-screen", "K_F9", False)
set_option(section, "toggle-fullscreen", "K_F11", False)
set_option(section, "reset-game", "K_F8", False)
set_option(section, "record-video", "K_F10", False)
set_option(section, "volume-down", "K_F1", False)
set_option(section, "volume-up", "K_F2", False)
set_option(section, "volume-mute", "K_F3", False)
set_option(section, "toggle-interpolator", "K_F7", False)
set_option(section, "toggle-audio-panel", "K_F6", False)
section = "joy"
add_section(section)
set_option(section, "advance", "7", False)
set_option(section, "pause", "7", False)
set_option(section, "select", "6", False)
set_option(section, "single-xy", "no", False)
set_option(section, "delay-axis", "0", False)
set_option(section, "vertical-axis", "1", False)
set_option(section, "horizontal-axis", "0", False)
section = "event"
add_section(section)
set_option(section, "user-event-id", "USEREVENT", False)
set_option(section, "command-id-offset", "1", False)
set_option(section, "command-key", "command", False)
set_option(section, "cancel-flag-key", "cancel", False)
section = "audio"
add_section(section)
set_option(section, "sfx-default-path", "~/storage/audio/sfx/default", False)
set_option(section, "sfx-repository-path", "~/storage/audio/sfx/all", False)
set_option(section, "sfx-project-path", "sfx", False)
set_option(section, "sfx-extensions", "wav, ogg, mp3", False)
set_option(section, "bgm-repository-path", "~/storage/audio/bgm", False)
set_option(section, "bgm-project-path", "bgm", False)
set_option(section, "sfx-volume", "1.0", False)
set_option(section, "bgm-volume", "1.0", False)
set_option(section, "volume", "1.0", False)
set_option(section, "panel-enabled", "no", False)
set_option(section, "panel-font", None, False)
set_option(section, "auto-load", "yes", False)
section = "interpolator-gui"
add_section(section)
set_option(section, "margin", "80", False)
set_option(section, "marker-color", "255, 0, 0", False)
set_option(section, "marker-size", "11", False)
set_option(section, "curve-color", "0, 255, 0", False)
set_option(section, "label-size", "16", False)
set_option(section, "label-precision", "2", False)
set_option(section, "axis-label-count", "8", False)
set_option(section, "prompt-size", "380, 60", False)
set_option(section, "prompt-border-color", "255, 0, 0", False)
set_option(section, "prompt-border-width", "3", False)
set_option(section, "prompt-character-limit", "21", False)
set_option(section, "prompt-text-size", "42", False)
set_option(section, "template-nodeset", "L 0 0, 1000 1", False)
set_option(section, "template-nodeset-name", "template", False)
set_option(section, "flat-y-range", "1", False)
def add_section(self, name):
if name not in self.order:
self.order.append(name)
RawConfigParser.add_section(self, name)
def set(self, section, option, value, modifiable=True):
if modifiable:
if section not in self.order:
self.order.append(section)
if section not in self.modifiable:
self.modifiable[section] = []
if option not in self.modifiable[section]:
self.modifiable[section].append(option)
RawConfigParser.set(self, section, option, value)
def read_project_config_file(self):
path = self.locate_project_config_file()
if path:
fp = open(path)
self.set_modifiable(fp)
fp.seek(0)
self.readfp(fp)
fp.seek(0)
self.set_order(fp)
fp.close()
else:
self.print_debug("No configuration file found")
def locate_project_config_file(self):
rel_path = self.project_file_rel_path
if not rel_path:
rel_path = self.default_project_file_rel_path
if exists(rel_path) and not self.is_shared_mode():
return rel_path
if self.resource_path:
installed_path = join(self.resource_path, rel_path)
if exists(installed_path):
return installed_path
def set_order(self, fp):
self.order = order = []
for line in open(self.locate_project_config_file()):
result = match("^\s*\[(.*)\]\s*$", line)
if result:
order.append(result.group(1))
def set_modifiable(self, fp):
config = RawConfigParser()
config.readfp(fp)
modifiable = self.modifiable
for section in config._sections:
if section not in modifiable:
modifiable[section] = []
for option in config._sections[section]:
if option != "__name__" and option not in modifiable[section]:
modifiable[section].append(option)
def is_shared_mode(self):
return "-s" in argv
def print_debug(self, statement):
if self.is_debug_mode():
print(statement)
def is_debug_mode(self):
return "-d" in argv
def modify_defaults(self):
self.set_installation_path()
self.set_resource_search_path()
self.set_screen_captures_path()
self.set_video_recordings_path()
self.set_data_exclusion_list()
self.set_requirements()
def set_installation_path(self):
self.set("setup", "installation-path",
join(self.get("setup", "installation-dir"),
self.get("setup", "package-root")), False)
def set_resource_search_path(self):
section, option = "setup", "resource-search-path"
search_path = self.get(section, option)
if self.resource_path:
search_path.append(self.resource_path)
else:
search_path.append(self.get("setup", "installation-path"))
self.set(section, option, search_path, False)
def get(self, section, option):
value = RawConfigParser.get(self, section, option)
if value is None:
value = self.get_substitute(section, option)
return self.cast_value(section, option, value)
def get_substitute(self, section, option):
if section == "display":
if option == "caption":
return self.get("setup", "title")
def cast_value(self, section, option, value):
pair = section, option
types = self.type_declarations
# if type(value) == str or type(value) == unicode:
if type(value) == str:
if pair in types["bool"]:
return value.lower() in self.get("setup", "boolean-true-lowercase")
elif pair in types["int"]:
return int(value)
elif pair in types["float"]:
return float(value)
elif pair in types["path"]:
return self.translate_path(value)
elif pair in types["list"]:
if value == "":
return []
else:
return [member.strip() for member in value.split(types.list_member_sep)]
elif pair in types["int-list"]:
return [int(member) for member in value.split(types.list_member_sep)]
elif pair in types["float-list"]:
return [float(member) for member in value.split(types.list_member_sep)]
elif pair in types["path-list"]:
return [self.translate_path(member.strip()) for member in value.split(types.list_member_sep)]
return value
def set_screen_captures_path(self):
section, option = "screen-captures", "path"
if not self.has_option(section, option):
self.set(section, option, join(self.build_home_path(), self.get(section, "rel-path")), False)
def build_home_path(self):
return join("~", "." + self.get("setup", "package-root"))
def set_video_recordings_path(self):
section, option = "video-recordings", "path"
if not self.has_option(section, option):
self.set(section, option, join(self.build_home_path(), self.get(section, "rel-path")), False)
def set_data_exclusion_list(self):
section, option = "setup", "data-exclude"
exclude = []
if self.has_option(section, option):
exclude = self.get(section, option)
exclude += [".git*", "README", "build/", "dist/", "*.egg-info",
"*.py", "MANIFEST*", "PKG-INFO", "*.pyc", "*.swp", "*~",
self.get("setup", "changelog"),
self.get("setup", "package-root"),
self.get("setup", "init-script")]
for location in self.get("setup", "additional-packages"):
exclude.append(location)
self.set(section, option, exclude, False)
def set_requirements(self):
section, option = "setup", "requirements"
requirements = []
if self.has_option(section, option):
requirements = self.get(section, option)
if "pygame" not in requirements:
requirements.append("pygame")
self.set(section, option, requirements, False)
def get_section(self, section):
assignments = {}
for option in self.options(section):
assignments[option] = self.get(section, option)
return assignments
def __repr__(self):
config = {}
for section in self.sections():
config[section] = self.get_section(section)
return pformat(config, 2, 1)
def items(self, section):
items = []
for option in self.options(section):
items.append((option, self.get(section, option)))
return items
def write(self, fp=None):
modifiable = self.modifiable
use_main = fp is None
if use_main:
path = self.locate_project_config_file()
if not path:
path = join(self.resource_path or "", self.default_project_file_rel_path)
fp = open(path, "w")
break_line = False
for section in self.order:
if section in modifiable:
if break_line:
fp.write("\n")
fp.write("[%s]\n" % section)
for option in modifiable[section]:
if self.has_option(section, option):
raw_value = self.get_raw_value(self.get(section, option))
# handle multiline entries
if "\n" in raw_value:
lines = raw_value.split("\n")
fp.write("{} = {}\n".format(option, lines[0]))
for line in lines[1:]:
# have to indent lines after the first to be compatible with configparser
fp.write(" {}\n".format(line))
else:
fp.write("{} = {}\n".format(option, raw_value))
break_line = True
if use_main:
fp.close()
def get_raw_value(self, value):
if isinstance(value, list):
raw = ""
for ii, value in enumerate(value):
if ii:
raw += ", "
raw += str(value)
else:
raw = str(value)
return raw
def clear_section(self, section):
if self.has_section(section):
for option in self.options(section):
self.remove_option(section, option)
def merge_command_line(self, command_line=None):
"""
Instantiate an argument parser to parse the command line. Load all settings submitted through the `--config` flag. Each setting
is a string starting with the chosen delimiter, followed by the section name, option name, and option value each separated by the
chosen delimiter.
For example, set the screen dimensions using "|" as the delimiter and set the Q key to the quit command using "," as the
delimiter,
$ ./OPEN-GAME --config "|display|dimensions|960,540" ",keys,quit,K_q"
@param command_line A list of strings to use as the command line. If this is `None`, `sys.argv` is used instead.
"""
parser = argparse.ArgumentParser()
parser.add_argument("--config", nargs="*", default=[])
# Loop over all assignment strings found
for assignment in parser.parse_known_args(command_line)[0].config:
# Split each assigment string by the first character in the string and pass along the parsed values as arguments
# to the set member function
self.set(*assignment.split(assignment[0])[1:])
class TypeDeclarations(dict):
list_member_sep = ','
defaults = {
"display": {"int": ["frame-duration", "wait-duration", "framerate-text-size"],
"bool": ["centered", "skip-frames", "fullscreen", "show-framerate", "use-framebuffer"],
"int-list": ["dimensions", "framerate-text-color", "framerate-text-background", "framerate-position"]},
"input": {"bool": "confirm-quit"},
"screen-captures": {"path": ["rel-path", "path"]},
"video-recordings": {"path": ["rel-path", "path"],
"int": ["framerate", "filename-digits"],
"bool": ["enable", "record-audio"]},
"setup": {"list": ["classifiers", "resource-search-path", "requirements", "data-exclude", "additional-packages", "osx-includes",
"boolean-true-lowercase"],
"path": ["installation-dir", "changelog", "description-file", "main-object", "icon-path", "windows-dist-path", "package-root"]},
"mouse": {"float": "double-click-time-limit",
"bool": "visible"},
"keys": {"list": ["up", "right", "down", "left"]},
"joy": {"int": ["advance", "pause", "select", "vertical-axis", "horizontal-axis"],
"float": "delay-axis",
"bool": "single-xy"},
"audio": {
"list": "sfx-extensions",
"path": "panel-font",
"path-list": ["sfx-default-path", "sfx-repository-path", "sfx-project-path", "bgm-repository-path", "bgm-project-path"],
"float": ["sfx-volume", "bgm-volume", "volume"],
"bool": ["panel-enabled", "auto-load"]
},
"event": {"int": "command-id-offset"},
"interpolator-gui": {"int": ["margin", "marker-size", "label-size", "axis-label-count", "label-precision", "prompt-border-width",
"prompt-character-limit", "prompt-text-size", "flat-y-range"],
"int-list": ["marker-color", "curve-color", "prompt-size", "prompt-border-color"]},
}
additional_defaults = {}
def __init__(self):
dict.__init__(self, {"bool": [], "int": [], "float": [], "path": [], "list": [], "int-list": [], "float-list": [], "path-list": []})
self.add_chart(self.defaults)
self.add_chart(self.additional_defaults)
def add(self, cast, section, option):
self[cast].append((section, option))
def add_chart(self, chart):
for section, declarations in chart.items():
for cast, options in declarations.items():
if type(options) != list:
options = [options]
for option in options:
self.add(cast, section, option)