pelican_comment_system.py 7.1 KB

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