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