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
48 self.value = str(value)
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)
76 text = '# ' + '\n# '.join(text.splitlines())
77 fp.write(text.rstrip()+'\n')
79 fp.write("[%s]\n" % self.section)
81 fp.write("%s = %s\n" % (self.option,
82 str(value).replace('\n', '\n\t')))
85 Setting('conditions', help='Default environmental conditions in case they are not specified in the force curve data.'),
86 Setting('conditions', 'temperature', '301', help='Temperature in Kelvin'),
88 Setting('loggers', help='Configure loggers, see\nhttp://docs.python.org/library/logging.html#configuration-file-format', wrap=False),
89 Setting('loggers', 'keys', 'root, hooke', help='Hooke only uses the hooke logger, but other included modules may also use logging and you can configure their loggers here as well.'),
90 Setting('handlers', help='Configure log handlers, see\nhttp://docs.python.org/library/logging.html#configuration-file-format', wrap=False),
91 Setting('handlers', 'keys', 'hand1'),
92 Setting('formatters', help='Configure log formatters, see\nhttp://docs.python.org/library/logging.html#configuration-file-format', wrap=False),
93 Setting('formatters', 'keys', 'form1'),
94 Setting('logger_root', help='Configure the root logger, see\nhttp://docs.python.org/library/logging.html#configuration-file-format', wrap=False),
95 Setting('logger_root', 'level', 'NOTSET'),
96 Setting('logger_root', 'handlers', 'hand1'),
97 Setting('logger_hooke', help='Configure the hooke logger, see\nhttp://docs.python.org/library/logging.html#configuration-file-format', wrap=False),
98 Setting('logger_hooke', 'level', 'DEBUG'),
99 Setting('logger_hooke', 'handlers', 'hand1', help='No specific handlers here, just propagate up to the root logger'),
100 Setting('logger_hooke', 'propagate', '0'),
101 Setting('logger_hooke', 'qualname', 'hooke'),
102 Setting('handler_hand1', help='Configure the default log handler, see\nhttp://docs.python.org/library/logging.html#configuration-file-format', wrap=False),
103 Setting('handler_hand1', 'class', 'StreamHandler'),
104 Setting('handler_hand1', 'level', 'NOTSET'),
105 Setting('handler_hand1', 'formatter', 'form1'),
106 Setting('handler_hand1', 'args', '(sys.stderr,)'),
107 Setting('formatter_form1', help='Configure the default log formatter, see\nhttp://docs.python.org/library/logging.html#configuration-file-format', wrap=False),
108 Setting('formatter_form1', 'format', '%(asctime)s %(levelname)s %(message)s'),
109 Setting('formatter_form1', 'datefmt', '', help='Leave blank for ISO8601, e.g. "2003-01-23 00:29:50,411".'),
110 Setting('formatter_form1', 'class', 'logging.Formatter'),
113 def get_setting(settings, match):
114 """Return the first Setting object matching both match.section and
118 if s.section == match.section and s.option == match.option:
122 class HookeConfigParser (configparser.SafeConfigParser):
123 """A wrapper around configparser.SafeConfigParser.
125 You will probably only need .read and .write.
131 >>> c = HookeConfigParser(paths=DEFAULT_PATHS,
132 ... default_settings=DEFAULT_SETTINGS)
133 >>> c.write(sys.stdout) # doctest: +ELLIPSIS
134 # Default environmental conditions in case they are not specified in
135 # the force curve data.
137 # Temperature in Kelvin
140 # Configure loggers, see
141 # http://docs.python.org/library/logging.html#configuration-file-format
143 # Hooke only uses the hooke logger, but other included modules may
144 # also use logging and you can configure their loggers here as well.
148 def __init__(self, paths=None, default_settings=None, defaults=None,
149 dict_type=OrderedDict, indent='# ', **kwargs):
150 # Can't use super() because SafeConfigParser is a classic class
151 #super(HookeConfigParser, self).__init__(defaults, dict_type)
152 configparser.SafeConfigParser.__init__(self, defaults, dict_type)
155 self._config_paths = paths
156 if default_settings == None:
157 default_settings = []
158 self._default_settings = default_settings
159 for key in ['initial_indent', 'subsequent_indent']:
160 if key not in kwargs:
162 self._comment_textwrap = textwrap.TextWrapper(**kwargs)
163 self._setup_default_settings()
165 def _setup_default_settings(self):
166 for setting in self._default_settings: #reversed(self._default_settings):
167 # reversed cause: http://docs.python.org/library/configparser.html
168 # "When adding sections or items, add them in the reverse order of
169 # how you want them to be displayed in the actual file."
170 if setting.section not in self.sections():
171 self.add_section(setting.section)
172 if setting.option != None:
173 self.set(setting.section, setting.option, setting.value)
175 def read(self, filenames=None):
176 """Read and parse a filename or a list of filenames.
178 If filenames is None, it defaults to ._config_paths. If a
179 filename is not in ._config_paths, it gets appended to the
180 list. We also run os.path.expanduser() on the input filenames
181 internally so you don't have to worry about it.
183 Files that cannot be opened are silently ignored; this is
184 designed so that you can specify a list of potential
185 configuration file locations (e.g. current directory, user's
186 home directory, systemwide directory), and all existing
187 configuration files in the list will be read. A single
188 filename may also be given.
190 Return list of successfully read files.
192 if filenames == None:
193 filenames = [os.path.expanduser(p) for p in self._config_paths]
195 if isinstance(filenames, basestring):
196 filenames = [filenames]
197 paths = [os.path.abspath(os.path.expanduser(p))
198 for p in self._config_paths]
199 for filename in filenames:
200 if os.path.abspath(os.path.expanduser(filename)) not in paths:
201 self._config_paths.append(filename)
202 # Can't use super() because SafeConfigParser is a classic class
203 #return super(HookeConfigParser, self).read(filenames)
204 return configparser.SafeConfigParser.read(self, filenames)
206 def _write_setting(self, fp, section=None, option=None, value=None,
208 """Print, if known, a nicely wrapped comment form of a
211 match = get_setting(self._default_settings, Setting(section, option))
213 match = Setting(section, option, value, **kwargs)
214 match.write(fp, value=value, wrapper=self._comment_textwrap)
216 def write(self, fp=None, comments=True):
217 """Write an .ini-format representation of the configuration state.
219 This expands on RawConfigParser.write() by optionally adding
220 comments when they are known (i.e. for ._default_settings).
221 However, comments are not read in during a read, so .read(x)
222 .write(x) may `not preserve comments`_.
224 .. _not preserve comments: http://bugs.python.org/issue1410680
229 >>> import sys, StringIO
230 >>> c = HookeConfigParser()
237 >>> c._read(StringIO.StringIO(instring), 'example.cfg')
238 >>> c.write(sys.stdout)
243 local_fp = fp == None
245 fp = open(os.path.expanduser(self._config_paths[-1]), 'w')
247 self._write_setting(fp, configparser.DEFAULTSECT,
248 help="Miscellaneous options")
249 for (key, value) in self._defaults.items():
250 self._write_setting(fp, configparser.DEFAULTSECT, key, value)
252 for section in self._sections:
253 self._write_setting(fp, section)
254 for (key, value) in self._sections[section].items():
255 if key != "__name__":
256 self._write_setting(fp, section, key, value)
261 class TestHookeConfigParser (unittest.TestCase):
262 def test_queue_safe(self):
263 """Ensure :class:`HookeConfigParser` is Queue-safe.
265 from multiprocessing import Queue
267 a = HookeConfigParser(
268 paths=DEFAULT_PATHS, default_settings=DEFAULT_SETTINGS)
271 for attr in ['_dict', '_defaults', '_sections', '_config_paths',
272 '_default_settings']:
273 a_value = getattr(a, attr)
274 b_value = getattr(b, attr)
277 'HookeConfigParser.%s did not survive Queue: %s != %s'
278 % (attr, a_value, b_value))