1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
3 # This file is part of Hooke.
5 # Hooke is free software: you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation, either
8 # version 3 of the License, or (at your option) any later version.
10 # Hooke is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with Hooke. If not, see
17 # <http://www.gnu.org/licenses/>.
19 """Configuration defaults, read/write, and template file creation for
23 import ConfigParser as configparser
28 from .compat.odict import odict as OrderedDict
32 '/usr/share/hooke/hooke.cfg',
33 '/etc/hooke/hooke.cfg',
37 """We start with the system files, and work our way to the more
38 specific user files, so the user can override the sysadmin who
39 in turn overrides the developer defaults.
42 class Setting (object):
43 """An entry (section or option) in HookeConfigParser.
45 def __init__(self, section, option=None, value=None, help=None, wrap=True):
46 self.section = section
52 def __eq__(self, other):
53 for attr in ['__class__', 'section', 'option', 'value', 'help']:
54 value = getattr(self, attr)
55 o_value = getattr(other, attr)
61 return self.option == None
64 return not self.is_section()
66 def write(self, fp, value=None, comments=True, wrapper=None):
67 if comments == True and self.help != None:
71 wrapper = textwrap.TextWrapper(
73 subsequent_indent="# ")
74 text = wrapper.fill(text)
75 fp.write(text.rstrip()+'\n')
77 fp.write("[%s]\n" % self.section)
79 fp.write("%s = %s\n" % (self.option,
80 str(value).replace('\n', '\n\t')))
83 Setting('display', help='Control display appearance: colour, ???, etc.'),
84 Setting('display', 'colour_ext', 'None', help=None),
85 Setting('display', 'colour_ret', 'None', help=None),
86 Setting('display', 'ext', '1', help=None),
87 Setting('display', 'ret', '1', help=None),
89 Setting('display', 'correct', '1', help=None),
90 Setting('display', 'colout_correct', 'None', help=None),
91 Setting('display', 'contact_point', '0', help=None),
92 Setting('display', 'medfilt', '0', help=None),
94 Setting('display', 'xaxes', '0', help=None),
95 Setting('display', 'yaxes', '0', help=None),
96 Setting('display', 'flatten', '1', help=None),
98 Setting('conditions', 'temperature', '301', help=None),
100 Setting('fitting', 'auto_fit_points', '50', help=None),
101 Setting('fitting', 'auto_slope_span', '20', help=None),
102 Setting('fitting', 'auto_delta_force', '1-', help=None),
103 Setting('fitting', 'auto_fit_nm', '5', help=None),
104 Setting('fitting', 'auto_min_p', '0.005', help=None),
105 Setting('fitting', 'auto_max_p', '10', help=None),
107 Setting('?', 'baseline_clicks', '0', help=None),
108 Setting('fitting', 'auto_left_baseline', '20', help=None),
109 Setting('fitting', 'auto_right_baseline', '20', help=None),
110 Setting('fitting', 'force_multiplier', '1', help=None),
112 Setting('display', 'fc_showphase', '0', help=None),
113 Setting('display', 'fc_showimposed', '0', help=None),
114 Setting('display', 'fc_interesting', '0', help=None),
115 Setting('?', 'tccd_threshold', '0', help=None),
116 Setting('?', 'tccd_coincident', '0', help=None),
117 Setting('display', '', '', help=None),
118 Setting('display', '', '', help=None),
120 Setting('filesystem', 'filterindex', '0', help=None),
121 Setting('filesystem', 'filters',
122 "Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')",
124 Setting('filesystem', 'workdir', 'test',
125 help='\n'.join(['# Substitute your work directory',
126 '#workdir = D:\hooke']),
128 Setting('filesystem', 'playlist', 'test.hkp', help=None),
131 def get_setting(settings, match):
132 """Return the first Setting object matching both match.section and
136 if s.section == match.section and s.option == match.option:
140 class HookeConfigParser (configparser.SafeConfigParser):
141 """A wrapper around configparser.SafeConfigParser.
143 You will probably only need .read and .write.
149 >>> c = HookeConfigParser(paths=DEFAULT_PATHS,
150 ... default_settings=DEFAULT_SETTINGS)
151 >>> c.write(sys.stdout) # doctest: +ELLIPSIS
152 # Control display appearance: colour, ???, etc.
158 def __init__(self, paths=None, default_settings=None, defaults=None,
159 dict_type=OrderedDict, indent='# ', **kwargs):
160 # Can't use super() because SafeConfigParser is a classic class
161 #super(HookeConfigParser, self).__init__(defaults, dict_type)
162 configparser.SafeConfigParser.__init__(self, defaults, dict_type)
165 self._config_paths = paths
166 if default_settings == None:
167 default_settings = []
168 self._default_settings = default_settings
169 for key in ['initial_indent', 'subsequent_indent']:
170 if key not in kwargs:
172 self._comment_textwrap = textwrap.TextWrapper(**kwargs)
173 self._setup_default_settings()
175 def _setup_default_settings(self):
176 for setting in self._default_settings: #reversed(self._default_settings):
177 # reversed cause: http://docs.python.org/library/configparser.html
178 # "When adding sections or items, add them in the reverse order of
179 # how you want them to be displayed in the actual file."
180 if setting.section not in self.sections():
181 self.add_section(setting.section)
182 if setting.option != None:
183 self.set(setting.section, setting.option, setting.value)
185 def read(self, filenames=None):
186 """Read and parse a filename or a list of filenames.
188 If filenames is None, it defaults to ._config_paths. If a
189 filename is not in ._config_paths, it gets appended to the
190 list. We also run os.path.expanduser() on the input filenames
191 internally so you don't have to worry about it.
193 Files that cannot be opened are silently ignored; this is
194 designed so that you can specify a list of potential
195 configuration file locations (e.g. current directory, user's
196 home directory, systemwide directory), and all existing
197 configuration files in the list will be read. A single
198 filename may also be given.
200 Return list of successfully read files.
202 if filenames == None:
203 filenames = [os.path.expanduser(p) for p in self._config_paths]
205 if isinstance(filenames, basestring):
206 filenames = [filenames]
207 paths = [os.path.abspath(os.path.expanduser(p))
208 for p in self._config_paths]
209 for filename in filenames:
210 if os.path.abspath(os.path.expanduser(filename)) not in paths:
211 self._config_paths.append(filename)
212 # Can't use super() because SafeConfigParser is a classic class
213 #return super(HookeConfigParser, self).read(filenames)
214 return configparser.SafeConfigParser.read(self, filenames)
216 def _write_setting(self, fp, section=None, option=None, value=None,
218 """Print, if known, a nicely wrapped comment form of a
221 match = get_setting(self._default_settings, Setting(section, option))
223 match = Setting(section, option, value, **kwargs)
224 match.write(fp, value=value, wrapper=self._comment_textwrap)
226 def write(self, fp=None, comments=True):
227 """Write an .ini-format representation of the configuration state.
229 This expands on RawConfigParser.write() by optionally adding
230 comments when they are known (i.e. for ._default_settings).
231 However, comments are not read in during a read, so .read(x)
232 .write(x) may `not preserve comments`_.
234 .. _not preserve comments: http://bugs.python.org/issue1410680
239 >>> import sys, StringIO
240 >>> c = HookeConfigParser()
247 >>> c._read(StringIO.StringIO(instring), 'example.cfg')
248 >>> c.write(sys.stdout)
253 local_fp = fp == None
255 fp = open(os.path.expanduser(self._config_paths[-1]), 'w')
257 self._write_setting(fp, configparser.DEFAULTSECT,
258 help="Miscellaneous options")
259 for (key, value) in self._defaults.items():
260 self._write_setting(fp, configparser.DEFAULTSECT, key, value)
262 for section in self._sections:
263 self._write_setting(fp, section)
264 for (key, value) in self._sections[section].items():
265 if key != "__name__":
266 self._write_setting(fp, section, key, value)
271 class TestHookeConfigParser (unittest.TestCase):
272 def test_queue_safe(self):
273 """Ensure :class:`HookeConfigParser` is Queue-safe.
275 from multiprocessing import Queue
277 a = HookeConfigParser(
278 paths=DEFAULT_PATHS, default_settings=DEFAULT_SETTINGS)
281 for attr in ['_dict', '_defaults', '_sections', '_config_paths',
282 '_default_settings']:
283 a_value = getattr(a, attr)
284 b_value = getattr(b, attr)
287 'HookeConfigParser.%s did not survive Queue: %s != %s'
288 % (attr, a_value, b_value))