Skip to content

Commit feb6c22

Browse files
committed
adding gravatar to the mix
1 parent f08caa1 commit feb6c22

File tree

7 files changed

+340
-10
lines changed

7 files changed

+340
-10
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,7 @@ pyrightconfig.json
210210

211211
# ablog
212212
_website/
213-
.doctrees/
213+
.doctrees/
214+
215+
# python.pe
216+
_static/images/gravatar/

blog/members/hellhound.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ lanaguage: Español
99

1010
# Jean-Pierre Chauvel
1111

12+
```{gravatar} jean.pierre@chauvel.org
13+
---
14+
class: something
15+
width: 200
16+
style: "margin: 0 auto; display: block;"
17+
with-circle-clip: true
18+
with-grayscale: true
19+
static-subdir: images/gravatar
20+
---
21+
```
22+
1223
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus.
1324
Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed,
1425
dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper

conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@
4747
# links. Dictionary keys are what should be used in ``post`` directive
4848
# to refer to the author. Default is ``{}``.
4949
blog_authors = {
50-
nickname: (name, email_link)
51-
for nickname, name, email_link in get_authors()
50+
nickname: (name, email_link) for nickname, name, email_link in get_authors()
5251
}
5352

5453

@@ -210,6 +209,7 @@
210209
"sphinx_togglebutton",
211210
"sphinx_sitemap",
212211
"embed",
212+
"gravatar",
213213
]
214214

215215
myst_enable_extensions = [

ext/gravatar.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
"""A directive to generate an image tag with a Gravatar pic.
2+
"""
3+
4+
import os
5+
import urllib.request
6+
from hashlib import sha256
7+
8+
from ablog.commands import find_confdir, read_conf
9+
from docutils import nodes
10+
from docutils.parsers.rst import Directive
11+
from jinja2 import BaseLoader, Environment
12+
from libgravatar import Gravatar
13+
from PIL import Image, ImageDraw
14+
from sphinx.application import Sphinx
15+
from sphinx.util import logging
16+
17+
logger = logging.getLogger(__name__)
18+
19+
GRAVATAR_TEMPLATE = """
20+
<div class="{{ klass }}">
21+
<img src=/{{ url }}
22+
{% if align %} align="{{ align }}"{% endif %}
23+
{% if klass %} class="{{ klass }}"{% endif %}
24+
{% if style %} style="{{ style }}"{% endif %}
25+
{% if width %} width="{{ width }}" height="{{ width }}"{% endif %}>
26+
</div>
27+
"""
28+
29+
FILENAME_EXT = ".png"
30+
31+
32+
class GravatarError(Exception):
33+
pass
34+
35+
36+
def _to_boolean(argument: str) -> str:
37+
# Weird behavior, but true~
38+
return argument is None
39+
40+
41+
class GravatarImage(Directive):
42+
arguments = 1
43+
has_content = True
44+
final_argument_whitespace = False
45+
option_spec = {
46+
"align": lambda a: a.strip(),
47+
"class": lambda a: a.strip(),
48+
"style": lambda a: a.strip(),
49+
"width": lambda a: a.strip(),
50+
"with-circle-clip": _to_boolean,
51+
"with-grayscale": _to_boolean,
52+
"static-subdir": lambda a: a.strip(),
53+
}
54+
55+
def _process_image(
56+
self, filepath: str, with_circle_clip: bool, with_grayscale: bool
57+
):
58+
original_image = Image.open(filepath)
59+
60+
if with_grayscale:
61+
# Convert the original image to grayscale
62+
original_image = original_image.convert("L")
63+
64+
if with_circle_clip:
65+
# Create a mask image with a white circle on a black background
66+
mask = Image.new("L", original_image.size, 0)
67+
draw = ImageDraw.Draw(mask)
68+
width, height = original_image.size
69+
draw.ellipse((0, 0, width, height), fill=255)
70+
71+
# Convert the mask to use an alpha channel
72+
result = original_image.copy()
73+
result.putalpha(mask)
74+
else:
75+
result = original_image
76+
result.save(filepath)
77+
78+
def run(self):
79+
email = self.content[0]
80+
align = self.options.get("align")
81+
klass = self.options.get("class")
82+
style = self.options.get("style")
83+
width = self.options.get("width")
84+
with_circle_clip = self.options.get("with-circle-clip")
85+
with_grayscale = self.options.get("with-grayscale")
86+
static_subdir = self.options.get("static-subdir")
87+
confdir = find_confdir()
88+
conf = read_conf(confdir)
89+
html_static_path = getattr(conf, "html_static_path", [])
90+
91+
if len(html_static_path) == 0:
92+
raise GravatarError(
93+
"html_static_path should have at least one path configured"
94+
)
95+
96+
# We choose the first path as the default path for the image
97+
save_path = os.path.join(html_static_path[0], static_subdir)
98+
99+
# Try to make the dir if it doesn't exist
100+
os.makedirs(save_path, exist_ok=True)
101+
102+
logger.info(f"Getting Gravatar image for email: {email}")
103+
url = Gravatar(email).get_image()
104+
if width is not None:
105+
url = f"{url}?s={width}"
106+
logger.info(f"Requesting Gravatar image from URL: {url}")
107+
108+
filename = sha256(url.encode()).digest().hex()
109+
filename = f"{filename}{FILENAME_EXT}"
110+
save_path = os.path.join(save_path, filename)
111+
urllib.request.urlretrieve(url, save_path)
112+
logger.info(f"Retrieving image into: {save_path}")
113+
114+
if with_circle_clip or with_grayscale:
115+
self._process_image(save_path, with_circle_clip, with_grayscale)
116+
logger.info("Applying image post-processing")
117+
118+
template = Environment(
119+
loader=BaseLoader, trim_blocks=True, lstrip_blocks=True
120+
).from_string(GRAVATAR_TEMPLATE)
121+
122+
out = template.render(
123+
url=save_path,
124+
align=align,
125+
klass=klass,
126+
style=style,
127+
width=width,
128+
)
129+
# User a raw pass-through node
130+
para = nodes.raw("", out, format="html")
131+
return [para]
132+
133+
134+
def setup(app: Sphinx):
135+
app.add_directive("gravatar", GravatarImage)
136+
137+
return {
138+
"version": "0.1",
139+
"parallel_read_safe": True,
140+
"parallel_write_safe": True,
141+
}

poetry.lock

Lines changed: 98 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ sphinx-design = "^0.5.0"
1818
sphinx-togglebutton = "^0.3.2"
1919
sphinx-sitemap = "^2.5.1"
2020
sphinx-tabs = "^3.4.5"
21+
libgravatar = "^1.0.4"
22+
pillow = "^10.3.0"
2123

2224

2325
[tool.poetry.group.dev.dependencies]

0 commit comments

Comments
 (0)