/hyde/ext/plugins/tagger.py

https://github.com/tomob/hyde · Python · 183 lines · 122 code · 23 blank · 38 comment · 29 complexity · 659296eec666d0c4305ec51f6b4c8208 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(str(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. try:
  92. taglist = attrgetter("meta.tags")(resource)
  93. except AttributeError:
  94. continue
  95. for tagname in taglist:
  96. if not tagname in tags:
  97. tag = Tag(tagname)
  98. tags[tagname] = tag
  99. tag.resources.append(resource)
  100. add_method(Node,
  101. 'walk_resources_tagged_with_%s' % tagname,
  102. walk_resources_tagged_with,
  103. tag=tag)
  104. else:
  105. tags[tagname].resources.append(resource)
  106. if not hasattr(resource, 'tags'):
  107. setattr(resource, 'tags', [])
  108. resource.tags.append(tags[tagname])
  109. try:
  110. tag_meta = self.site.config.tagger.tags.to_dict()
  111. except AttributeError:
  112. tag_meta = {}
  113. for tagname, meta in tag_meta.iteritems():
  114. # Don't allow name and resources in meta
  115. if 'resources' in meta:
  116. del(meta['resources'])
  117. if 'name' in meta:
  118. del(meta['name'])
  119. if tagname in tags:
  120. tags[tagname].update(meta)
  121. self.site.tagger = Expando(dict(tags=tags))
  122. def site_complete(self):
  123. """
  124. Generate archives.
  125. """
  126. content = self.site.content
  127. archive_config = None
  128. try:
  129. archive_config = attrgetter("tagger.archives")(self.site.config)
  130. except AttributeError:
  131. return
  132. self.logger.debug("Generating archives for tags")
  133. for name, config in archive_config.to_dict().iteritems():
  134. if not 'template' in config:
  135. raise self.template.exception_class(
  136. "No Template sepecified in tagger configuration.")
  137. source = content.node_from_relative_path(config.get('source', ''))
  138. target = self.site.config.deploy_root_path.child_folder(
  139. config.get('target', 'tags'))
  140. extension = config.get('extension', 'html')
  141. if not target.exists:
  142. target.make()
  143. template = config['template']
  144. text = "{%% extends \"%s\" %%}" % template
  145. for tagname, tag in self.site.tagger.tags.to_dict().iteritems():
  146. context = {}
  147. context.update(self.site.context)
  148. context.update(dict(
  149. time_now=datetime.now(),
  150. site=self.site,
  151. node=source,
  152. tag=tag,
  153. walker=getattr(source,
  154. "walk_resources_tagged_with_%s" % tagname)
  155. ))
  156. archive_text = self.template.render(text, context)
  157. archive_file = File(target.child("%s.%s" % (tagname, extension)
  158. if extension is not None else tagname))
  159. archive_file.write(archive_text)