|
|
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 by Eero Tamminen <[email protected]> ! 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: defaultpath = "%s%c.hatari" % (os.path.expanduser("~"), os.path.sep) ! 68: ! 69: def __init__(self, cfgfile, defaults = {}, miss_is_error = True): ! 70: "ConfigStore(cfgfile[,defaults,miss_is_error])" ! 71: self.defaults = defaults ! 72: self.miss_is_error = miss_is_error ! 73: path = self._get_fullpath(cfgfile) ! 74: self.cfgfile = cfgfile ! 75: self.load(path) ! 76: ! 77: def load(self, path): ! 78: "load(path), load given configuration file" ! 79: if path: ! 80: sections = self._read(path) ! 81: if sections: ! 82: print "Loaded configuration file:", path ! 83: self.cfgfile = os.path.basename(path) ! 84: self.sections = sections ! 85: else: ! 86: print "ERROR: configuration file '%' loading failed" % path ! 87: return ! 88: else: ! 89: print "WARNING: configuration file missing, using defaults" ! 90: self.sections = self.defaults ! 91: self.path = path ! 92: self.original = self.get_checkpoint() ! 93: self.changed = False ! 94: ! 95: def is_loaded(self): ! 96: "is_loaded() -> True if configuration loading succeeded" ! 97: if self.sections: ! 98: return True ! 99: return False ! 100: ! 101: def get_path(self): ! 102: "get_path() -> configuration file path" ! 103: return self.path ! 104: ! 105: def _get_fullpath(self, cfgfile): ! 106: "get_fullpath(cfgfile) -> path or None, check first CWD & then HOME for cfgfile" ! 107: # hatari.cfg can be in home or current work dir ! 108: for path in (os.getcwd(), os.path.expanduser("~")): ! 109: if path: ! 110: path = self._check_path(path, cfgfile) ! 111: if path: ! 112: return path ! 113: return None ! 114: ! 115: def _check_path(self, path, cfgfile): ! 116: """check_path(path,cfgfile) -> path ! 117: ! 118: return full path if cfg in path/.hatari/ or in path prefixed with '.'""" ! 119: sep = os.path.sep ! 120: testpath = "%s%c.hatari%c%s" % (path, sep, sep, cfgfile) ! 121: if os.path.exists(testpath): ! 122: return testpath ! 123: testpath = "%s%c.%s" % (path, sep, cfgfile) ! 124: if os.path.exists(testpath): ! 125: return testpath ! 126: return None ! 127: ! 128: def _read(self, path): ! 129: "_read(path) -> (all keys, section2key mappings)" ! 130: config = open(path, "r") ! 131: if not config: ! 132: return ({}, {}) ! 133: name = "[_orphans_]" ! 134: seckeys = {} ! 135: sections = {} ! 136: for line in config.readlines(): ! 137: line = line.strip() ! 138: if not line or line[0] == '#': ! 139: continue ! 140: if line[0] == '[': ! 141: if line in sections: ! 142: print "WARNING: section '%s' twice in configuration" % line ! 143: if seckeys: ! 144: sections[name] = seckeys ! 145: seckeys = {} ! 146: name = line ! 147: continue ! 148: if line.find('=') < 0: ! 149: print "WARNING: line without key=value pair:\n%s" % line ! 150: continue ! 151: key, text = [string.strip() for string in line.split('=')] ! 152: seckeys[key] = text_to_value(text) ! 153: if seckeys: ! 154: sections[name] = seckeys ! 155: return sections ! 156: ! 157: def get_checkpoint(self): ! 158: "get_checkpoint() -> checkpoint, get the state of variables at this point" ! 159: checkpoint = {} ! 160: for section in self.sections.keys(): ! 161: checkpoint[section] = self.sections[section].copy() ! 162: return checkpoint ! 163: ! 164: def get_checkpoint_changes(self, checkpoint): ! 165: "get_checkpoint_changes() -> list of (key, value) pairs for later changes" ! 166: changed = [] ! 167: if not self.changed: ! 168: return changed ! 169: for section in self.sections.keys(): ! 170: if section not in checkpoint: ! 171: for key, value in self.sections[section].items(): ! 172: changed.append((key, value)) ! 173: continue ! 174: for key, value in self.sections[section].items(): ! 175: if (key not in checkpoint[section] or ! 176: value != checkpoint[section][key]): ! 177: text = value_to_text(key, value) ! 178: changed.append(("%s.%s" % (section, key), text)) ! 179: return changed ! 180: ! 181: def revert_to_checkpoint(self, checkpoint): ! 182: "revert_to_checkpoint(checkpoint), revert to given checkpoint" ! 183: self.sections = checkpoint ! 184: ! 185: def get(self, section, key): ! 186: return self.sections[section][key] ! 187: ! 188: def set(self, section, key, value): ! 189: "set(section,key,value), set given key to given section" ! 190: if section not in self.sections: ! 191: if self.miss_is_error: ! 192: raise AttributeError, "no section '%s'" % section ! 193: self.sections[section] = {} ! 194: if key not in self.sections[section]: ! 195: if self.miss_is_error: ! 196: raise AttributeError, "key '%s' not in section '%s'" % (key, section) ! 197: self.sections[section][key] = value ! 198: self.changed = True ! 199: elif self.sections[section][key] != value: ! 200: self.changed = True ! 201: self.sections[section][key] = value ! 202: ! 203: def is_changed(self): ! 204: "is_changed() -> True if current configuration is changed" ! 205: return self.changed ! 206: ! 207: def get_changes(self): ! 208: "get_changes(), return (key, value) list for each changed config option" ! 209: return self.get_checkpoint_changes(self.original) ! 210: ! 211: def write(self, fileobj): ! 212: "write(fileobj), write current configuration to given file object" ! 213: sections = self.sections.keys() ! 214: sections.sort() ! 215: for name in sections: ! 216: fileobj.write("%s\n" % name) ! 217: keys = self.sections[name].keys() ! 218: keys.sort() ! 219: for key in keys: ! 220: value = value_to_text(key, self.sections[name][key]) ! 221: fileobj.write("%s = %s\n" % (key, value)) ! 222: fileobj.write("\n") ! 223: ! 224: def save(self): ! 225: "save() -> path, if configuration changed, save it" ! 226: if not self.changed: ! 227: print "No configuration changes to save, skipping" ! 228: return None ! 229: if not self.path: ! 230: print "WARNING: no existing configuration file, trying to create one" ! 231: if not os.path.exists(self.defaultpath): ! 232: os.mkdir(self.defaultpath) ! 233: self.path = "%s%c%s" % (self.defaultpath, os.path.sep, self.cfgfile) ! 234: fileobj = open(self.path, "w") ! 235: if not fileobj: ! 236: print "ERROR: opening '%s' for saving failed" % self.path ! 237: return None ! 238: self.write(fileobj) ! 239: print "Saved configuration file:", self.path ! 240: self.changed = False ! 241: return path ! 242: ! 243: def save_as(self, path): ! 244: "save_as(path) -> path, save configuration to given file and select it" ! 245: assert(path) ! 246: if not os.path.exists(os.path.dirname(path)): ! 247: os.makedirs(os.path.dirname(path)) ! 248: self.path = path ! 249: self.changed = True ! 250: self.save() ! 251: return path ! 252: ! 253: def save_tmp(self, path): ! 254: "save_tmp(path) -> path, save configuration to given file without selecting it" ! 255: if not os.path.exists(os.path.dirname(path)): ! 256: os.makedirs(os.path.dirname(path)) ! 257: fileobj = open(path, "w") ! 258: if not fileobj: ! 259: print "ERROR: opening '%s' for saving failed" % path ! 260: return None ! 261: self.write(fileobj) ! 262: print "Saved temporary configuration file:", path ! 263: return path
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.