2 # -*- coding: utf-8 -*-
7 Configuration defaults, read/write, and template file creation for Hooke.
12 import ConfigParser as configparser
17 from .compat.odict import odict as OrderedDict
21 '/usr/share/hooke/hooke.cfg',
22 '/etc/hooke/hooke.cfg',
26 """We start with the system files, and work our way to the more
27 specific user files, so the user can override the sysadmin who
28 in turn overrides the developer defaults.
31 class Setting (object):
32 """An entry (section or option) in HookeConfigParser.
34 def __init__(self, section, option=None, value=None, help=None, wrap=True):
35 self.section = section
41 def __eq__(self, other):
42 for attr in ['__class__', 'section', 'option', 'value', 'help']:
43 value = getattr(self, attr)
44 o_value = getattr(other, attr)
50 return self.option == None
53 return not self.is_section()
55 def write(self, fp, value=None, comments=True, wrapper=None):
56 if comments == True and self.help != None:
60 wrapper = textwrap.TextWrapper(
62 subsequent_indent="# ")
63 text = wrapper.fill(text)
64 fp.write(text.rstrip()+'\n')
66 fp.write("[%s]\n" % self.section)
68 fp.write("%s = %s\n" % (self.option,
69 str(value).replace('\n', '\n\t')))
72 Setting('display', help='Control display appearance: colour, ???, etc.'),
73 Setting('display', 'colour_ext', 'None', help=None),
74 Setting('display', 'colour_ret', 'None', help=None),
75 Setting('display', 'ext', '1', help=None),
76 Setting('display', 'ret', '1', help=None),
78 Setting('display', 'correct', '1', help=None),
79 Setting('display', 'colout_correct', 'None', help=None),
80 Setting('display', 'contact_point', '0', help=None),
81 Setting('display', 'medfilt', '0', help=None),
83 Setting('display', 'xaxes', '0', help=None),
84 Setting('display', 'yaxes', '0', help=None),
85 Setting('display', 'flatten', '1', help=None),
87 Setting('conditions', 'temperature', '301', help=None),
89 Setting('fitting', 'auto_fit_points', '50', help=None),
90 Setting('fitting', 'auto_slope_span', '20', help=None),
91 Setting('fitting', 'auto_delta_force', '1-', help=None),
92 Setting('fitting', 'auto_fit_nm', '5', help=None),
93 Setting('fitting', 'auto_min_p', '0.005', help=None),
94 Setting('fitting', 'auto_max_p', '10', help=None),
96 Setting('?', 'baseline_clicks', '0', help=None),
97 Setting('fitting', 'auto_left_baseline', '20', help=None),
98 Setting('fitting', 'auto_right_baseline', '20', help=None),
99 Setting('fitting', 'force_multiplier', '1', help=None),
101 Setting('display', 'fc_showphase', '0', help=None),
102 Setting('display', 'fc_showimposed', '0', help=None),
103 Setting('display', 'fc_interesting', '0', help=None),
104 Setting('?', 'tccd_threshold', '0', help=None),
105 Setting('?', 'tccd_coincident', '0', help=None),
106 Setting('display', '', '', help=None),
107 Setting('display', '', '', help=None),
109 Setting('filesystem', 'filterindex', '0', help=None),
110 Setting('filesystem', 'filters',
111 "Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')",
113 Setting('filesystem', 'workdir', 'test',
114 help='\n'.join(['# Substitute your work directory',
115 '#workdir = D:\hooke']),
117 Setting('filesystem', 'playlist', 'test.hkp', help=None),
120 def get_setting(settings, match):
121 """Return the first Setting object matching both match.section and
125 if s.section == match.section and s.option == match.option:
129 class HookeConfigParser (configparser.SafeConfigParser):
130 """A wrapper around configparser.SafeConfigParser.
132 You will probably only need .read and .write.
138 >>> c = HookeConfigParser(paths=DEFAULT_PATHS,
139 ... default_settings=DEFAULT_SETTINGS)
140 >>> c.write(sys.stdout) # doctest: +ELLIPSIS
141 # Control display appearance: colour, ???, etc.
147 def __init__(self, paths=None, default_settings=None, defaults=None,
148 dict_type=OrderedDict, indent='# ', **kwargs):
149 # Can't use super() because SafeConfigParser is a classic class
150 #super(HookeConfigParser, self).__init__(defaults, dict_type)
151 configparser.SafeConfigParser.__init__(self, defaults, dict_type)
154 self._config_paths = paths
155 if default_settings == None:
156 default_settings = []
157 self._default_settings = default_settings
158 for key in ['initial_indent', 'subsequent_indent']:
159 if key not in kwargs:
161 self._comment_textwrap = textwrap.TextWrapper(**kwargs)
162 self._setup_default_settings()
164 def _setup_default_settings(self):
165 for setting in self._default_settings: #reversed(self._default_settings):
166 # reversed cause: http://docs.python.org/library/configparser.html
167 # "When adding sections or items, add them in the reverse order of
168 # how you want them to be displayed in the actual file."
169 if setting.section not in self.sections():
170 self.add_section(setting.section)
171 if setting.option != None:
172 self.set(setting.section, setting.option, setting.value)
174 def read(self, filenames=None):
175 """Read and parse a filename or a list of filenames.
177 If filenames is None, it defaults to ._config_paths. If a
178 filename is not in ._config_paths, it gets appended to the
179 list. We also run os.path.expanduser() on the input filenames
180 internally so you don't have to worry about it.
182 Files that cannot be opened are silently ignored; this is
183 designed so that you can specify a list of potential
184 configuration file locations (e.g. current directory, user's
185 home directory, systemwide directory), and all existing
186 configuration files in the list will be read. A single
187 filename may also be given.
189 Return list of successfully read files.
191 if filenames == None:
192 filenames = [os.path.expanduser(p) for p in self._config_paths]
194 if isinstance(filenames, basestring):
195 filenames = [filenames]
196 paths = [os.path.abspath(os.path.expanduser(p))
197 for p in self._config_paths]
198 for filename in filenames:
199 if os.path.abspath(os.path.expanduser(filename)) not in paths:
200 self._config_paths.append(filename)
201 # Can't use super() because SafeConfigParser is a classic class
202 #return super(HookeConfigParser, self).read(filenames)
203 return configparser.SafeConfigParser.read(self, filenames)
205 def _write_setting(self, fp, section=None, option=None, value=None,
207 """Print, if known, a nicely wrapped comment form of a
210 match = get_setting(self._default_settings, Setting(section, option))
212 match = Setting(section, option, value, **kwargs)
213 match.write(fp, value=value, wrapper=self._comment_textwrap)
215 def write(self, fp=None, comments=True):
216 """Write an .ini-format representation of the configuration state.
218 This expands on RawConfigParser.write() by optionally adding
219 comments when they are known (i.e. for ._default_settings).
220 However, comments are not read in during a read, so .read(x)
221 .write(x) may `not preserve comments`_.
223 .. _not preserve comments: http://bugs.python.org/issue1410680
228 >>> import sys, StringIO
229 >>> c = HookeConfigParser()
236 >>> c._read(StringIO.StringIO(instring), 'example.cfg')
237 >>> c.write(sys.stdout)
242 local_fp = fp == None
244 fp = open(os.path.expanduser(self._config_paths[-1]), 'w')
246 self._write_setting(fp, configparser.DEFAULTSECT,
247 help="Miscellaneous options")
248 for (key, value) in self._defaults.items():
249 self._write_setting(fp, configparser.DEFAULTSECT, key, value)
251 for section in self._sections:
252 self._write_setting(fp, section)
253 for (key, value) in self._sections[section].items():
254 if key != "__name__":
255 self._write_setting(fp, section, key, value)
260 class TestHookeConfigParser (unittest.TestCase):
261 def test_queue_safe(self):
262 """Ensure :class:`HookeConfigParser` is Queue-safe.
264 from multiprocessing import Queue
266 a = HookeConfigParser(
267 paths=DEFAULT_PATHS, default_settings=DEFAULT_SETTINGS)
270 for attr in ['_dict', '_defaults', '_sections', '_config_paths',
271 '_default_settings']:
272 a_value = getattr(a, attr)
273 b_value = getattr(b, attr)
276 'HookeConfigParser.%s did not survive Queue: %s != %s'
277 % (attr, a_value, b_value))