[pelican_comment_system] Added Avatars and Identicons

This commit is contained in:
Bernhard Scheirle
2014-04-10 08:58:19 +02:00
parent 1a20cc84b2
commit 98e2f16059
10 changed files with 653 additions and 190 deletions

View File

@@ -1,192 +1,29 @@
# Pelican comment system # Pelican comment system
The pelican comment system allows you to add static comments to your articles. It also supports replies to comments. 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 Markdown files. Each comment in it own file.
See it in action here: [blog.scheirle.de](http://blog.scheirle.de/posts/2014/March/29/static-comments-via-email/) #### Features
- Static comments for each article
- Replies to comments
- Avatars and [Identicons](https://en.wikipedia.org/wiki/Identicon)
- Easy styleable via the themes
Thanks to jesrui the author of [Static comments](https://github.com/getpelican/pelican-plugins/tree/master/static_comments). I reused some code from it.
See it in action here: [blog.scheirle.de](http://blog.scheirle.de/posts/2014/March/29/static-comments-via-email/)
Author | Website | Github Author | Website | Github
-------------------|---------------------------|------------------------------ -------------------|---------------------------|------------------------------
Bernhard Scheirle | <http://blog.scheirle.de> | <https://github.com/Scheirle> Bernhard Scheirle | <http://blog.scheirle.de> | <https://github.com/Scheirle>
## Installation ## Instructions
Activate the plugin by adding it to your `pelicanconf.py` - [Installation and basic usage](doc/installation.md)
- [Avatars and Identicons](doc/avatars.md)
- [Comment Form (aka: never gather Metadata)](doc/form.md)
## Requirements
To create identicons the Python Image Library is needed. Therefore you either need PIL **or** Pillow (recommended).
PLUGIN_PATH = '/path/to/pelican-plugins' ##### Install Pillow
PLUGINS = ['pelican_comment_system'] easy_install Pillow
PELICAN_COMMENT_SYSTEM = True
If you don't use avatars or identicons this plugin works fine without PIL/Pillow. You will however get a warning that identicons are deactivated (as expected).
And modify your `article.html` theme (see below).
## Settings
Name | Type | Default | Description
-----------------------------|-----------|------------|-------
`PELICAN_COMMENT_SYSTEM` | `boolean` | `False` | Activates or deactivates the comment system
`PELICAN_COMMENT_SYSTEM_DIR` | `string` | `comments` | Folder where the comments are stored
### Folder structure
Every comment file has to be stored in a sub folder of `PELICAN_COMMENT_SYSTEM_DIR`.
Sub folders are named after the `slug` of the articles.
So the comments to your `foo-bar` article are stored in `comments/foo-bar/`
The filenames of the comment files are up to you. But the filename is the Identifier of the comment (without extension).
##### Example folder structure
.
└── comments
└── foo-bar
│ ├── 1.md
│ └── 0.md
└── some-other-slug
├── random-Name.md
├── 1.md
└── 0.md
### Comment file
##### Meta information
Tag | Required | Description
--------------|-----------|----------------
`date` | yes | Date when the comment was posted
`replyto` | no | Identifier of the parent comment. Identifier = Filename (without extension)
`locale_date` | forbidden | Will be overwritten with a locale representation of the date
Every other (custom) tag gets parsed as well and will be available through the theme.
##### Example of a comment file
date: 2014-3-21 15:02
author: Author of the comment
website: http://authors.website.com
replyto: 7
anothermetatag: some random tag
Content of the comment.
### Theme
In the `article.html` theme file are now two more variables available.
Variables | Description
----------------------------------|--------------------------
`article.metadata.comments_count` | Amount of total comments for this article (including replies to comments)
`article.metadata.comments` | Array containing the top level comments for this article (no replies to comments)
#### Comment object
Variables | Description
-----------|--------------------------
`id` | Identifier of this comment
`content` | Content of this comment
`metadata` | All metadata as in the comment file (or described above)
`replies` | Array containing the top level replies for this comment
##### Example article.html theme
(only the comment section)
```html
{% if article.metadata.comments %}
{% for comment in article.metadata.comments recursive %}
{% set metadata = comment.metadata %}
{% if loop.depth0 == 0 %}
{% set marginLeft = 0 %}
{% else %}
{% set marginLeft = 50 %}
{% 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>
<h4>{{ metadata['author'] }}</h4>
<p>Posted on <abbr class="published" title="{{ metadata['date'].isoformat() }}">{{ metadata['locale_date'] }}</abbr></p>
{{ comment.content }}
{% if comment.replies %}
{{ loop(comment.replies) }}
{% endif %}
</article>
{% endfor %}
{% else %}
<p>There are no comments yet.<p>
{% endif %}
```
## Recommendation
Add a form, which allows your visitors to easily write comments.
But more importantly, on submit the form generates a mailto-link.
The resulting email contains a valid markdown block. Now you only have to copy this block in a new file. And therefore there is no need to gather the metadata (like date, author, replyto) yourself.
##### Reply button
Add this in the above `for` loop, so your visitors can reply to a comment.
```html
<button onclick="reply('{{comment.id | urlencode}}');">Reply</button>
```
##### form + javascript
```html
<form role="form" id="commentForm" action="#">
<input name="Name" type="text" id="commentForm_inputName" placeholder="Enter your name or synonym">
<textarea name="Text" id="commentForm_inputText" rows="10" style="resize:vertical;" placeholder="Your comment"></textarea>
<button type="submit" id="commentForm_button">Post via email</button>
<input name="replyto" type="hidden" id="commentForm_replyto">
</form>
```
```javascript
<script type="text/javascript">
function reply(id)
{
id = decodeURIComponent(id);
$('#commentForm_replyto').val(id);
}
$(document).ready(function() {
function generateMailToLink()
{
var user = 'your_user_name'; //user@domain = your email address
var domain = 'your_email_provider';
var subject = 'Comment for \'{{ article.slug }}\'' ;
var d = new Date();
var body = ''
+ 'Hey,\nI posted a new comment on ' + document.URL + '\n\nGreetings ' + $("#commentForm_inputName").val() + '\n\n\n'
+ 'Raw comment data:\n'
+ '----------------------------------------\n'
+ 'date: ' + d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate() + ' ' + d.getHours() + ':' + d.getMinutes() + '\n'
+ 'author: ' + $("#commentForm_inputName").val() + '\n';
var replyto = $('#commentForm_replyto').val();
if (replyto.length != 0)
{
body += 'replyto: ' + replyto + '\n'
}
body += '\n'
+ $("#commentForm_inputText").val() + '\n'
+ '----------------------------------------\n';
var link = 'mailto:' + user + '@' + domain + '?subject='
+ encodeURIComponent(subject)
+ "&body="
+ encodeURIComponent(body);
return link;
}
$('#commentForm').on("submit",
function( event )
{
event.preventDefault();
$(location).attr('href', generateMailToLink());
}
);
});
</script>
```
(jQuery is required for this script)
Don't forget to set the Variables `user` and `domain`.

View File

@@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-
"""
"""
import logging
import os
import hashlib
logger = logging.getLogger(__name__)
_log = "pelican_comment_system: avatars: "
try:
from identicon import identicon
_identiconImported = True
except ImportError as e:
logger.warning(_log + "identicon deactivated: " + str(e))
_identiconImported = False
# Global Variables
_identicon_save_path = None
_identicon_output_path = None
_identicon_data = None
_identicon_size = None
_initialized = False
_authors = None
_missingAvatars = []
def _ready():
if not _initialized:
logger.warning(_log + "Module not initialized. use init")
if not _identicon_data:
logger.debug(_log + "No identicon data set")
return _identiconImported and _initialized and _identicon_data
def init(pelican_output_path, identicon_output_path, identicon_data, identicon_size, authors):
global _identicon_save_path
global _identicon_output_path
global _identicon_data
global _identicon_size
global _initialized
global _authors
_identicon_save_path = os.path.join(pelican_output_path, identicon_output_path)
_identicon_output_path = identicon_output_path
_identicon_data = identicon_data
_identicon_size = identicon_size
_authors = authors
_initialized = True
def _createIdenticonOutputFolder():
if not _ready():
return
if not os.path.exists(_identicon_save_path):
os.makedirs(_identicon_save_path)
def getAvatarPath(comment_id, metadata):
if not _ready():
return ''
md5 = hashlib.md5()
author = tuple()
for data in _identicon_data:
if data in metadata:
string = str(metadata[data])
md5.update(string)
author += tuple([string])
else:
logger.warning(_log + data + " is missing in comment: " + comment_id)
if author in _authors:
return _authors[author]
global _missingAvatars
code = md5.hexdigest()
if not code in _missingAvatars:
_missingAvatars.append(code)
return os.path.join(_identicon_output_path, '%s.png' % code)
def generateAndSaveMissingAvatars():
_createIdenticonOutputFolder()
for code in _missingAvatars:
avatar_path = '%s.png' % code
avatar = identicon.render_identicon(int(code, 16), _identicon_size)
avatar_save_path = os.path.join(_identicon_save_path, avatar_path)
avatar.save(avatar_save_path, 'PNG')

View File

@@ -0,0 +1,35 @@
# Avatars and Identicons
To activate the avatars and [identicons](https://en.wikipedia.org/wiki/Identicon) you have to set `PELICAN_COMMENT_SYSTEM_IDENTICON_DATA`.
##### Example
```python
PELICAN_COMMENT_SYSTEM_IDENTICON_DATA = ('author')
```
Now every comment with the same author tag will be treated as if written from the same person. And therefore have the same avatar/identicon. Of cause you can modify this tuple so other metadata are checked.
## Specific Avatars
To set a specific avatar for a author you have to add them to the `PELICAN_COMMENT_SYSTEM_AUTHORS` dictionary.
The `key` of the dictionary has to be a tuple of the form of `PELICAN_COMMENT_SYSTEM_IDENTICON_DATA`, so in our case only the author's name.
The `value` of the dictionary is the path to the specific avatar.
##### Example
```python
PELICAN_COMMENT_SYSTEM_AUTHORS = {
('John'): "images/authors/john.png",
('Tom'): "images/authors/tom.png",
}
```
## Theme
To display the avatars and identicons simply add the following in the "comment for loop" in you theme:
```html
<img src="{{ SITEURL }}/{{ comment.avatar }}"
alt="Avatar"
height="{{ PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE }}"
width="{{ PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE }}">
```
Of cause the `height` and `width` are optional, but they make sure that everything has the same size (in particular specific avatars).

View File

@@ -0,0 +1,83 @@
# Comment Form (aka: never gather Metadata)
Add a form, which allows your visitors to easily write comments.
But more importantly, on submit the form generates a mailto-link.
The resulting email contains a valid markdown block. Now you only have to copy this block in a new file. And therefore there is no need to gather the metadata (like date, author, replyto) yourself.
#### Reply button
Add this in the "comment for loop" in your article theme, so your visitors can reply to a comment.
```html
<button onclick="reply('{{comment.id | urlencode}}');">Reply</button>
```
#### Form
A basic form so your visitors can write comments.
```html
<form role="form" id="commentForm" action="#">
<input name="Name" type="text" id="commentForm_inputName" placeholder="Enter your name or synonym">
<textarea name="Text" id="commentForm_inputText" rows="10" style="resize:vertical;" placeholder="Your comment"></textarea>
<button type="submit" id="commentForm_button">Post via email</button>
<input name="replyto" type="hidden" id="commentForm_replyto">
</form>
```
You may want to add a button to reset the `replyto` field.
#### Javascript
To generate the mailto-Link and set the `replyto` field there is some javascript required.
```javascript
<script type="text/javascript">
function reply(id)
{
id = decodeURIComponent(id);
$('#commentForm_replyto').val(id);
}
$(document).ready(function() {
function generateMailToLink()
{
var user = 'your_user_name'; //user@domain = your email address
var domain = 'your_email_provider';
var subject = 'Comment for \'{{ article.slug }}\'' ;
var d = new Date();
var body = ''
+ 'Hey,\nI posted a new comment on ' + document.URL + '\n\nGreetings ' + $("#commentForm_inputName").val() + '\n\n\n'
+ 'Raw comment data:\n'
+ '----------------------------------------\n'
+ 'date: ' + d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate() + ' ' + d.getHours() + ':' + d.getMinutes() + '\n'
+ 'author: ' + $("#commentForm_inputName").val() + '\n';
var replyto = $('#commentForm_replyto').val();
if (replyto.length != 0)
{
body += 'replyto: ' + replyto + '\n'
}
body += '\n'
+ $("#commentForm_inputText").val() + '\n'
+ '----------------------------------------\n';
var link = 'mailto:' + user + '@' + domain + '?subject='
+ encodeURIComponent(subject)
+ "&body="
+ encodeURIComponent(body);
return link;
}
$('#commentForm').on("submit",
function( event )
{
event.preventDefault();
$(location).attr('href', generateMailToLink());
}
);
});
</script>
```
(jQuery is required for this script)
Don't forget to set the Variables `user` and `domain`.

View File

@@ -0,0 +1,104 @@
# Installation
Activate the plugin by adding it to your `pelicanconf.py`
PLUGIN_PATH = '/path/to/pelican-plugins'
PLUGINS = ['pelican_comment_system']
PELICAN_COMMENT_SYSTEM = True
And modify your `article.html` theme (see below).
## Settings
Name | Type | Default | Description
-----------------------------------------------|-----------|--------------------|-------
`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_IDENTICON_OUTPUT_PATH` | `string` | `images/identicon` | 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_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 infos [here](avatars.md)
## Folder structure
Every comment file has to be stored in a sub folder of `PELICAN_COMMENT_SYSTEM_DIR`.
Sub folders are named after the `slug` of the articles.
So the comments to your `foo-bar` article are stored in `comments/foo-bar/`
The filenames of the comment files are up to you. But the filename is the Identifier of the comment (without extension).
##### Example folder structure
.
└── comments
└── foo-bar
│ ├── 1.md
│ └── 0.md
└── some-other-slug
├── random-Name.md
├── 1.md
└── 0.md
## Comment file
### Meta information
Tag | Required | Description
--------------|-----------|----------------
`date` | yes | Date when the comment was posted
`replyto` | no | Identifier of the parent comment. Identifier = Filename (without extension)
`locale_date` | forbidden | Will be overwritten with a locale representation of the date
Every other (custom) tag gets parsed as well and will be available through the theme.
##### Example of a comment file
date: 2014-3-21 15:02
author: Author of the comment
website: http://authors.website.com
replyto: 7
anothermetatag: some random tag
Content of the comment.
## Theme
In the `article.html` theme file are now two more variables available.
Variables | Description
----------------------------------|--------------------------
`article.metadata.comments_count` | Amount of total comments for this article (including replies to comments)
`article.metadata.comments` | Array containing the top level comments for this article (no replies to comments)
### Comment object
Variables | Description
-----------|--------------------------
`id` | Identifier of this comment
`content` | Content of this comment
`metadata` | All metadata as in the comment file (or described above)
`replies` | Array containing the top level replies for this comment
`avatar` | Path to the avatar or identicon of the comment author
##### Example article.html theme
(only the comment section)
```html
{% if article.metadata.comments %}
{% for comment in article.metadata.comments recursive %}
{% set metadata = comment.metadata %}
{% if loop.depth0 == 0 %}
{% set marginLeft = 0 %}
{% else %}
{% set marginLeft = 50 %}
{% 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>
<h4>{{ metadata['author'] }}</h4>
<p>Posted on <abbr class="published" title="{{ metadata['date'].isoformat() }}">{{ metadata['locale_date'] }}</abbr></p>
{{ comment.content }}
{% if comment.replies %}
{{ loop(comment.replies) }}
{% endif %}
</article>
{% endfor %}
{% else %}
<p>There are no comments yet.<p>
{% endif %}
```

View File

@@ -0,0 +1,11 @@
identicon.py is Licesensed under FreeBSD License.
(http://www.freebsd.org/copyright/freebsd-license.html)
Copyright 1994-2009 Shin Adachi. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,17 @@
identicon.py: identicon python implementation.
==============================================
:Author:Shin Adachi <shn@glucose.jp>
## usage
### commandline
python identicon.py [code]
### python
import identicon
identicon.render_identicon(code, size)
Return a PIL Image class instance which have generated identicon image.
`size` specifies patch size. Generated image size is 3 * `size`.

View File

@@ -0,0 +1,255 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
identicon.py
identicon python implementation.
by Shin Adachi <shn@glucose.jp>
= usage =
== commandline ==
>>> python identicon.py [code]
== python ==
>>> import identicon
>>> identicon.render_identicon(code, size)
Return a PIL Image class instance which have generated identicon image.
```size``` specifies `patch size`. Generated image size is 3 * ```size```.
"""
# g
# PIL Modules
from PIL import Image, ImageDraw, ImagePath, ImageColor
__all__ = ['render_identicon', 'IdenticonRendererBase']
class Matrix2D(list):
"""Matrix for Patch rotation"""
def __init__(self, initial=[0.] * 9):
assert isinstance(initial, list) and len(initial) == 9
list.__init__(self, initial)
def clear(self):
for i in xrange(9):
self[i] = 0.
def set_identity(self):
self.clear()
for i in xrange(3):
self[i] = 1.
def __str__(self):
return '[%s]' % ', '.join('%3.2f' % v for v in self)
def __mul__(self, other):
r = []
if isinstance(other, Matrix2D):
for y in xrange(3):
for x in xrange(3):
v = 0.0
for i in xrange(3):
v += (self[i * 3 + x] * other[y * 3 + i])
r.append(v)
else:
raise NotImplementedError
return Matrix2D(r)
def for_PIL(self):
return self[0:6]
@classmethod
def translate(kls, x, y):
return kls([1.0, 0.0, float(x),
0.0, 1.0, float(y),
0.0, 0.0, 1.0])
@classmethod
def scale(kls, x, y):
return kls([float(x), 0.0, 0.0,
0.0, float(y), 0.0,
0.0, 0.0, 1.0])
"""
# need `import math`
@classmethod
def rotate(kls, theta, pivot=None):
c = math.cos(theta)
s = math.sin(theta)
matR = kls([c, -s, 0., s, c, 0., 0., 0., 1.])
if not pivot:
return matR
return kls.translate(-pivot[0], -pivot[1]) * matR *
kls.translate(*pivot)
"""
@classmethod
def rotateSquare(kls, theta, pivot=None):
theta = theta % 4
c = [1., 0., -1., 0.][theta]
s = [0., 1., 0., -1.][theta]
matR = kls([c, -s, 0., s, c, 0., 0., 0., 1.])
if not pivot:
return matR
return kls.translate(-pivot[0], -pivot[1]) * matR * \
kls.translate(*pivot)
class IdenticonRendererBase(object):
PATH_SET = []
def __init__(self, code):
"""
@param code code for icon
"""
if not isinstance(code, int):
code = int(code)
self.code = code
def render(self, size):
"""
render identicon to PIL.Image
@param size identicon patchsize. (image size is 3 * [size])
@return PIL.Image
"""
# decode the code
middle, corner, side, foreColor, backColor = self.decode(self.code)
# make image
image = Image.new("RGB", (size * 3, size * 3))
draw = ImageDraw.Draw(image)
# fill background
draw.rectangle((0, 0, image.size[0], image.size[1]), fill=0)
kwds = {
'draw': draw,
'size': size,
'foreColor': foreColor,
'backColor': backColor}
# middle patch
self.drawPatch((1, 1), middle[2], middle[1], middle[0], **kwds)
# side patch
kwds['type'] = side[0]
for i in xrange(4):
pos = [(1, 0), (2, 1), (1, 2), (0, 1)][i]
self.drawPatch(pos, side[2] + 1 + i, side[1], **kwds)
# corner patch
kwds['type'] = corner[0]
for i in xrange(4):
pos = [(0, 0), (2, 0), (2, 2), (0, 2)][i]
self.drawPatch(pos, corner[2] + 1 + i, corner[1], **kwds)
return image
def drawPatch(self, pos, turn, invert, type, draw, size, foreColor,
backColor):
"""
@param size patch size
"""
path = self.PATH_SET[type]
if not path:
# blank patch
invert = not invert
path = [(0., 0.), (1., 0.), (1., 1.), (0., 1.), (0., 0.)]
patch = ImagePath.Path(path)
if invert:
foreColor, backColor = backColor, foreColor
mat = Matrix2D.rotateSquare(turn, pivot=(0.5, 0.5)) *\
Matrix2D.translate(*pos) *\
Matrix2D.scale(size, size)
patch.transform(mat.for_PIL())
draw.rectangle((pos[0] * size, pos[1] * size, (pos[0] + 1) * size,
(pos[1] + 1) * size), fill=backColor)
draw.polygon(patch, fill=foreColor, outline=foreColor)
### virtual functions
def decode(self, code):
raise NotImplementedError
class DonRenderer(IdenticonRendererBase):
"""
Don Park's implementation of identicon
see : http://www.docuverse.com/blog/donpark/2007/01/19/identicon-updated-and-source-released
"""
PATH_SET = [
[(0, 0), (4, 0), (4, 4), (0, 4)], # 0
[(0, 0), (4, 0), (0, 4)],
[(2, 0), (4, 4), (0, 4)],
[(0, 0), (2, 0), (2, 4), (0, 4)],
[(2, 0), (4, 2), (2, 4), (0, 2)], # 4
[(0, 0), (4, 2), (4, 4), (2, 4)],
[(2, 0), (4, 4), (2, 4), (3, 2), (1, 2), (2, 4), (0, 4)],
[(0, 0), (4, 2), (2, 4)],
[(1, 1), (3, 1), (3, 3), (1, 3)], # 8
[(2, 0), (4, 0), (0, 4), (0, 2), (2, 2)],
[(0, 0), (2, 0), (2, 2), (0, 2)],
[(0, 2), (4, 2), (2, 4)],
[(2, 2), (4, 4), (0, 4)],
[(2, 0), (2, 2), (0, 2)],
[(0, 0), (2, 0), (0, 2)],
[]] # 15
MIDDLE_PATCH_SET = [0, 4, 8, 15]
# modify path set
for idx in xrange(len(PATH_SET)):
if PATH_SET[idx]:
p = map(lambda vec: (vec[0] / 4.0, vec[1] / 4.0), PATH_SET[idx])
PATH_SET[idx] = p + p[:1]
def decode(self, code):
# decode the code
middleType = self.MIDDLE_PATCH_SET[code & 0x03]
middleInvert= (code >> 2) & 0x01
cornerType = (code >> 3) & 0x0F
cornerInvert= (code >> 7) & 0x01
cornerTurn = (code >> 8) & 0x03
sideType = (code >> 10) & 0x0F
sideInvert = (code >> 14) & 0x01
sideTurn = (code >> 15) & 0x03
blue = (code >> 16) & 0x1F
green = (code >> 21) & 0x1F
red = (code >> 27) & 0x1F
foreColor = (red << 3, green << 3, blue << 3)
return (middleType, middleInvert, 0),\
(cornerType, cornerInvert, cornerTurn),\
(sideType, sideInvert, sideTurn),\
foreColor, ImageColor.getrgb('white')
def render_identicon(code, size, renderer=None):
if not renderer:
renderer = DonRenderer
return renderer(code).render(size)
if __name__ == '__main__':
import sys
if len(sys.argv) < 2:
print 'usage: python identicon.py [CODE]....'
raise SystemExit
for code in sys.argv[1:]:
if code.startswith('0x') or code.startswith('0X'):
code = int(code[2:], 16)
elif code.startswith('0'):
code = int(code[1:], 8)
else:
code = int(code)
icon = render_identicon(code, 24)
icon.save('%08x.png' % code, 'PNG')

View File

@@ -18,12 +18,15 @@ from pelican import signals
from pelican.utils import strftime from pelican.utils import strftime
from pelican.readers import MarkdownReader from pelican.readers import MarkdownReader
import avatars
class Comment: class Comment:
def __init__(self, id, metadata, content): def __init__(self, id, metadata, content, avatar):
self.id = id self.id = id
self.content = content self.content = content
self.metadata = metadata self.metadata = metadata
self.replies = [] self.replies = []
self.avatar = avatar
def addReply(self, comment): def addReply(self, comment):
self.replies.append(comment) self.replies.append(comment)
@@ -53,15 +56,32 @@ class Comment:
return amount + len(self.replies) return amount + len(self.replies)
def 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_DATA', ())
DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE', 72)
DEFAULT_CONFIG.setdefault('PELICAN_COMMENT_SYSTEM_AUTHORS', {})
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')
pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_OUTPUT_PATH', 'images/identicon')
pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_DATA', ())
pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE', 72)
pelican.settings.setdefault('PELICAN_COMMENT_SYSTEM_AUTHORS', {})
def initialize(article_generator):
avatars.init(
article_generator.settings['OUTPUT_PATH'],
article_generator.settings['PELICAN_COMMENT_SYSTEM_IDENTICON_OUTPUT_PATH'],
article_generator.settings['PELICAN_COMMENT_SYSTEM_IDENTICON_DATA'],
article_generator.settings['PELICAN_COMMENT_SYSTEM_IDENTICON_SIZE']/3,
article_generator.settings['PELICAN_COMMENT_SYSTEM_AUTHORS'],
)
def add_static_comments(gen, metadata): def add_static_comments(gen, metadata):
if gen.settings['PELICAN_COMMENT_SYSTEM'] != True: if gen.settings['PELICAN_COMMENT_SYSTEM'] != True:
return return
@@ -73,21 +93,25 @@ def add_static_comments(gen, metadata):
logger.warning("pelican_comment_system: cant't locate comments files without slug tag in the article") logger.warning("pelican_comment_system: cant't locate comments files without slug tag in the article")
return return
reader = MarkdownReader(gen.settings)
comments = []
replies = []
folder = os.path.join(gen.settings['PELICAN_COMMENT_SYSTEM_DIR'], metadata['slug']) folder = os.path.join(gen.settings['PELICAN_COMMENT_SYSTEM_DIR'], metadata['slug'])
if not os.path.isdir(folder): if not os.path.isdir(folder):
logger.debug("No comments found for: " + metadata['slug']) logger.debug("No comments found for: " + metadata['slug'])
return return
reader = MarkdownReader(gen.settings)
comments = []
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: if extension[1:].lower() in reader.file_extensions:
content, meta = reader.read(folder + "/" + file) content, meta = reader.read(os.path.join(folder, file))
meta['locale_date'] = strftime(meta['date'], gen.settings['DEFAULT_DATE_FORMAT']) meta['locale_date'] = strftime(meta['date'], gen.settings['DEFAULT_DATE_FORMAT'])
com = Comment(name, meta, content)
avatar_path = avatars.getAvatarPath(name, meta)
com = Comment(name, meta, content, avatar_path)
if 'replyto' in meta: if 'replyto' in meta:
replies.append( com ) replies.append( com )
else: else:
@@ -109,6 +133,11 @@ def add_static_comments(gen, metadata):
metadata['comments_count'] = len(comments) + count metadata['comments_count'] = len(comments) + count
metadata['comments'] = comments metadata['comments'] = comments
def writeIdenticonsToDisk(gen, writer):
avatars.generateAndSaveMissingAvatars()
def register(): def register():
signals.initialized.connect(initialized) signals.initialized.connect(pelican_initialized)
signals.article_generator_init.connect(initialize)
signals.article_generator_context.connect(add_static_comments) signals.article_generator_context.connect(add_static_comments)
signals.article_writer_finalized.connect(writeIdenticonsToDisk)