mirror of
https://github.com/Wiimpathy/HatariWii.git
synced 2024-11-22 17:59:14 +01:00
271 lines
9.9 KiB
Python
271 lines
9.9 KiB
Python
|
#!/usr/bin/env python
|
||
|
#
|
||
|
# Class and helper functions for handling (Hatari) INI style
|
||
|
# configuration files: loading, saving, setting/getting variables,
|
||
|
# mapping them to sections, listing changes
|
||
|
#
|
||
|
# Copyright (C) 2008-2012 by Eero Tamminen
|
||
|
#
|
||
|
# This program is free software; you can redistribute it and/or modify
|
||
|
# it under the terms of the GNU General Public License as published by
|
||
|
# the Free Software Foundation; either version 2 of the License, or
|
||
|
# (at your option) any later version.
|
||
|
#
|
||
|
# This program is distributed in the hope that it will be useful,
|
||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
# GNU General Public License for more details.
|
||
|
|
||
|
import os
|
||
|
# mapping from Hatari config variable name to type id (Bool, Int, String)
|
||
|
from conftypes import conftypes
|
||
|
|
||
|
# ------------------------------------------------------
|
||
|
# Helper functions for type safe Hatari configuration variable access.
|
||
|
# Map booleans, integers and strings to Python types, and back to strings.
|
||
|
|
||
|
def value_to_text(key, value):
|
||
|
"value_to_text(key, value) -> text, convert Python type to string"
|
||
|
assert(key in conftypes)
|
||
|
valtype = type(value)
|
||
|
if valtype == bool:
|
||
|
assert(conftypes[key] == "Bool")
|
||
|
if value:
|
||
|
text = "TRUE"
|
||
|
else:
|
||
|
text = "FALSE"
|
||
|
elif valtype == int:
|
||
|
assert(conftypes[key] == "Int")
|
||
|
text = str(value)
|
||
|
else:
|
||
|
assert(conftypes[key] == "String")
|
||
|
if value == None:
|
||
|
text = ""
|
||
|
else:
|
||
|
text = value
|
||
|
return text
|
||
|
|
||
|
def text_to_value(text):
|
||
|
"text_to_value(text) -> value, convert INI file values to real types"
|
||
|
# bool?
|
||
|
upper = text.upper()
|
||
|
if upper == "FALSE":
|
||
|
value = False
|
||
|
elif upper == "TRUE":
|
||
|
value = True
|
||
|
else:
|
||
|
try:
|
||
|
# integer?
|
||
|
value = int(text)
|
||
|
except ValueError:
|
||
|
# string
|
||
|
value = text
|
||
|
return value
|
||
|
|
||
|
|
||
|
# ------------------------------------------------------
|
||
|
# Handle INI style configuration files as used by Hatari
|
||
|
|
||
|
class ConfigStore:
|
||
|
def __init__(self, userconfdir, defaults = {}, miss_is_error = True):
|
||
|
"ConfigStore(userconfdir, fgfile[,defaults,miss_is_error])"
|
||
|
self.defaults = defaults
|
||
|
self.userpath = self._get_full_userpath(userconfdir)
|
||
|
self.miss_is_error = miss_is_error
|
||
|
|
||
|
def _get_full_userpath(self, leafdir):
|
||
|
"get_userpath(leafdir) -> config file default save path from HOME, CWD or their subdir"
|
||
|
# user's hatari.cfg can be in home or current work dir,
|
||
|
# current dir is used only if $HOME fails
|
||
|
for path in (os.getenv("HOME"), os.getenv("HOMEPATH"), os.getcwd()):
|
||
|
if path and os.path.exists(path) and os.path.isdir(path):
|
||
|
if leafdir:
|
||
|
hpath = "%s%c%s" % (path, os.path.sep, leafdir)
|
||
|
if os.path.exists(hpath) and os.path.isdir(hpath):
|
||
|
return hpath
|
||
|
return path
|
||
|
return None
|
||
|
|
||
|
def get_filepath(self, filename):
|
||
|
"get_filepath(filename) -> return correct full path to config file"
|
||
|
# user config has preference over system one
|
||
|
for path in (self.userpath, os.getenv("HATARI_SYSTEM_CONFDIR")):
|
||
|
if path:
|
||
|
file = "%s%c%s" % (path, os.path.sep, filename)
|
||
|
if os.path.isfile(file):
|
||
|
return file
|
||
|
# writing needs path name although it's missing for reading
|
||
|
return "%s%c%s" % (self.userpath, os.path.sep, filename)
|
||
|
|
||
|
def load(self, path):
|
||
|
"load(path) -> load given configuration file"
|
||
|
if os.path.isfile(path):
|
||
|
sections = self._read(path)
|
||
|
if sections:
|
||
|
self.sections = sections
|
||
|
else:
|
||
|
print("ERROR: configuration file loading failed!")
|
||
|
return
|
||
|
else:
|
||
|
print("WARNING: configuration file missing!")
|
||
|
if self.defaults:
|
||
|
print("-> using dummy 'defaults'.")
|
||
|
self.sections = self.defaults
|
||
|
self.path = path
|
||
|
self.cfgfile = os.path.basename(path)
|
||
|
self.original = self.get_checkpoint()
|
||
|
self.changed = False
|
||
|
|
||
|
def is_loaded(self):
|
||
|
"is_loaded() -> True if configuration loading succeeded"
|
||
|
if self.sections:
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
def get_path(self):
|
||
|
"get_path() -> configuration file path"
|
||
|
return self.path
|
||
|
|
||
|
def _read(self, path):
|
||
|
"_read(path) -> (all keys, section2key mappings)"
|
||
|
print("Reading configuration file '%s'..." % path)
|
||
|
config = open(path, "r")
|
||
|
if not config:
|
||
|
return ({}, {})
|
||
|
name = "[_orphans_]"
|
||
|
seckeys = {}
|
||
|
sections = {}
|
||
|
for line in config.readlines():
|
||
|
line = line.strip()
|
||
|
if not line or line[0] == '#':
|
||
|
continue
|
||
|
if line[0] == '[':
|
||
|
if line in sections:
|
||
|
print("WARNING: section '%s' twice in configuration" % line)
|
||
|
if seckeys:
|
||
|
sections[name] = seckeys
|
||
|
seckeys = {}
|
||
|
name = line
|
||
|
continue
|
||
|
if line.find('=') < 0:
|
||
|
print("WARNING: line without key=value pair:\n%s" % line)
|
||
|
continue
|
||
|
key, text = [string.strip() for string in line.split('=')]
|
||
|
seckeys[key] = text_to_value(text)
|
||
|
if seckeys:
|
||
|
sections[name] = seckeys
|
||
|
return sections
|
||
|
|
||
|
def get_checkpoint(self):
|
||
|
"get_checkpoint() -> checkpoint, get the state of variables at this point"
|
||
|
checkpoint = {}
|
||
|
for section in self.sections.keys():
|
||
|
checkpoint[section] = self.sections[section].copy()
|
||
|
return checkpoint
|
||
|
|
||
|
def get_checkpoint_changes(self, checkpoint):
|
||
|
"get_checkpoint_changes() -> list of (key, value) pairs for later changes"
|
||
|
changed = []
|
||
|
if not self.changed:
|
||
|
return changed
|
||
|
for section in self.sections.keys():
|
||
|
if section not in checkpoint:
|
||
|
for key, value in self.sections[section].items():
|
||
|
changed.append((key, value))
|
||
|
continue
|
||
|
for key, value in self.sections[section].items():
|
||
|
if (key not in checkpoint[section] or
|
||
|
value != checkpoint[section][key]):
|
||
|
text = value_to_text(key, value)
|
||
|
changed.append(("%s.%s" % (section, key), text))
|
||
|
return changed
|
||
|
|
||
|
def revert_to_checkpoint(self, checkpoint):
|
||
|
"revert_to_checkpoint(checkpoint), revert to given checkpoint"
|
||
|
self.sections = checkpoint
|
||
|
|
||
|
def get(self, section, key):
|
||
|
return self.sections[section][key]
|
||
|
|
||
|
def set(self, section, key, value):
|
||
|
"set(section,key,value), set given key to given section"
|
||
|
if section not in self.sections:
|
||
|
if self.miss_is_error:
|
||
|
raise AttributeError("no section '%s'" % section)
|
||
|
self.sections[section] = {}
|
||
|
if key not in self.sections[section]:
|
||
|
if self.miss_is_error:
|
||
|
raise AttributeError("key '%s' not in section '%s'" % (key, section))
|
||
|
self.sections[section][key] = value
|
||
|
self.changed = True
|
||
|
elif self.sections[section][key] != value:
|
||
|
self.changed = True
|
||
|
self.sections[section][key] = value
|
||
|
|
||
|
def is_changed(self):
|
||
|
"is_changed() -> True if current configuration is changed"
|
||
|
return self.changed
|
||
|
|
||
|
def get_changes(self):
|
||
|
"get_changes(), return (key, value) list for each changed config option"
|
||
|
return self.get_checkpoint_changes(self.original)
|
||
|
|
||
|
def write(self, fileobj):
|
||
|
"write(fileobj), write current configuration to given file object"
|
||
|
sections = list(self.sections.keys())
|
||
|
sections.sort()
|
||
|
for name in sections:
|
||
|
fileobj.write("%s\n" % name)
|
||
|
keys = list(self.sections[name].keys())
|
||
|
keys.sort()
|
||
|
for key in keys:
|
||
|
value = value_to_text(key, self.sections[name][key])
|
||
|
fileobj.write("%s = %s\n" % (key, value))
|
||
|
fileobj.write("\n")
|
||
|
|
||
|
def save(self):
|
||
|
"save() -> path, if configuration changed, save it"
|
||
|
if not self.changed:
|
||
|
print("No configuration changes to save, skipping")
|
||
|
return None
|
||
|
fileobj = None
|
||
|
if self.path:
|
||
|
try:
|
||
|
fileobj = open(self.path, "w")
|
||
|
except:
|
||
|
pass
|
||
|
if not fileobj:
|
||
|
print("WARNING: non-existing/writable configuration file, creating a new one...")
|
||
|
if not os.path.exists(self.userpath):
|
||
|
os.makedirs(self.userpath)
|
||
|
self.path = "%s%c%s" % (self.userpath, os.path.sep, self.cfgfile)
|
||
|
fileobj = open(self.path, "w")
|
||
|
if not fileobj:
|
||
|
print("ERROR: opening '%s' for saving failed" % self.path)
|
||
|
return None
|
||
|
self.write(fileobj)
|
||
|
print("Saved configuration file:", self.path)
|
||
|
self.changed = False
|
||
|
return self.path
|
||
|
|
||
|
def save_as(self, path):
|
||
|
"save_as(path) -> path, save configuration to given file and select it"
|
||
|
assert(path)
|
||
|
if not os.path.exists(os.path.dirname(path)):
|
||
|
os.makedirs(os.path.dirname(path))
|
||
|
self.path = path
|
||
|
self.changed = True
|
||
|
return self.save()
|
||
|
|
||
|
def save_tmp(self, path):
|
||
|
"save_tmp(path) -> path, save configuration to given file without selecting it"
|
||
|
if not os.path.exists(os.path.dirname(path)):
|
||
|
os.makedirs(os.path.dirname(path))
|
||
|
fileobj = open(path, "w")
|
||
|
if not fileobj:
|
||
|
print("ERROR: opening '%s' for saving failed" % path)
|
||
|
return None
|
||
|
self.write(fileobj)
|
||
|
print("Saved temporary configuration file:", path)
|
||
|
return path
|