|
|
1.1 ! root 1: #!/usr/bin/env python ! 2: # ! 3: # Class and helper functions for handling (Hatari) INI style ! 4: # configuration files: loading, saving, setting/getting variables, ! 5: # mapping them to sections, listing changes ! 6: # ! 7: # Copyright (C) 2008-2010 by Eero Tamminen <eerot at berlios> ! 8: # ! 9: # This program is free software; you can redistribute it and/or modify ! 10: # it under the terms of the GNU General Public License as published by ! 11: # the Free Software Foundation; either version 2 of the License, or ! 12: # (at your option) any later version. ! 13: # ! 14: # This program is distributed in the hope that it will be useful, ! 15: # but WITHOUT ANY WARRANTY; without even the implied warranty of ! 16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ! 17: # GNU General Public License for more details. ! 18: ! 19: import os ! 20: ! 21: # ------------------------------------------------------ ! 22: # Helper functions for type safe Hatari configuration variable access. ! 23: # Map booleans, integers and strings to Python types, and back to strings. ! 24: ! 25: def value_to_text(key, value): ! 26: "value_to_text(key, value) -> text, convert Python type to string" ! 27: valtype = type(value) ! 28: if valtype == bool: ! 29: assert(key[0] == "b") # bool prefix ! 30: if value: ! 31: text = "TRUE" ! 32: else: ! 33: text = "FALSE" ! 34: elif valtype == int: ! 35: assert(key[0] in ("n", "k")) # numeric/keycode prefix ! 36: text = str(value) ! 37: else: ! 38: assert(key[0] == "s") # string prefix ! 39: if value == None: ! 40: text = "" ! 41: else: ! 42: text = value ! 43: return text ! 44: ! 45: def text_to_value(text): ! 46: "text_to_value(text) -> value, convert INI file values to real types" ! 47: # bool? ! 48: upper = text.upper() ! 49: if upper == "FALSE": ! 50: value = False ! 51: elif upper == "TRUE": ! 52: value = True ! 53: else: ! 54: try: ! 55: # integer? ! 56: value = int(text) ! 57: except ValueError: ! 58: # string ! 59: value = text ! 60: return value ! 61: ! 62: ! 63: # ------------------------------------------------------ ! 64: # Handle INI style configuration files as used by Hatari ! 65: ! 66: class ConfigStore: ! 67: def __init__(self, userconfdir, defaults = {}, miss_is_error = True): ! 68: "ConfigStore(userconfdir, fgfile[,defaults,miss_is_error])" ! 69: self.defaults = defaults ! 70: self.userpath = self._get_full_userpath(userconfdir) ! 71: self.miss_is_error = miss_is_error ! 72: ! 73: def _get_full_userpath(self, leafdir): ! 74: "get_userpath(leafdir) -> config file default save path from HOME, CWD or their subdir" ! 75: # user's hatari.cfg can be in home or current work dir, ! 76: # current dir is used only if $HOME fails ! 77: for path in (os.getenv("HOME"), os.getenv("HOMEPATH"), os.getcwd()): ! 78: if path and os.path.exists(path) and os.path.isdir(path): ! 79: if leafdir: ! 80: hpath = "%s%c%s" % (path, os.path.sep, leafdir) ! 81: if os.path.exists(hpath) and os.path.isdir(hpath): ! 82: return hpath ! 83: return path ! 84: return None ! 85: ! 86: def get_filepath(self, filename): ! 87: "get_filepath(filename) -> return correct full path to config file" ! 88: # user config has preference over system one ! 89: for path in (self.userpath, os.getenv("HATARI_SYSTEM_CONFDIR")): ! 90: if path: ! 91: file = "%s%c%s" % (path, os.path.sep, filename) ! 92: if os.path.isfile(file): ! 93: return file ! 94: # writing needs path name although it's missing for reading ! 95: return "%s%c%s" % (self.userpath, os.path.sep, filename) ! 96: ! 97: def load(self, path): ! 98: "load(path) -> load given configuration file" ! 99: if os.path.isfile(path): ! 100: sections = self._read(path) ! 101: if sections: ! 102: self.sections = sections ! 103: else: ! 104: print "ERROR: configuration file loading failed!" ! 105: return ! 106: else: ! 107: print "WARNING: configuration file missing!" ! 108: if self.defaults: ! 109: print "-> using dummy 'defaults'." ! 110: self.sections = self.defaults ! 111: self.path = path ! 112: self.cfgfile = os.path.basename(path) ! 113: self.original = self.get_checkpoint() ! 114: self.changed = False ! 115: ! 116: def is_loaded(self): ! 117: "is_loaded() -> True if configuration loading succeeded" ! 118: if self.sections: ! 119: return True ! 120: return False ! 121: ! 122: def get_path(self): ! 123: "get_path() -> configuration file path" ! 124: return self.path ! 125: ! 126: def _read(self, path): ! 127: "_read(path) -> (all keys, section2key mappings)" ! 128: print "Reading configuration file '%s'..." % path ! 129: config = open(path, "r") ! 130: if not config: ! 131: return ({}, {}) ! 132: name = "[_orphans_]" ! 133: seckeys = {} ! 134: sections = {} ! 135: for line in config.readlines(): ! 136: line = line.strip() ! 137: if not line or line[0] == '#': ! 138: continue ! 139: if line[0] == '[': ! 140: if line in sections: ! 141: print "WARNING: section '%s' twice in configuration" % line ! 142: if seckeys: ! 143: sections[name] = seckeys ! 144: seckeys = {} ! 145: name = line ! 146: continue ! 147: if line.find('=') < 0: ! 148: print "WARNING: line without key=value pair:\n%s" % line ! 149: continue ! 150: key, text = [string.strip() for string in line.split('=')] ! 151: seckeys[key] = text_to_value(text) ! 152: if seckeys: ! 153: sections[name] = seckeys ! 154: return sections ! 155: ! 156: def get_checkpoint(self): ! 157: "get_checkpoint() -> checkpoint, get the state of variables at this point" ! 158: checkpoint = {} ! 159: for section in self.sections.keys(): ! 160: checkpoint[section] = self.sections[section].copy() ! 161: return checkpoint ! 162: ! 163: def get_checkpoint_changes(self, checkpoint): ! 164: "get_checkpoint_changes() -> list of (key, value) pairs for later changes" ! 165: changed = [] ! 166: if not self.changed: ! 167: return changed ! 168: for section in self.sections.keys(): ! 169: if section not in checkpoint: ! 170: for key, value in self.sections[section].items(): ! 171: changed.append((key, value)) ! 172: continue ! 173: for key, value in self.sections[section].items(): ! 174: if (key not in checkpoint[section] or ! 175: value != checkpoint[section][key]): ! 176: text = value_to_text(key, value) ! 177: changed.append(("%s.%s" % (section, key), text)) ! 178: return changed ! 179: ! 180: def revert_to_checkpoint(self, checkpoint): ! 181: "revert_to_checkpoint(checkpoint), revert to given checkpoint" ! 182: self.sections = checkpoint ! 183: ! 184: def get(self, section, key): ! 185: return self.sections[section][key] ! 186: ! 187: def set(self, section, key, value): ! 188: "set(section,key,value), set given key to given section" ! 189: if section not in self.sections: ! 190: if self.miss_is_error: ! 191: raise AttributeError, "no section '%s'" % section ! 192: self.sections[section] = {} ! 193: if key not in self.sections[section]: ! 194: if self.miss_is_error: ! 195: raise AttributeError, "key '%s' not in section '%s'" % (key, section) ! 196: self.sections[section][key] = value ! 197: self.changed = True ! 198: elif self.sections[section][key] != value: ! 199: self.changed = True ! 200: self.sections[section][key] = value ! 201: ! 202: def is_changed(self): ! 203: "is_changed() -> True if current configuration is changed" ! 204: return self.changed ! 205: ! 206: def get_changes(self): ! 207: "get_changes(), return (key, value) list for each changed config option" ! 208: return self.get_checkpoint_changes(self.original) ! 209: ! 210: def write(self, fileobj): ! 211: "write(fileobj), write current configuration to given file object" ! 212: sections = self.sections.keys() ! 213: sections.sort() ! 214: for name in sections: ! 215: fileobj.write("%s\n" % name) ! 216: keys = self.sections[name].keys() ! 217: keys.sort() ! 218: for key in keys: ! 219: value = value_to_text(key, self.sections[name][key]) ! 220: fileobj.write("%s = %s\n" % (key, value)) ! 221: fileobj.write("\n") ! 222: ! 223: def save(self): ! 224: "save() -> path, if configuration changed, save it" ! 225: if not self.changed: ! 226: print "No configuration changes to save, skipping" ! 227: return None ! 228: fileobj = None ! 229: if self.path: ! 230: try: ! 231: fileobj = open(self.path, "w") ! 232: except: ! 233: pass ! 234: if not fileobj: ! 235: print "WARNING: non-existing/writable configuration file, creating a new one..." ! 236: if not os.path.exists(self.userpath): ! 237: os.makedirs(self.userpath) ! 238: self.path = "%s%c%s" % (self.userpath, os.path.sep, self.cfgfile) ! 239: fileobj = open(self.path, "w") ! 240: if not fileobj: ! 241: print "ERROR: opening '%s' for saving failed" % self.path ! 242: return None ! 243: self.write(fileobj) ! 244: print "Saved configuration file:", self.path ! 245: self.changed = False ! 246: return self.path ! 247: ! 248: def save_as(self, path): ! 249: "save_as(path) -> path, save configuration to given file and select it" ! 250: assert(path) ! 251: if not os.path.exists(os.path.dirname(path)): ! 252: os.makedirs(os.path.dirname(path)) ! 253: self.path = path ! 254: self.changed = True ! 255: return self.save() ! 256: ! 257: def save_tmp(self, path): ! 258: "save_tmp(path) -> path, save configuration to given file without selecting it" ! 259: if not os.path.exists(os.path.dirname(path)): ! 260: os.makedirs(os.path.dirname(path)) ! 261: fileobj = open(path, "w") ! 262: if not fileobj: ! 263: print "ERROR: opening '%s' for saving failed" % path ! 264: return None ! 265: self.write(fileobj) ! 266: print "Saved temporary configuration file:", path ! 267: return path
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.