diff 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
line wrap: on
line diff
--- a/piecrust/configuration.py	Wed Oct 15 21:18:27 2014 -0700
+++ b/piecrust/configuration.py	Wed Oct 15 23:01:05 2014 -0700
@@ -116,7 +116,7 @@
     m = header_regex.match(text)
     if m is not None:
         header = str(m.group('header'))
-        config = yaml.load(header, Loader=OrderedDictYAMLLoader)
+        config = yaml.load(header, Loader=ConfigurationLoader)
         offset = m.end()
     else:
         config = {}
@@ -124,16 +124,18 @@
     return config, offset
 
 
-class OrderedDictYAMLLoader(yaml.SafeLoader):
+class ConfigurationLoader(yaml.SafeLoader):
     """ A YAML loader that loads mappings into ordered dictionaries.
     """
     def __init__(self, *args, **kwargs):
-        super(OrderedDictYAMLLoader, self).__init__(*args, **kwargs)
+        super(ConfigurationLoader, self).__init__(*args, **kwargs)
 
-        self.add_constructor(u'tag:yaml.org,2002:map',
+        self.add_constructor('tag:yaml.org,2002:map',
                 type(self).construct_yaml_map)
-        self.add_constructor(u'tag:yaml.org,2002:omap',
+        self.add_constructor('tag:yaml.org,2002:omap',
                 type(self).construct_yaml_map)
+        self.add_constructor('tag:yaml.org,2002:sexagesimal',
+                type(self).construct_yaml_time)
 
     def construct_yaml_map(self, node):
         data = collections.OrderedDict()
@@ -156,3 +158,49 @@
             mapping[key] = value
         return mapping
 
+    time_regexp = re.compile(
+            r'''^(?P<hour>[0-9][0-9]?)
+                :(?P<minute>[0-9][0-9])
+                (:(?P<second>[0-9][0-9])
+                (\.(?P<fraction>[0-9]+))?)?$''', re.X)
+
+    def construct_yaml_time(self, node):
+        self.construct_scalar(node)
+        match = self.time_regexp.match(node.value)
+        values = match.groupdict()
+        hour = int(values['hour'])
+        minute = int(values['minute'])
+        second = 0
+        if values['second']:
+            second = int(values['second'])
+        usec = 0
+        if values['fraction']:
+            usec = float('0.' + values['fraction'])
+        return second + minute * 60 + hour * 60 * 60 + usec
+
+
+ConfigurationLoader.add_implicit_resolver(
+        'tag:yaml.org,2002:sexagesimal',
+        re.compile(r'''^[0-9][0-9]?:[0-9][0-9]
+                    (:[0-9][0-9](\.[0-9]+)?)?$''', re.X),
+        list('0123456789'))
+
+
+# We need to add our `sexagesimal` resolver before the `int` one, which
+# already supports sexagesimal notation in YAML 1.1 (but not 1.2). However,
+# because we know we pretty much always want it for representing time, we
+# need a simple `12:30` to mean 45000, not 750. So that's why we override
+# the default behaviour.
+for ch in list('0123456789'):
+    ch_resolvers = ConfigurationLoader.yaml_implicit_resolvers[ch]
+    ch_resolvers.insert(0, ch_resolvers.pop())
+
+
+class ConfigurationDumper(yaml.SafeDumper):
+    def represent_ordered_dict(self, data):
+        return self.represent_mapping('tag:yaml.org,2002:omap', data)
+
+
+ConfigurationDumper.add_representer(collections.OrderedDict,
+        ConfigurationDumper.represent_ordered_dict)
+