Browse Source

Merge pull request #236 from clokep/rst-comments

pelican_comment_system: Allow restructedtext comments.
Alexis Metaireau 10 years ago
parent
commit
62e437025c

+ 1 - 1
pelican_comment_system/Readme.md

@@ -1,6 +1,6 @@
 # Pelican comment system
 # Pelican comment system
 The pelican comment system allows you to add static comments to your articles.
 The pelican comment system allows you to add static comments to your articles.
-The comments are stored in Markdown files. Each comment in it own file.
+The comments are stored in files which can be processed by Pelican (e.g.: Markdown, reStructuredText, ...). Each comment in its own file.
 
 
 #### Features
 #### Features
  - Static comments for each article
  - Static comments for each article

+ 22 - 6
pelican_comment_system/comment.py

@@ -3,29 +3,45 @@
 Author: Bernhard Scheirle
 Author: Bernhard Scheirle
 """
 """
 from __future__ import unicode_literals
 from __future__ import unicode_literals
+import os
+
 from pelican import contents
 from pelican import contents
 from pelican.contents import Content
 from pelican.contents import Content
+from pelican.utils import slugify
+
+from . import avatars
 
 
 class Comment(Content):
 class Comment(Content):
 	mandatory_properties = ('author', 'date')
 	mandatory_properties = ('author', 'date')
 	default_template = 'None'
 	default_template = 'None'
 
 
-	def __init__(self, id, avatar, content, metadata, settings, source_path, context):
+	def __init__(self, content, metadata, settings, source_path, context):
+		# Strip the path off the full filename.
+		name = os.path.split(source_path)[1]
+
+		if not hasattr(self, 'slug'):
+			#compute the slug before initializing the base Content object, so it doesn't get set there
+			#This is required because we need a slug containing the file extension.
+			self.slug = slugify( name, settings.get('SLUG_SUBSTITUTIONS', ()))
+
 		super(Comment,self).__init__( content, metadata, settings, source_path, context )
 		super(Comment,self).__init__( content, metadata, settings, source_path, context )
-		self.id = id
+
 		self.replies = []
 		self.replies = []
-		self.avatar = avatar
+
+		# Strip the extension from the filename.
+		name = os.path.splitext(name)[0]
+		self.avatar = avatars.getAvatarPath(name, metadata)
 		self.title = "Posted by:  {}".format(metadata['author'])
 		self.title = "Posted by:  {}".format(metadata['author'])
 
 
 	def addReply(self, comment):
 	def addReply(self, comment):
 		self.replies.append(comment)
 		self.replies.append(comment)
 
 
-	def getReply(self, id):
+	def getReply(self, slug):
 		for reply in self.replies:
 		for reply in self.replies:
-			if reply.id == id:
+			if reply.slug == slug:
 				return reply
 				return reply
 			else:
 			else:
-				deepReply = reply.getReply(id)
+				deepReply = reply.getReply( slug )
 				if deepReply != None:
 				if deepReply != None:
 					return deepReply
 					return deepReply
 		return None
 		return None

+ 2 - 2
pelican_comment_system/doc/feed.md

@@ -4,14 +4,14 @@ Be sure that the id of the html tag containing the comment matches `COMMENT_URL`
 
 
 ##### pelicanconf.py
 ##### pelicanconf.py
 ```python
 ```python
-COMMENT_URL = "#my_own_comment_id_{path}"
+COMMENT_URL = "#my_own_comment_id_{slug}"
 ```
 ```
 
 
 ##### Theme
 ##### Theme
 ```html
 ```html
 {% for comment in article.comments recursive %}
 {% for comment in article.comments recursive %}
 	...
 	...
-	<article id="my_own_comment_id_{{comment.id}}">{{ comment.content }}</article>
+	<article id="my_own_comment_id_{{comment.slug}}">{{ comment.content }}</article>
 	...
 	...
 {% endfor %}
 {% endfor %}
 ```
 ```

+ 1 - 1
pelican_comment_system/doc/form.md

@@ -8,7 +8,7 @@ The resulting email contains a valid markdown block. Now you only have to copy t
 Add this in the "comment for loop" in your article theme, so your visitors can reply to a comment.
 Add this in the "comment for loop" in your article theme, so your visitors can reply to a comment.
 
 
 ```html
 ```html
-<button onclick="reply('{{comment.id | urlencode}}');">Reply</button>
+<button onclick="reply('{{comment.slug | urlencode}}');">Reply</button>
 ```
 ```
 
 
 #### Form
 #### Form

+ 9 - 9
pelican_comment_system/doc/installation.md

@@ -1,7 +1,7 @@
 # Installation
 # Installation
 Activate the plugin by adding it to your `pelicanconf.py`
 Activate the plugin by adding it to your `pelicanconf.py`
 
 
