|
|
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.