3 """Configuration defaults, read/write, and template file creation for
7 import ConfigParser as configparser
12 from .compat.odict import odict as OrderedDict
16 '/usr/share/hooke/hooke.cfg',
17 '/etc/hooke/hooke.cfg',
21 """We start with the system files, and work our way to the more
22 specific user files, so the user can override the sysadmin who
23 in turn overrides the developer defaults.
26 class Setting (object):
27 """An entry (section or option) in HookeConfigParser.
29 def __init__(self, section, option=None, value=None, help=None, wrap=True):
30 self.section = section
36 def __eq__(self, other):
37 for attr in ['__class__', 'section', 'option', 'value', 'help']:
38 value = getattr(self, attr)
39 o_value = getattr(other, attr)
45 return self.option == None
48 return not self.is_section()
50 def write(self, fp, value=None, comments=True, wrapper=None):
51 if comments == True and self.help != None:
55 wrapper = textwrap.TextWrapper(
57 subsequent_indent="# ")
58 text = wrapper.fill(text)
59 fp.write(text.rstrip()+'\n')
61 fp.write("[%s]\n" % self.section)
63 fp.write("%s = %s\n" % (self.option,
64 str(value).replace('\n', '\n\t')))
67 Setting('display', help='Control display appearance: colour, ???, etc.'),
68 Setting('display', 'colour_ext', 'None', help=None),
69 Setting('display', 'colour_ret', 'None', help=None),
70 Setting('display', 'ext', '1', help=None),
71 Setting('display', 'ret', '1', help=None),
73 Setting('display', 'correct', '1', help=None),
74 Setting('display', 'colout_correct', 'None', help=None),
75 Setting('display', 'contact_point', '0', help=None),
76 Setting('display', 'medfilt', '0', help=None),
78 Setting('display', 'xaxes', '0', help=None),
79 Setting('display', 'yaxes', '0', help=None),
80 Setting('display', 'flatten', '1', help=None),
82 Setting('conditions', 'temperature', '301', help=None),
84 Setting('fitting', 'auto_fit_points', '50', help=None),
85 Setting('fitting', 'auto_slope_span', '20', help=None),
86 Setting('fitting', 'auto_delta_force', '1-', help=None),
87 Setting('fitting', 'auto_fit_nm', '5', help=None),
88 Setting('fitting', 'auto_min_p', '0.005', help=None),
89 Setting('fitting', 'auto_max_p', '10', help=None),
91 Setting('?', 'baseline_clicks', '0', help=None),
92 Setting('fitting', 'auto_left_baseline', '20', help=None),
93 Setting('fitting', 'auto_right_baseline', '20', help=None),
94 Setting('fitting', 'force_multiplier', '1', help=None),
96 Setting('display', 'fc_showphase', '0', help=None),
97 Setting('display', 'fc_showimposed', '0', help=None),
98 Setting('display', 'fc_interesting', '0', help=None),
99 Setting('?', 'tccd_threshold', '0', help=None),
100 Setting('?', 'tccd_coincident', '0', help=None),
101 Setting('display', '', '', help=None),
102 Setting('display', '', '', help=None),
104 Setting('filesystem', 'filterindex', '0', help=None),
105 Setting('filesystem', 'filters',
106 "Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')",
108 Setting('filesystem', 'workdir', 'test',
109 help='\n'.join(['# Substitute your work directory',
110 '#workdir = D:\hooke']),
112 Setting('filesystem', 'playlist', 'test.hkp', help=None),
115 def get_setting(settings, match):
116 """Return the first Setting object matching both match.section and
120 if s.section == match.section and s.option == match.option:
124 class HookeConfigParser (configparser.SafeConfigParser):
125 """A wrapper around configparser.SafeConfigParser.
127 You will probably only need .read and .write.
133 >>> c = HookeConfigParser(paths=DEFAULT_PATHS,
134 ... default_settings=DEFAULT_SETTINGS)
135 >>> c.write(sys.stdout) # doctest: +ELLIPSIS
136 # Control display appearance: colour, ???, etc.
142 def __init__(self, paths=None, default_settings=None, defaults=None,
143 dict_type=OrderedDict, indent='# ', **kwargs):
144 # Can't use super() because SafeConfigParser is a classic class
145 #super(HookeConfigParser, self).__init__(defaults, dict_type)
146 configparser.SafeConfigParser.__init__(self, defaults, dict_type)
149 self._config_paths = paths
150 if default_settings == None:
151 default_settings = []
152 self._default_settings = default_settings
153 for key in ['initial_indent', 'subsequent_indent']:
154 if key not in kwargs:
156 self._comment_textwrap = textwrap.TextWrapper(**kwargs)
157 self._setup_default_settings()
159 def _setup_default_settings(self):
160 for setting in self._default_settings: #reversed(self._default_settings):
161 # reversed cause: http://docs.python.org/library/configparser.html
162 # "When adding sections or items, add them in the reverse order of
163 # how you want them to be displayed in the actual file."
164 if setting.section not in self.sections():
165 self.add_section(setting.section)
166 if setting.option != None:
167 self.set(setting.section, setting.option, setting.value)
169 def read(self, filenames=None):
170 """Read and parse a filename or a list of filenames.
172 If filenames is None, it defaults to ._config_paths. If a
173 filename is not in ._config_paths, it gets appended to the
174 list. We also run os.path.expanduser() on the input filenames
175 internally so you don't have to worry about it.
177 Files that cannot be opened are silently ignored; this is
178 designed so that you can specify a list of potential
179 configuration file locations (e.g. current directory, user's
180 home directory, systemwide directory), and all existing
181 configuration files in the list will be read. A single
182 filename may also be given.
184 Return list of successfully read files.
186 if filenames == None:
187 filenames = [os.path.expanduser(p) for p in self._config_paths]
189 if isinstance(filenames, basestring):
190 filenames = [filenames]
191 paths = [os.path.abspath(os.path.expanduser(p))
192 for p in self._config_paths]
193 for filename in filenames:
194 if os.path.abspath(os.path.expanduser(filename)) not in paths:
195 self._config_paths.append(filename)
196 # Can't use super() because SafeConfigParser is a classic class
197 #return super(HookeConfigParser, self).read(filenames)
198 return configparser.SafeConfigParser.read(self, filenames)
200 def _write_setting(self, fp, section=None, option=None, value=None,
202 """Print, if known, a nicely wrapped comment form of a
205 match = get_setting(self._default_settings, Setting(section, option))
207 match = Setting(section, option, value, **kwargs)
208 match.write(fp, value=value, wrapper=self._comment_textwrap)
210 def write(self, fp=None, comments=True):
211 """Write an .ini-format representation of the configuration state.
213 This expands on RawConfigParser.write() by optionally adding
214 comments when they are known (i.e. for ._default_settings).
215 However, comments are not read in during a read, so .read(x)
216 .write(x) may `not preserve comments`_.
218 .. _not preserve comments: http://bugs.python.org/issue1410680
223 >>> import sys, StringIO
224 >>> c = HookeConfigParser()
231 >>> c._read(StringIO.StringIO(instring), 'example.cfg')
232 >>> c.write(sys.stdout)
237 local_fp = fp == None
239 fp = open(os.path.expanduser(self._config_paths[-1]), 'w')
241 self._write_setting(fp, configparser.DEFAULTSECT,
242 help="Miscellaneous options")
243 for (key, value) in self._defaults.items():
244 self._write_setting(fp, configparser.DEFAULTSECT, key, value)
246 for section in self._sections:
247 self._write_setting(fp, section)
248 for (key, value) in self._sections[section].items():
249 if key != "__name__":
250 self._write_setting(fp, section, key, value)
255 class TestHookeConfigParser (unittest.TestCase):
256 def test_queue_safe(self):
257 """Ensure :class:`HookeConfigParser` is Queue-safe.
259 from multiprocessing import Queue
261 a = HookeConfigParser(
262 paths=DEFAULT_PATHS, default_settings=DEFAULT_SETTINGS)
265 for attr in ['_dict', '_defaults', '_sections', '_config_paths',
266 '_default_settings']:
267 a_value = getattr(a, attr)
268 b_value = getattr(b, attr)
271 'HookeConfigParser.%s did not survive Queue: %s != %s'
272 % (attr, a_value, b_value))