Mercurial > piecrust2
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 |
