comparison piecrust/configuration.py @ 107:10fc9c8bf682

Better support for times in YAML interop. * Use our own sexagesimal parser/dumper for YAML to properly parse times. * Better name for the custom parser/dumper classes. * Add unit tests.
author Ludovic Chabant <ludovic@chabant.com>
date Wed, 15 Oct 2014 23:01:05 -0700
parents d64e4703f5e6
children b540d431f2da
comparison
equal deleted inserted replaced
106:5effaf1978d0 107:10fc9c8bf682
114 114
115 def parse_config_header(text): 115 def parse_config_header(text):
116 m = header_regex.match(text) 116 m = header_regex.match(text)
117 if m is not None: 117 if m is not None:
118 header = str(m.group('header')) 118 header = str(m.group('header'))
119 config = yaml.load(header, Loader=OrderedDictYAMLLoader) 119 config = yaml.load(header, Loader=ConfigurationLoader)
120 offset = m.end() 120 offset = m.end()
121 else: 121 else:
122 config = {} 122 config = {}
123 offset = 0 123 offset = 0
124 return config, offset 124 return config, offset
125 125
126 126
127 class OrderedDictYAMLLoader(yaml.SafeLoader): 127 class ConfigurationLoader(yaml.SafeLoader):
128 """ A YAML loader that loads mappings into ordered dictionaries. 128 """ A YAML loader that loads mappings into ordered dictionaries.
129 """ 129 """
130 def __init__(self, *args, **kwargs): 130 def __init__(self, *args, **kwargs):
131 super(OrderedDictYAMLLoader, self).__init__(*args, **kwargs) 131 super(ConfigurationLoader, self).__init__(*args, **kwargs)
132 132
133 self.add_constructor(u'tag:yaml.org,2002:map', 133 self.add_constructor('tag:yaml.org,2002:map',
134 type(self).construct_yaml_map) 134 type(self).construct_yaml_map)
135 self.add_constructor(u'tag:yaml.org,2002:omap', 135 self.add_constructor('tag:yaml.org,2002:omap',
136 type(self).construct_yaml_map) 136 type(self).construct_yaml_map)
137 self.add_constructor('tag:yaml.org,2002:sexagesimal',
138 type(self).construct_yaml_time)
137 139
138 def construct_yaml_map(self, node): 140 def construct_yaml_map(self, node):
139 data = collections.OrderedDict() 141 data = collections.OrderedDict()
140 yield data 142 yield data
141 value = self.construct_mapping(node) 143 value = self.construct_mapping(node)
154 "found unhashable key", key_node.start_mark) 156 "found unhashable key", key_node.start_mark)
155 value = self.construct_object(value_node, deep=deep) 157 value = self.construct_object(value_node, deep=deep)
156 mapping[key] = value 158 mapping[key] = value
157 return mapping 159 return mapping
158 160
161 time_regexp = re.compile(
162 r'''^(?P<hour>[0-9][0-9]?)
163 :(?P<minute>[0-9][0-9])
164 (:(?P<second>[0-9][0-9])
165 (\.(?P<fraction>[0-9]+))?)?$''', re.X)
166
167 def construct_yaml_time(self, node):
168 self.construct_scalar(node)
169 match = self.time_regexp.match(node.value)
170 values = match.groupdict()
171 hour = int(values['hour'])
172 minute = int(values['minute'])
173 second = 0
174 if values['second']:
175 second = int(values['second'])
176 usec = 0
177 if values['fraction']:
178 usec = float('0.' + values['fraction'])
179 return second + minute * 60 + hour * 60 * 60 + usec
180
181
182 ConfigurationLoader.add_implicit_resolver(
183 'tag:yaml.org,2002:sexagesimal',
184 re.compile(r'''^[0-9][0-9]?:[0-9][0-9]
185 (:[0-9][0-9](\.[0-9]+)?)?$''', re.X),
186 list('0123456789'))
187
188
189 # We need to add our `sexagesimal` resolver before the `int` one, which
190 # already supports sexagesimal notation in YAML 1.1 (but not 1.2). However,
191 # because we know we pretty much always want it for representing time, we
192 # need a simple `12:30` to mean 45000, not 750. So that's why we override
193 # the default behaviour.
194 for ch in list('0123456789'):
195 ch_resolvers = ConfigurationLoader.yaml_implicit_resolvers[ch]
196 ch_resolvers.insert(0, ch_resolvers.pop())
197
198
199 class ConfigurationDumper(yaml.SafeDumper):
200 def represent_ordered_dict(self, data):
201 return self.represent_mapping('tag:yaml.org,2002:omap', data)
202
203
204 ConfigurationDumper.add_representer(collections.OrderedDict,
205 ConfigurationDumper.represent_ordered_dict)
206