/hyde/ext/plugins/tagger.py

https://github.com/colinsullivan/hyde · Python · 215 lines · 140 code · 25 blank · 50 comment · 28 complexity · 094cf91103c19ab992c7cf33a50aeff1 MD5 · raw file

  1. # -*- coding: utf-8 -*-
  2. """
  3. Contains classes and utilities related to tagging
  4. resources in hyde.
  5. """
  6. import re
  7. from hyde.fs import File, Folder
  8. from hyde.model import Expando
  9. from hyde.plugin import Plugin
  10. from hyde.site import Node, Resource
  11. from hyde.util import add_method, add_property, pairwalk
  12. from collections import namedtuple
  13. from datetime import datetime
  14. from functools import partial
  15. from itertools import ifilter, izip, tee, product
  16. from operator import attrgetter
  17. class Tag(Expando):
  18. """
  19. A simple object that represents a tag.
  20. """
  21. def __init__(self, name):
  22. """
  23. Initialize the tag with a name.
  24. """
  25. self.name = name
  26. self.resources = []
  27. def __repr__(self):
  28. return self.name
  29. def __str__(self):
  30. return self.name
  31. def get_tagger_sort_method(site):
  32. config = site.config
  33. content = site.content
  34. walker = 'walk_resources'
  35. sorter = None
  36. try:
  37. sorter = attrgetter('tagger.sorter')(config)
  38. walker = walker + '_sorted_by_%s' % sorter
  39. except AttributeError:
  40. pass
  41. try:
  42. walker = getattr(content, walker)
  43. except AttributeError:
  44. raise self.template.exception_class(
  45. "Cannot find the sorter: %s" % sorter)
  46. return walker
  47. def walk_resources_tagged_with(node, tag):
  48. tags = set(unicode(tag).split('+'))
  49. walker = get_tagger_sort_method(node.site)
  50. for resource in walker():
  51. try:
  52. taglist = set(attrgetter("meta.tags")(resource))
  53. except AttributeError:
  54. continue
  55. if tags <= taglist:
  56. yield resource
  57. class TaggerPlugin(Plugin):
  58. """
  59. Tagger plugin for hyde. Adds the ability to do tag resources and search
  60. based on the tags.
  61. Configuration example
  62. ---------------------
  63. #yaml
  64. sorter:
  65. kind:
  66. atts: source.kind
  67. tagger:
  68. sorter: kind # How to sort the resources in a tag
  69. archives:
  70. blog:
  71. template: tagged_posts.j2
  72. source: blog
  73. target: blog/tags
  74. archive_extension: html
  75. """
  76. def __init__(self, site):
  77. super(TaggerPlugin, self).__init__(site)
  78. def begin_site(self):
  79. """
  80. Initialize plugin. Add tag to the site context variable
  81. and methods for walking tagged resources.
  82. """
  83. self.logger.debug("Adding tags from metadata")
  84. config = self.site.config
  85. content = self.site.content
  86. tags = {}
  87. add_method(Node,
  88. 'walk_resources_tagged_with', walk_resources_tagged_with)
  89. walker = get_tagger_sort_method(self.site)
  90. for resource in walker():
  91. self._process_tags_in_resource(resource, tags)
  92. self._process_tag_metadata(tags)
  93. self.site.tagger = Expando(dict(tags=tags))
  94. self._generate_archives()
  95. def _process_tag_metadata(self, tags):
  96. """
  97. Parses and adds metadata to the tagger object, if the tagger
  98. configuration contains metadata.
  99. """
  100. try:
  101. tag_meta = self.site.config.tagger.tags.to_dict()
  102. except AttributeError:
  103. tag_meta = {}
  104. for tagname, meta in tag_meta.iteritems():
  105. # Don't allow name and resources in meta
  106. if 'resources' in meta:
  107. del(meta['resources'])
  108. if 'name' in meta:
  109. del(meta['name'])
  110. if tagname in tags:
  111. tags[tagname].update(meta)
  112. def _process_tags_in_resource(self, resource, tags):
  113. """
  114. Reads the tags associated with this resource and
  115. adds them to the tag list if needed.
  116. """
  117. try:
  118. taglist = attrgetter("meta.tags")(resource)
  119. except AttributeError:
  120. return
  121. for tagname in taglist:
  122. if not tagname in tags:
  123. tag = Tag(tagname)
  124. tags[tagname] = tag
  125. tag.resources.append(resource)
  126. add_method(Node,
  127. 'walk_resources_tagged_with_%s' % tagname,
  128. walk_resources_tagged_with,
  129. tag=tag)
  130. else:
  131. tags[tagname].resources.append(resource)
  132. if not hasattr(resource, 'tags'):
  133. setattr(resource, 'tags', [])
  134. resource.tags.append(tags[tagname])
  135. def _generate_archives(self):
  136. """
  137. Generates archives if the configuration demands.
  138. """
  139. archive_config = None
  140. try:
  141. archive_config = attrgetter("tagger.archives")(self.site.config)
  142. except AttributeError:
  143. return
  144. self.logger.debug("Generating archives for tags")
  145. for name, config in archive_config.to_dict().iteritems():
  146. self._create_tag_archive(config)
  147. def _create_tag_archive(self, config):
  148. """
  149. Generates archives for each tag based on the given configuration.
  150. """
  151. if not 'template' in config:
  152. raise self.template.exception_class(
  153. "No Template specified in tagger configuration.")
  154. content = self.site.content.source_folder
  155. source = Folder(config.get('source', ''))
  156. target = content.child_folder(config.get('target', 'tags'))
  157. if not target.exists:
  158. target.make()
  159. # Write meta data for the configuration
  160. meta = config.get('meta', {})
  161. meta_text = u''
  162. if meta:
  163. import yaml
  164. meta_text = yaml.dump(meta, default_flow_style=False)
  165. extension = config.get('extension', 'html')
  166. template = config['template']
  167. archive_text = u"""
  168. ---
  169. extends: false
  170. %(meta)s
  171. ---
  172. {%% set tag = site.tagger.tags['%(tag)s'] %%}
  173. {%% set source = site.content.node_from_relative_path('%(node)s') %%}
  174. {%% set walker = source['walk_resources_tagged_with_%(tag)s'] %%}
  175. {%% extends "%(template)s" %%}
  176. """
  177. for tagname, tag in self.site.tagger.tags.to_dict().iteritems():
  178. tag_data = {
  179. "tag": tagname,
  180. "node": source.name,
  181. "template": template,
  182. "meta": meta_text
  183. }
  184. text = archive_text % tag_data
  185. archive_file = File(target.child("%s.%s" % (tagname, extension)))
  186. archive_file.delete()
  187. archive_file.write(text.strip())
  188. self.site.content.add_resource(archive_file)