A fork of hyde, the static site generation. Some patches will be pushed upstream.
 
 
 

195 lines
6.1 KiB

  1. # -*- coding: utf-8 -*-
  2. """
  3. Contains classes and utilities related to meta data in hyde.
  4. """
  5. import re
  6. from hyde.model import Expando
  7. from hyde.plugin import Plugin
  8. import yaml
  9. class Metadata(Expando):
  10. """
  11. Container class for yaml meta data.
  12. """
  13. def __init__(self, data, parent=None):
  14. super(Metadata, self).__init__({})
  15. if parent:
  16. self.update(parent.__dict__)
  17. if data:
  18. self.update(data)
  19. def update(self, data):
  20. """
  21. Updates the metadata with new stuff
  22. """
  23. if isinstance(data, basestring):
  24. super(Metadata, self).update(yaml.load(data))
  25. else:
  26. super(Metadata, self).update(data)
  27. class MetaPlugin(Plugin):
  28. """
  29. Metadata plugin for hyde. Loads meta data in the following order:
  30. 1. meta.yaml: files in any folder
  31. 2. frontmatter: any text file with content enclosed within three dashes
  32. or three equals signs.
  33. Example:
  34. ---
  35. abc: def
  36. ---
  37. Supports YAML syntax.
  38. """
  39. def __init__(self, site):
  40. super(MetaPlugin, self).__init__(site)
  41. self.yaml_finder = re.compile(
  42. r"^\s*(?:---|===)\s*\n((?:.|\n)+?)\n\s*(?:---|===)\s*\n*",
  43. re.MULTILINE)
  44. def begin_site(self):
  45. """
  46. Initialize site meta data.
  47. Go through all the nodes and resources to initialize
  48. meta data at each level.
  49. """
  50. config = self.site.config
  51. metadata = config.meta if hasattr(config, 'meta') else {}
  52. self.site.meta = Metadata(metadata)
  53. self.nodemeta = 'nodemeta.yaml'
  54. if hasattr(self.site.meta, 'nodemeta'):
  55. self.nodemeta = self.site.meta.nodemeta
  56. for node in self.site.content.walk():
  57. self.__read_node__(node)
  58. for resource in node.resources:
  59. if not hasattr(resource, 'meta'):
  60. resource.meta = Metadata({}, node.meta)
  61. if resource.source_file.is_text and not resource.simple_copy:
  62. self.__read_resource__(resource, resource.source_file.read_all())
  63. def __read_resource__(self, resource, text):
  64. """
  65. Reads the resource metadata and assigns it to
  66. the resource. Load meta data by looking for the marker.
  67. Once loaded, remove the meta area from the text.
  68. """
  69. self.logger.debug("Trying to load metadata from resource [%s]" % resource)
  70. match = re.match(self.yaml_finder, text)
  71. if not match:
  72. self.logger.debug("No metadata found in resource [%s]" % resource)
  73. data = {}
  74. else:
  75. text = text[match.end():]
  76. data = match.group(1)
  77. if not hasattr(resource, 'meta') or not resource.meta:
  78. if not hasattr(resource.node, 'meta'):
  79. resource.node.meta = Metadata({})
  80. resource.meta = Metadata(data, resource.node.meta)
  81. else:
  82. resource.meta.update(data)
  83. self.__update_standard_attributes__(resource)
  84. self.logger.debug("Successfully loaded metadata from resource [%s]"
  85. % resource)
  86. return text or ' '
  87. def __update_standard_attributes__(self, obj):
  88. """
  89. Updates standard attributes on the resource and
  90. page based on the provided meta data.
  91. """
  92. if not hasattr(obj, 'meta'):
  93. return
  94. standard_attributes = ['is_processable', 'uses_template']
  95. for attr in standard_attributes:
  96. if hasattr(obj.meta, attr):
  97. setattr(obj, attr, getattr(obj.meta, attr))
  98. def __read_node__(self, node):
  99. """
  100. Look for nodemeta.yaml (or configured name). Load and assign it
  101. to the node.
  102. """
  103. nodemeta = node.get_resource(self.nodemeta)
  104. parent_meta = node.parent.meta if node.parent else self.site.meta
  105. if nodemeta:
  106. nodemeta.is_processable = False
  107. metadata = nodemeta.source_file.read_all()
  108. if hasattr(node, 'meta') and node.meta:
  109. node.meta.update(metadata)
  110. else:
  111. node.meta = Metadata(metadata, parent=parent_meta)
  112. else:
  113. node.meta = Metadata({}, parent=parent_meta)
  114. self.__update_standard_attributes__(node)
  115. def begin_node(self, node):
  116. """
  117. Read node meta data.
  118. """
  119. self.__read_node__(node)
  120. def begin_text_resource(self, resource, text):
  121. """
  122. Update the meta data again, just in case it
  123. has changed. Return text without meta data.
  124. """
  125. return self.__read_resource__(resource, text)
  126. class AutoExtendPlugin(Plugin):
  127. """
  128. The plugin class for extending templates using metadata.
  129. """
  130. def __init__(self, site):
  131. super(AutoExtendPlugin, self).__init__(site)
  132. def begin_text_resource(self, resource, text):
  133. """
  134. If the meta data for the resource contains a layout attribute,
  135. and there is no extends statement, this plugin automatically adds
  136. an extends statement to the top of the file.
  137. """
  138. if not resource.uses_template:
  139. return text
  140. layout = None
  141. block = None
  142. try:
  143. layout = resource.meta.extends
  144. except AttributeError:
  145. pass
  146. try:
  147. block = resource.meta.default_block
  148. except AttributeError:
  149. pass
  150. if layout:
  151. self.logger.debug("Autoextending %s with %s" % (
  152. resource.relative_path, layout))
  153. extends_pattern = self.template.patterns['extends']
  154. if not re.search(extends_pattern, text):
  155. extended_text = self.template.get_extends_statement(layout)
  156. extended_text += '\n'
  157. if block:
  158. extended_text += ('%s\n%s\n%s' %
  159. (self.t_block_open_tag(block),
  160. text,
  161. self.t_block_close_tag(block)))
  162. else:
  163. extended_text += text
  164. return extended_text
  165. return text