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