pelican_comment_system.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. # -*- coding: utf-8 -*-
  2. """
  3. Pelican Comment System
  4. ======================
  5. A Pelican plugin, which allows you to add comments to your articles.
  6. Author: Bernhard Scheirle
  7. """
  8. from __future__ import unicode_literals
  9. import logging
  10. import os
  11. import copy
  12. logger = logging.getLogger(__name__)
  13. from itertools import chain
  14. from pelican import signals
  15. from pelican.readers import Readers
  16. from pelican.writers import Writer
  17. from . comment import Comment
  18. from . import avatars
  19. _all_comments = []
  20. _pelican_writer = None
  21. _pelican_obj = None
  22. def setdefault(pelican, settings):
  23. from pelican.settings import DEFAULT_CONFIG
  24. for key, value in settings:
  25. DEFAULT_CONFIG.setdefault(key, value)
  26. if not pelican:
  27. return
  28. for key, value in settings:
  29. pelican.settings.setdefault(key, value)
  30. def pelican_initialized(pelican):
  31. from pelican.settings import DEFAULT_CONFIG
  32. settings = [
  33. ('PELICAN_COMMENT_SYSTEM', False),
  34. ('PELICAN_COMMENT_SYSTEM_DIR', 'comments'),
  35. ('PELICAN_COMMENT_SYSTEM_IDENTICON_OUTPUT_PATH', 'images/identicon'),
  36. ('PELICAN_COMMENT_SYSTEM_IDENTICON_DATA', ()),
  37. ('PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE', 72),
  38. ('PELICAN_COMMENT_SYSTEM_AUTHORS', {}),
  39. ('PELICAN_COMMENT_SYSTEM_FEED', os.path.join('feeds', 'comment.%s.atom.xml')),
  40. ('PELICAN_COMMENT_SYSTEM_FEED_ALL', os.path.join('feeds', 'comments.all.atom.xml')),
  41. ('COMMENT_URL', '#comment-{slug}')
  42. ]
  43. setdefault(pelican, settings)
  44. DEFAULT_CONFIG['PAGE_EXCLUDES'].append(
  45. DEFAULT_CONFIG['PELICAN_COMMENT_SYSTEM_DIR'])
  46. DEFAULT_CONFIG['ARTICLE_EXCLUDES'].append(
  47. DEFAULT_CONFIG['PELICAN_COMMENT_SYSTEM_DIR'])
  48. pelican.settings['PAGE_EXCLUDES'].append(
  49. pelican.settings['PELICAN_COMMENT_SYSTEM_DIR'])
  50. pelican.settings['ARTICLE_EXCLUDES'].append(
  51. pelican.settings['PELICAN_COMMENT_SYSTEM_DIR'])
  52. global _pelican_obj
  53. _pelican_obj = pelican
  54. def initialize(article_generator):
  55. avatars.init(
  56. article_generator.settings['OUTPUT_PATH'],
  57. article_generator.settings[
  58. 'PELICAN_COMMENT_SYSTEM_IDENTICON_OUTPUT_PATH'],
  59. article_generator.settings['PELICAN_COMMENT_SYSTEM_IDENTICON_DATA'],
  60. article_generator.settings[
  61. 'PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE'] / 3,
  62. article_generator.settings['PELICAN_COMMENT_SYSTEM_AUTHORS'],
  63. )
  64. # Reset old states (autoreload mode)
  65. global _all_comments
  66. global _pelican_writer
  67. _pelican_writer = _pelican_obj.get_writer()
  68. _all_comments = []
  69. def warn_on_slug_collision(items):
  70. slugs = {}
  71. for comment in items:
  72. if not comment.slug in slugs:
  73. slugs[comment.slug] = [comment]
  74. else:
  75. slugs[comment.slug].append(comment)
  76. for slug, itemList in slugs.items():
  77. len_ = len(itemList)
  78. if len_ > 1:
  79. logger.warning('There are %s comments with the same slug: %s', len_, slug)
  80. for x in itemList:
  81. logger.warning(' %s', x.source_path)
  82. def write_feed_all(gen, writer):
  83. if gen.settings['PELICAN_COMMENT_SYSTEM'] is not True:
  84. return
  85. if gen.settings['PELICAN_COMMENT_SYSTEM_FEED_ALL'] is None:
  86. return
  87. context = copy.copy(gen.context)
  88. context['SITENAME'] += " - All Comments"
  89. context['SITESUBTITLE'] = ""
  90. path = gen.settings['PELICAN_COMMENT_SYSTEM_FEED_ALL']
  91. global _all_comments
  92. _all_comments = sorted(_all_comments)
  93. _all_comments.reverse()
  94. for com in _all_comments:
  95. com.title = com.article.title + " - " + com.title
  96. com.override_url = com.article.url + com.url
  97. writer.write_feed(_all_comments, context, path)
  98. def write_feed(gen, items, context, slug):
  99. if gen.settings['PELICAN_COMMENT_SYSTEM_FEED'] is None:
  100. return
  101. path = gen.settings['PELICAN_COMMENT_SYSTEM_FEED'] % slug
  102. _pelican_writer.write_feed(items, context, path)
  103. def process_comments(article_generator):
  104. for article in article_generator.articles:
  105. add_static_comments(article_generator, article)
  106. def mirror_to_translations(article):
  107. for translation in article.translations:
  108. translation.comments_count = article.comments_count
  109. translation.comments = article.comments
  110. def add_static_comments(gen, content):
  111. if gen.settings['PELICAN_COMMENT_SYSTEM'] is not True:
  112. return
  113. global _all_comments
  114. content.comments_count = 0
  115. content.comments = []
  116. mirror_to_translations(content)
  117. # Modify the local context, so we get proper values for the feed
  118. context = copy.copy(gen.context)
  119. context['SITEURL'] += "/" + content.url
  120. context['SITENAME'] += " - Comments: " + content.title
  121. context['SITESUBTITLE'] = ""
  122. folder = os.path.join(
  123. gen.settings['PATH'],
  124. gen.settings['PELICAN_COMMENT_SYSTEM_DIR'],
  125. content.slug
  126. )
  127. if not os.path.isdir(folder):
  128. logger.debug("No comments found for: %s", content.slug)
  129. write_feed(gen, [], context, content.slug)
  130. return
  131. reader = Readers(gen.settings)
  132. comments = []
  133. replies = []
  134. for file in os.listdir(folder):
  135. name, extension = os.path.splitext(file)
  136. if extension[1:].lower() in reader.extensions:
  137. com = reader.read_file(
  138. base_path=folder, path=file,
  139. content_class=Comment, context=context)
  140. com.article = content
  141. _all_comments.append(com)
  142. if hasattr(com, 'replyto'):
  143. replies.append(com)
  144. else:
  145. comments.append(com)
  146. feed_items = sorted(comments + replies)
  147. feed_items.reverse()
  148. warn_on_slug_collision(feed_items)
  149. write_feed(gen, feed_items, context, content.slug)
  150. # TODO: Fix this O(n²) loop
  151. for reply in replies:
  152. found_parent = False
  153. for comment in chain(comments, replies):
  154. if comment.slug == reply.replyto:
  155. comment.addReply(reply)
  156. found_parent = True
  157. break
  158. if not found_parent:
  159. logger.warning('Comment "%s/%s" is a reply to non-existent comment "%s". '
  160. 'Make sure the replyto attribute is set correctly.',
  161. content.slug, reply.slug, reply.replyto)
  162. count = 0
  163. for comment in comments:
  164. comment.sortReplies()
  165. count += comment.countReplies()
  166. comments = sorted(comments)
  167. content.comments_count = len(comments) + count
  168. content.comments = comments
  169. mirror_to_translations(content)
  170. def writeIdenticonsToDisk(gen, writer):
  171. avatars.generateAndSaveMissingAvatars()
  172. def pelican_finalized(pelican):
  173. if pelican.settings['PELICAN_COMMENT_SYSTEM'] is not True:
  174. return
  175. global _all_comments
  176. print('Processed %s comment(s)' % len(_all_comments))
  177. def register():
  178. signals.initialized.connect(pelican_initialized)
  179. signals.article_generator_init.connect(initialize)
  180. signals.article_generator_finalized.connect(process_comments)
  181. signals.article_writer_finalized.connect(writeIdenticonsToDisk)
  182. signals.article_writer_finalized.connect(write_feed_all)
  183. signals.finalized.connect(pelican_finalized)