-	PLUGIN_PATH = '/path/to/pelican-plugins'
+	PLUGIN_PATH = ['/path/to/pelican-plugins']
 	PLUGINS = ['pelican_comment_system']
 	PLUGINS = ['pelican_comment_system']
 	PELICAN_COMMENT_SYSTEM = True
 	PELICAN_COMMENT_SYSTEM = True
 
 
@@ -11,13 +11,13 @@ And modify your `article.html` theme (see below).
 Name                                           | Type      | Default                    | Description
 Name                                           | Type      | Default                    | Description
 -----------------------------------------------|-----------|----------------------------|-------
 -----------------------------------------------|-----------|----------------------------|-------
 `PELICAN_COMMENT_SYSTEM`                       | `boolean` | `False`                    | Activates or deactivates the comment system
 `PELICAN_COMMENT_SYSTEM`                       | `boolean` | `False`                    | Activates or deactivates the comment system
-`PELICAN_COMMENT_SYSTEM_DIR`                   | `string`  | `comments`                 | Folder where the comments are stored
+`PELICAN_COMMENT_SYSTEM_DIR`                   | `string`  | `comments`                 | Folder where the comments are stored, relative to `PATH`
 `PELICAN_COMMENT_SYSTEM_IDENTICON_OUTPUT_PATH` | `string`  | `images/identicon`         | Relative URL to the output folder where the identicons are stored
 `PELICAN_COMMENT_SYSTEM_IDENTICON_OUTPUT_PATH` | `string`  | `images/identicon`         | Relative URL to the output folder where the identicons are stored
 `PELICAN_COMMENT_SYSTEM_IDENTICON_DATA`        | `tuple`   | `()`                       | Contains all Metadata tags, which in combination identifies a comment author (like `('author', 'email')`)
 `PELICAN_COMMENT_SYSTEM_IDENTICON_DATA`        | `tuple`   | `()`                       | Contains all Metadata tags, which in combination identifies a comment author (like `('author', 'email')`)
 `PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE`        | `int`     | `72`                       | Width and height of the identicons. Has to be a multiple of 3.
 `PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE`        | `int`     | `72`                       | Width and height of the identicons. Has to be a multiple of 3.
 `PELICAN_COMMENT_SYSTEM_AUTHORS`               | `dict`    | `{}`                       | Comment authors, which should have a specific avatar. More info [here](avatars.md)
 `PELICAN_COMMENT_SYSTEM_AUTHORS`               | `dict`    | `{}`                       | Comment authors, which should have a specific avatar. More info [here](avatars.md)
 `PELICAN_COMMENT_SYSTEM_FEED`                  | `string`  |`feeds/comment.%s.atom.xml` | Relative URL to output the Atom feed for each article.`%s` gets replaced with the slug of the article. More info [here](http://docs.getpelican.com/en/latest/settings.html#feed-settings)
 `PELICAN_COMMENT_SYSTEM_FEED`                  | `string`  |`feeds/comment.%s.atom.xml` | Relative URL to output the Atom feed for each article.`%s` gets replaced with the slug of the article. More info [here](http://docs.getpelican.com/en/latest/settings.html#feed-settings)
-`COMMENT_URL`                                  | `string`  | `#comment-{path}`          | `{path}` gets replaced with the id of the comment. More info [here](feed.md)
+`COMMENT_URL`                                  | `string`  | `#comment-{slug}`          | `{slug}` gets replaced with the slug of the comment. More info [here](feed.md)
 
 
 ## Folder structure
 ## Folder structure
 Every comment file has to be stored in a sub folder of `PELICAN_COMMENT_SYSTEM_DIR`.
 Every comment file has to be stored in a sub folder of `PELICAN_COMMENT_SYSTEM_DIR`.
@@ -46,7 +46,8 @@ Tag           | Required  | Description
 --------------|-----------|----------------
 --------------|-----------|----------------
 `date`        | yes       | Date when the comment was posted
 `date`        | yes       | Date when the comment was posted
 `author`      | yes       | Name of the comment author
 `author`      | yes       | Name of the comment author
-`replyto`     | no        | Identifier of the parent comment. Identifier = Filename (**with** extension)
+`slug`        | no        | Slug of the comment. If not present it will be computed from the file name (including the extension)
+`replyto`     | no        | Slug of the parent comment
 
 
 Every other (custom) tag gets parsed as well and will be available through the theme.
 Every other (custom) tag gets parsed as well and will be available through the theme.
 
 
@@ -55,7 +56,7 @@ Every other (custom) tag gets parsed as well and will be available through the t
 	date: 2014-3-21 15:02
 	date: 2014-3-21 15:02
 	author: Author of the comment
 	author: Author of the comment
 	website: http://authors.website.com
 	website: http://authors.website.com
-	replyto: 7
+	replyto: 1md
 	anothermetatag: some random tag
 	anothermetatag: some random tag
 
 
 	Content of the comment.
 	Content of the comment.
@@ -69,13 +70,12 @@ Variables                | Description
 `article.comments`       | Array containing the top level comments for this article (no replies to comments)
 `article.comments`       | Array containing the top level comments for this article (no replies to comments)
 
 
 ### Comment object
 ### Comment object
-The comment object is a [content](https://github.com/getpelican/pelican/blob/master/pelican/contents.py#L34) object, so all common attributes are available (like author, content, date, local_date, metadata, ...).
+The comment object is a [content](https://github.com/getpelican/pelican/blob/master/pelican/contents.py#L34) object, so all common attributes are available (like author, content, date, local_date, slug, metadata, ...).
 
 
 Additional following attributes are added:
 Additional following attributes are added:
 
 
 Attribute  | Description
 Attribute  | Description
 -----------|--------------------------
 -----------|--------------------------
-`id`       | Identifier of this comment
 `replies`  | Array containing the top level replies for this comment
 `replies`  | Array containing the top level replies for this comment
 `avatar`   | Path to the avatar or identicon of the comment author
 `avatar`   | Path to the avatar or identicon of the comment author
 
 
@@ -89,8 +89,8 @@ Attribute  | Description
 		{% else %}
 		{% else %}
 			{% set marginLeft = 50 %}
 			{% set marginLeft = 50 %}
 		{% endif %}
 		{% endif %}
-			<article id="comment-{{comment.id}}" style="border: 1px solid #DDDDDD; padding: 5px 0px 0px 5px; margin: 0px -1px 5px {{marginLeft}}px;">
-				<a href="{{ SITEURL }}/{{ article.url }}#comment-{{comment.id}}" rel="bookmark" title="Permalink to this comment">Permalink</a>
+			<article id="comment-{{comment.slug}}" style="border: 1px solid #DDDDDD; padding: 5px 0px 0px 5px; margin: 0px -1px 5px {{marginLeft}}px;">
+				<a href="{{ SITEURL }}/{{ article.url }}#comment-{{comment.slug}}" rel="bookmark" title="Permalink to this comment">Permalink</a>
 				<h4>{{ comment.author }}</h4>
 				<h4>{{ comment.author }}</h4>
 				<p>Posted on <abbr class="published" title="{{ comment.date.isoformat() }}">{{ comment.locale_date }}</abbr></p>
 				<p>Posted on <abbr class="published" title="{{ comment.date.isoformat() }}">{{ comment.locale_date }}</abbr></p>
 				{{ comment.metadata['my_custom_metadata'] }}
 				{{ comment.metadata['my_custom_metadata'] }}

+ 34 - 14
pelican_comment_system/pelican_comment_system.py

@@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
 
 
 from itertools import chain
 from itertools import chain
 from pelican import signals
 from pelican import signals
-from pelican.readers import MarkdownReader
+from pelican.readers import Readers
 from pelican.writers import Writer
 from pelican.writers import Writer
 
 
 from . comment import Comment
 from . comment import Comment
@@ -26,13 +26,15 @@ from . import avatars
 def pelican_initialized(pelican):
 def pelican_initialized(pelican):
 	from pelican.settings import DEFAULT_CONFIG
 	from pelican.settings import DEFAULT_CONFIG
 	DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM', False)
 	DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM', False)
-	DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_DIR' 'comments')
+	DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_DIR', 'comments')
 	DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_OUTPUT_PATH' 'images/identicon')
 	DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_OUTPUT_PATH' 'images/identicon')
 	DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_DATA', ())
 	DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_DATA', ())
 	DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE', 72)
 	DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE', 72)
 	DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_AUTHORS', {})
 	DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_AUTHORS', {})
 	DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_FEED', os.path.join('feeds', 'comment.%s.atom.xml'))
 	DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_FEED', os.path.join('feeds', 'comment.%s.atom.xml'))
-	DEFAULT_CONFIG.setdefault('COMMENT_URL', '#comment-{path}')
+	DEFAULT_CONFIG.setdefault('COMMENT_URL', '#comment-{slug}')
+	DEFAULT_CONFIG['PAGE_EXCLUDES'].append(DEFAULT_CONFIG['PELICAN_COMMENT_SYSTEM_DIR'])
+	DEFAULT_CONFIG['ARTICLE_EXCLUDES'].append(DEFAULT_CONFIG['PELICAN_COMMENT_SYSTEM_DIR'])
 	if pelican:
 	if pelican:
 		pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM', False)
 		pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM', False)
 		pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_DIR', 'comments')
 		pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_DIR', 'comments')
@@ -41,8 +43,10 @@ def pelican_initialized(pelican):
 		pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE', 72)
 		pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE', 72)
 		pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_AUTHORS', {})
 		pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_AUTHORS', {})
 		pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_FEED', os.path.join('feeds', 'comment.%s.atom.xml'))
 		pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_FEED', os.path.join('feeds', 'comment.%s.atom.xml'))
-		pelican.settings.setdefault('COMMENT_URL', '#comment-{path}')
+		pelican.settings.setdefault('COMMENT_URL', '#comment-{slug}')
 
 
+		pelican.settings['PAGE_EXCLUDES'].append(pelican.settings['PELICAN_COMMENT_SYSTEM_DIR'])
+		pelican.settings['ARTICLE_EXCLUDES'].append(pelican.settings['PELICAN_COMMENT_SYSTEM_DIR'])
 
 
 def initialize(article_generator):
 def initialize(article_generator):
 	avatars.init(
 	avatars.init(
@@ -53,6 +57,22 @@ def initialize(article_generator):
 		article_generator.settings['PELICAN_COMMENT_SYSTEM_AUTHORS'],
 		article_generator.settings['PELICAN_COMMENT_SYSTEM_AUTHORS'],
 		)
 		)
 
 
+def warn_on_slug_collision( items ):
+	slugs = {}
+	for comment in items:
+		if not comment.slug in slugs:
+			slugs[ comment.slug ] = [ comment ]
+		else:
+			slugs[ comment.slug ].append( comment )
+
+	for slug, itemList in slugs.items():
+		len_ = len( itemList )
+		if len_ > 1:
+			logger.warning('There are %s comments with the same slug: %s'% (len_, slug))
+			for x in itemList:
+				logger.warning('    %s' % x.source_path)
+
+
 def write_feed(gen, items, context, slug):
 def write_feed(gen, items, context, slug):
 	if gen.settings['PELICAN_COMMENT_SYSTEM_FEED'] == None:
 	if gen.settings['PELICAN_COMMENT_SYSTEM_FEED'] == None:
 		return
 		return
@@ -75,37 +95,37 @@ def add_static_comments(gen, content):
 	context['SITENAME'] += " - Comments: " + content.title
 	context['SITENAME'] += " - Comments: " + content.title
 	context['SITESUBTITLE'] = ""
 	context['SITESUBTITLE'] = ""
 
 
-	folder = os.path.join(gen.settings['PELICAN_COMMENT_SYSTEM_DIR'], content.slug)
+	folder = os.path.join(gen.settings['PATH'], gen.settings['PELICAN_COMMENT_SYSTEM_DIR'], content.slug)
 
 
 	if not os.path.isdir(folder):
 	if not os.path.isdir(folder):
 		logger.debug("No comments found for: " + content.slug)
 		logger.debug("No comments found for: " + content.slug)
 		write_feed(gen, [], context, content.slug)
 		write_feed(gen, [], context, content.slug)
 		return
 		return
 
 
-	reader = MarkdownReader(gen.settings)
+	reader = Readers(gen.settings)
 	comments = []
 	comments = []
 	replies = []
 	replies = []
 
 
 	for file in os.listdir(folder):
 	for file in os.listdir(folder):
 		name, extension = os.path.splitext(file)
 		name, extension = os.path.splitext(file)
-		if extension[1:].lower() in reader.file_extensions:
-			com_content, meta = reader.read(os.path.join(folder, file))
-			
-			avatar_path = avatars.getAvatarPath(name, meta)
+		if extension[1:].lower() in reader.extensions:
+			com = reader.read_file(
+				base_path=folder, path=file,
+				content_class=Comment, context=context)
 
 
-			com = Comment(file, avatar_path, com_content, meta, gen.settings, file, context)
-
-			if 'replyto' in meta:
+			if hasattr(com, 'replyto'):
 				replies.append( com )
 				replies.append( com )
 			else:
 			else:
 				comments.append( com )
 				comments.append( com )
 
 
+	warn_on_slug_collision( comments + replies )
+
 	write_feed(gen, comments + replies, context, content.slug)
 	write_feed(gen, comments + replies, context, content.slug)
 
 
 	#TODO: Fix this O(n²) loop
 	#TODO: Fix this O(n²) loop
 	for reply in replies:
 	for reply in replies:
 		for comment in chain(comments, replies):
 		for comment in chain(comments, replies):
-			if comment.id == reply.metadata['replyto']:
+			if comment.slug == reply.replyto:
 				comment.addReply(reply)
 				comment.addReply(reply)
 
 
 	count = 0
 	count = 0