Modernize code and add a test

This commit is contained in:
Leonardo Giordani 2021-03-12 16:34:46 +00:00 committed by Justin Mayer
commit 5b7dbc440c
14 changed files with 546 additions and 110 deletions

15
.editorconfig Normal file
View file

@ -0,0 +1,15 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.py]
max_line_length = 88
[*.yml]
indent_size = 2

104
.github/workflows/main.yml vendored Normal file
View file

@ -0,0 +1,104 @@
name: build
on: [push, pull_request]
env:
PYTEST_ADDOPTS: "--color=yes"
jobs:
test:
name: Test - ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Set up Pip cache
uses: actions/cache@v2
id: pip-cache
with:
path: ~/.cache/pip
key: pip-${{ hashFiles('**/pyproject.toml') }}
- name: Upgrade Pip
run: python -m pip install --upgrade pip
- name: Install Poetry
run: python -m pip install poetry
- name: Set up Poetry cache
uses: actions/cache@v2
id: poetry-cache
with:
path: ~/.cache/pypoetry/virtualenvs
key: poetry-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies
run: |
poetry run pip install --upgrade pip
poetry install
- name: Run tests
run: poetry run invoke tests
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: "3.x"
- name: Set Poetry cache
uses: actions/cache@v2
id: poetry-cache
with:
path: ~/.cache/pypoetry/virtualenvs
key: poetry-${{ hashFiles('**/poetry.lock') }}
- name: Upgrade Pip
run: python -m pip install --upgrade pip
- name: Install Poetry
run: python -m pip install poetry
- name: Install dependencies
run: |
poetry run pip install --upgrade pip
poetry install
- name: Run linters
run: poetry run invoke lint
deploy:
name: Deploy
environment: Deployment
needs: [test, lint]
runs-on: ubuntu-latest
if: ${{ github.ref=='refs/heads/main' && github.event_name!='pull_request' }}
steps:
- uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: "3.x"
- name: Check release
id: check_release
run: |
python -m pip install --upgrade pip
python -m pip install poetry githubrelease httpx==0.16.1 autopub
echo "##[set-output name=release;]$(autopub check)"
- name: Publish
if: ${{ steps.check_release.outputs.release=='' }}
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
git remote set-url origin https://$GITHUB_TOKEN@github.com/${{ github.repository }}
autopub prepare
poetry build
autopub commit
autopub githubrelease
poetry publish -u __token__ -p $PYPI_PASSWORD

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
poetry.lock

31
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,31 @@
# See https://pre-commit.com/hooks.html for info on hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
hooks:
- id: check-added-large-files
- id: check-ast
- id: check-case-conflict
- id: check-toml
- id: check-yaml
- id: debug-statements
- id: detect-private-key
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 19.10b0
hooks:
- id: black
- repo: https://gitlab.com/pycqa/flake8
rev: 3.9.0
hooks:
- id: flake8
args: [--max-line-length=88]
language_version: python3.7
- repo: https://github.com/PyCQA/isort
rev: 5.7.0
hooks:
- id: isort

9
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,9 @@
Contributing
============
Contributions are welcome and much appreciated. Every little bit helps. You can contribute by improving the documentation, adding missing features, and fixing bugs. You can also help out by reviewing and commenting on [existing issues][].
To start contributing to this plugin, review the [Contributing to Pelican][] documentation, beginning with the **Contributing Code** section.
[existing issues]: https://github.com/pelican-plugins/share-post/issues
[Contributing to Pelican]: https://docs.getpelican.com/en/latest/contribute.html

105
README.md
View file

@ -1,75 +1,86 @@
# Share Post
# Share Post: A Plugin for Pelican
A Pelican plugin to create share URLs of article
[![Build Status](https://img.shields.io/github/workflow/status/pelican-plugins/share-post/build)](https://github.com/pelican-plugins/share-post/actions)
[![PyPI Version](https://img.shields.io/pypi/v/pelican-share-post)](https://pypi.org/project/pelican-share-post/)
![License](https://img.shields.io/pypi/l/pelican-share-post?color=blue)
# Author
Share Post is a Pelican plugin that creates share links in articles that allow site visitors to share the current article with others in a privacy-friendly manner.
Copyright (c) Talha Mansoor
Many web sites have share widgets to let readers share posts on social networks. Most of these widgets are used by vendors for online tracking. These widgets can also be visually-distracting and negatively affect readers attention.
Author | Talha Mansoor
----------------|-----
Author Email | talha131@gmail.com
Author Homepage | http://onCrashReboot.com
Github Account | https://github.com/talha131
Share Post creates old-school URLs for some popular sites which your theme can use. These links do not have the ability to track site visitors. They can also be unobtrusive depending on how Pelican theme uses them.
### Contributors
* [Jonathan DEKHTIAR](https://github.com/DEKHTIARJonathan) - contact@jonathandekhtiar.eu
* [Paolo Melchiorre](https://github.com/pauloxnet) - [www.paulox.net](https://www.paulox.net/)
Installation
------------
## Why do you need it?
This plugin can be installed via:
Almost all website have share widgets to let readers share posts on social
networks. Most of these widgets are used by vendors for online tracking. These
widgets are also visual which quite often become a distraction and negatively
affect readers attention.
python -m pip install pelican-share-post
`share_post` creates old school URLs for some popular sites which your theme
can use. These links do not have the ability to track the users. They can also
be unobtrusive depending on how Pelican theme uses them.
Usage
-----
## Requirements
This plugin adds to each Pelican article a dictionary of URLs that, when followed, allows the reader to easily share the article via specific channels. When activated, the plugin adds the attribute `share_post` to each article with the following format:
`share_post` requires BeautifulSoup
```bash
pip install beautifulsoup4
``` python
article.share_post = {
"facebook": "<URL>",
"email": "<URL>",
"twitter": "<URL>",
"diaspora": "<URL>",
"linkedin": "<URL>",
"hacker-news": "<URL>",
"reddit": "<URL>",
}
```
## How to Use
You can then access those variables in your template. For example:
`share_post` adds a dictionary attribute to `article` which can be accessed via
`article.share_post`. Keys of the dictionary are as follows,
1. `facebook`
1. `email`
1. `twitter`
1. `diaspora`
1. `linkedin`
1. `hacker-news`
1. `reddit`
## Template Example
```html
``` html+jinja
{% if article.share_post and article.status != 'draft' %}
<section>
<p id="post-share-links">
Share on:
<a href="{{article.share_post['diaspora']}}" target="_blank" title="Share on Diaspora">Diaspora*</a>
<a href="{{article.share_post['diaspora']}}" title="Share on Diaspora">Diaspora*</a>
<a href="{{article.share_post['twitter']}}" target="_blank" title="Share on Twitter">Twitter</a>
<a href="{{article.share_post['twitter']}}" title="Share on Twitter">Twitter</a>
<a href="{{article.share_post['facebook']}}" target="_blank" title="Share on Facebook">Facebook</a>
<a href="{{article.share_post['facebook']}}" title="Share on Facebook">Facebook</a>
<a href="{{article.share_post['linkedin']}}" target="_blank" title="Share on LinkedIn">LinkedIn</a>
<a href="{{article.share_post['linkedin']}}" title="Share on LinkedIn">LinkedIn</a>
<a href="{{article.share_post['hacker-news']}}" target="_blank" title="Share on HackerNews">HackerNews</a>
<a href="{{article.share_post['hacker-news']}}" title="Share on HackerNews">HackerNews</a>
<a href="{{article.share_post['email']}}" target="_blank" title="Share via Email">Email</a>
<a href="{{article.share_post['email']}}" title="Share via Email">Email</a>
<a href="{{article.share_post['reddit']}}" target="_blank" title="Share via Reddit">Reddit</a>
<a href="{{article.share_post['reddit']}}" title="Share via Reddit">Reddit</a>
</p>
</section>
{% endif %}
```
Contributing
------------
Contributions are welcome and much appreciated. Every little bit helps. You can contribute by improving the documentation, adding missing features, and fixing bugs. You can also help out by reviewing and commenting on [existing issues][].
To start contributing to this plugin, review the [Contributing to Pelican][] documentation, beginning with the **Contributing Code** section.
[existing issues]: https://github.com/pelican-plugins/share-post/issues
[Contributing to Pelican]: https://docs.getpelican.com/en/latest/contribute.html
Contributors
------------
* [Talha Mansoor](https://www.oncrashreboot.com) - talha131@gmail.com
* [Jonathan DEKHTIAR](https://github.com/DEKHTIARJonathan) - contact@jonathandekhtiar.eu
* [Justin Mayer](https://justinmayer.com)
* [Leonardo Giordani](https://www.thedigitalcatonline.com)
License
-------
This project is licensed under the MIT license.

3
RELEASE.md Normal file
View file

@ -0,0 +1,3 @@
Release type: major
Initial release as namespace plugin

View file

@ -0,0 +1,9 @@
import pytest
from pelican.tests.support import temporary_folder
@pytest.fixture
def tmp_folder():
with temporary_folder() as tf:
yield tf

View file

@ -1,100 +1,125 @@
"""
Share Post plugin.
Share Post
==========
This plugin adds share URL to article. These links are textual which means no
online tracking of your readers.
This plugin was originally created by
Talha Mansoor <talha131@gmail.com>
This plugin adds social share URLs to each article.
"""
# If you want to add a new link_processor please
# have a look at the create_link decorator and
# follow the example of the other functions
from urllib.parse import quote
from bs4 import BeautifulSoup
from pelican import contents, signals
from pelican.generators import ArticlesGenerator, PagesGenerator
try:
from urllib.parse import quote
except ImportError:
from urllib import quote
_create_link_functions = []
def article_title(content):
main_title = BeautifulSoup(content.title, 'html.parser').get_text().strip()
sub_title = ''
if hasattr(content, 'subtitle'):
sub_title = ' ' + BeautifulSoup(content.subtitle, 'html.parser').get_text().strip() # noqa
return quote(('%s%s' % (main_title, sub_title)).encode('utf-8'))
# Use this decorator to mark a function as
# a link creator. The function's prototype shall be
# create_link_NAME(title, url, content)
# where
# NAME is the name of the target, e.g. "dispora" or "facebook"
# title is the HTML-safe title of the content
# url is the content URL
# content is the full object, should you need to extract more data.
def create_link(f):
_create_link_functions.append(f)
return f
def article_url(content):
site_url = content.settings['SITEURL']
return quote(('%s/%s' % (site_url, content.url)).encode('utf-8'))
@create_link
def create_link_email(title, url, content):
return f"mailto:?subject={title}&amp;body={url}"
def article_summary(content):
return quote(BeautifulSoup(content.summary, 'html.parser').get_text().strip().encode('utf-8')) # noqa
@create_link
def create_link_hacker_news(title, url, content):
return f"https://news.ycombinator.com/submitlink?t={title}&u={url}"
def twitter_hastags(content):
tags = getattr(content, 'tags', [])
hashtags = ','.join((tag.slug for tag in tags))
return '' if not hashtags else '&hashtags=%s' % hashtags
@create_link
def create_link_diaspora(title, url, content):
return f"https://sharetodiaspora.github.io/?title={title}&url={url}"
def twitter_via(content):
twitter_username = content.settings.get('TWITTER_USERNAME', '')
return '' if not twitter_username else '&via=%s' % twitter_username
@create_link
def create_link_facebook(title, url, content):
return f"https://www.facebook.com/sharer/sharer.php?u={url}"
def share_post(content):
@create_link
def create_link_twitter(title, url, content):
twitter_username = content.settings.get("TWITTER_USERNAME", "")
via = f"&via={twitter_username}" if twitter_username else ""
tags = getattr(content, "tags", [])
tags = ",".join([tag.slug for tag in tags])
hashtags = f"&hashtags={tags}" if tags else ""
return f"https://twitter.com/intent/tweet?text={title}&url={url}{via}{hashtags}"
@create_link
def create_link_reddit(title, url, content):
return f"https://www.reddit.com/submit?url={url}&title={title}"
@create_link
def create_link_linkedin(title, url, content):
summary = quote(
BeautifulSoup(content.summary, "html.parser").get_text().strip().encode("utf-8")
)
return (
f"https://www.linkedin.com/shareArticle?"
f"mini=true&url={url}&title={title}&"
f"summary={summary}&source={url}"
)
def create_share_links(content):
if isinstance(content, contents.Static):
return
title = article_title(content)
url = article_url(content)
summary = article_summary(content)
hastags = twitter_hastags(content)
via = twitter_via(content)
main_title = BeautifulSoup(content.title, "html.parser").get_text().strip()
mail_link = 'mailto:?subject=%s&amp;body=%s' % (title, url)
diaspora_link = 'https://sharetodiaspora.github.io/?title=%s&url=%s' % (
title, url)
facebook_link = 'https://www.facebook.com/sharer/sharer.php?u=%s' % url
twitter_link = 'https://twitter.com/intent/tweet?text=%s&url=%s%s%s' % (
title, url, via, hastags)
hackernews_link = 'https://news.ycombinator.com/submitlink?t=%s&u=%s' % (
title, url)
linkedin_link = 'https://www.linkedin.com/shareArticle?mini=true&url=%s&title=%s&summary=%s&source=%s' % ( # noqa
url, title, summary, url
try:
sub_title = (
" " + BeautifulSoup(content.subtitle, "html.parser").get_text().strip()
)
reddit_link = 'https://www.reddit.com/submit?url=%s&title=%s' % (
url, title)
except AttributeError:
sub_title = ""
content.share_post = {
'diaspora': diaspora_link,
'twitter': twitter_link,
'facebook': facebook_link,
'linkedin': linkedin_link,
'hacker-news': hackernews_link,
'email': mail_link,
'reddit': reddit_link,
}
title = quote(f"{main_title}{sub_title}".encode("utf-8"))
site_url = content.settings["SITEURL"]
url = quote(f"{site_url}/{content.url}".encode("utf-8"))
content.share_post = {}
for func in _create_link_functions:
key = func.__name__.replace("create_link_", "").replace("_", "-")
content.share_post[key] = func(title, url, content)
def run_plugin(generators):
for generator in generators:
if isinstance(generator, ArticlesGenerator):
for article in generator.articles:
share_post(article)
create_share_links(article)
for translation in article.translations:
share_post(translation)
create_share_links(translation)
elif isinstance(generator, PagesGenerator):
for page in generator.pages:
share_post(page)
create_share_links(page)
def register():
try:
signals.all_generators_finalized.connect(run_plugin)
except AttributeError:
# NOTE: This results in #314 so shouldn't really be relied on
# https://github.com/getpelican/pelican-plugins/issues/314
signals.content_object_init.connect(share_post)

View file

@ -0,0 +1,8 @@
Title: Test post
Date: 2021-02-01 13:00:00
Category: test
Tags: foo, bar, foobar
Summary: I have a lot to test
Series: test_series
Content

View file

@ -0,0 +1,65 @@
import os
from share_post import run_plugin
from pelican.generators import ArticlesGenerator
from pelican.tests.support import get_context, get_settings
from . import share_post
def test_share_post(tmp_folder):
base_path = os.path.dirname(os.path.abspath(__file__))
test_data_path = os.path.join(base_path, "test_data")
share_post.register()
settings = get_settings()
context = get_context(settings)
generator = ArticlesGenerator(
context=context,
settings=settings,
path=test_data_path,
theme=settings["THEME"],
output_path=tmp_folder,
)
generator.generate_context()
run_plugin([generator])
share_links = generator.articles[0].share_post
assert (
share_links["diaspora"]
== "https://sharetodiaspora.github.io/?title=Test%20post&url=/test-post.html"
)
assert share_links["twitter"] == (
"https://twitter.com/intent/tweet?text=Test%20post"
"&url=/test-post.html&hashtags=foo,bar,foobar"
)
assert (
share_links["facebook"]
== "https://www.facebook.com/sharer/sharer.php?u=/test-post.html"
)
assert share_links["linkedin"] == (
"https://www.linkedin.com/shareArticle?"
"mini=true&url=/test-post.html&title=Test%20post&"
"summary=I%20have%20a%20lot%20to%20test&source=/test-post.html"
)
assert (
share_links["hacker-news"]
== "https://news.ycombinator.com/submitlink?t=Test%20post&u=/test-post.html"
)
assert (
share_links["email"] == "mailto:?subject=Test%20post&amp;body=/test-post.html"
)
assert (
share_links["reddit"]
== "https://www.reddit.com/submit?url=/test-post.html&title=Test%20post"
)

73
pyproject.toml Normal file
View file

@ -0,0 +1,73 @@
[tool.poetry]
name = "pelican-share-post"
version = "0.0.0"
description = "A Pelican plugin to create share URLs of article"
authors = ["Talha Mansoor <talha131@gmail.com>"]
license = "MIT"
readme = "README.md"
keywords = ["pelican", "plugin", "social"]
repository = "https://github.com/pelican-plugins/share-post"
documentation = "https://docs.getpelican.com"
packages = [
{ include = "pelican" },
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Framework :: Pelican",
"Framework :: Pelican :: Plugins",
"Intended Audience :: End Users/Desktop",
"Operating System :: OS Independent",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Software Development :: Libraries :: Python Modules",
]
[tool.poetry.urls]
"Funding" = "https://donate.getpelican.com/"
"Issue Tracker" = "https://github.com/pelican-plugins/share-post/issues"
[tool.poetry.dependencies]
python = "^3.6"
pelican = "^4.5"
markdown = {version = "^3.2.2", optional = true}
beautifulsoup4 = "^4.9.3"
[tool.poetry.dev-dependencies]
black = {version = "^19.10b0", allow-prereleases = true}
flake8 = "^3.9"
flake8-black = "^0.2.0"
invoke = "^1.3"
isort = "^5.4"
livereload = "^2.6"
markdown = "^3.2.2"
pytest = "^6.0"
pytest-cov = "^2.8"
pytest-pythonpath = "^0.7.3"
pytest-sugar = "^0.9.4"
Werkzeug = "^1.0"
[tool.poetry.extras]
markdown = ["markdown"]
[tool.autopub]
project-name = "Share Post"
git-username = "botpub"
git-email = "botpub@autopub.rocks"
append-github-contributor = true
[tool.isort]
# Maintain compatibility with Black
profile = "black"
multi_line_output = 3
# Sort imports within their section independent of the import type
force_sort_within_sections = true
# Designate "pelican" as separate import section
known_pelican = "pelican"
sections = "FUTURE,STDLIB,THIRDPARTY,PELICAN,FIRSTPARTY,LOCALFOLDER"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

79
tasks.py Normal file
View file

@ -0,0 +1,79 @@
import os
from pathlib import Path
from shutil import which
from invoke import task
PKG_NAME = "share_post"
PKG_PATH = Path(f"pelican/plugins/{PKG_NAME}")
ACTIVE_VENV = os.environ.get("VIRTUAL_ENV", None)
VENV_HOME = Path(os.environ.get("WORKON_HOME", "~/.local/share/virtualenvs"))
VENV_PATH = Path(ACTIVE_VENV) if ACTIVE_VENV else (VENV_HOME / PKG_NAME)
VENV = str(VENV_PATH.expanduser())
TOOLS = ["poetry", "pre-commit"]
POETRY = which("poetry") if which("poetry") else (VENV / Path("bin") / "poetry")
PRECOMMIT = (
which("pre-commit") if which("pre-commit") else (VENV / Path("bin") / "pre-commit")
)
@task
def tests(c):
"""Run the test suite"""
c.run(f"{VENV}/bin/pytest", pty=True)
@task
def black(c, check=False, diff=False):
"""Run Black auto-formatter, optionally with --check or --diff"""
check_flag, diff_flag = "", ""
if check:
check_flag = "--check"
if diff:
diff_flag = "--diff"
c.run(f"{VENV}/bin/black {check_flag} {diff_flag} {PKG_PATH} tasks.py")
@task
def isort(c, check=False, diff=False):
check_flag, diff_flag = "", ""
if check:
check_flag = "-c"
if diff:
diff_flag = "--diff"
c.run(f"{VENV}/bin/isort {check_flag} {diff_flag} .")
@task
def flake8(c):
c.run(f"{VENV}/bin/flake8 {PKG_PATH} tasks.py")
@task
def lint(c):
isort(c, check=True)
black(c, check=True)
flake8(c)
@task
def tools(c):
"""Install tools in the virtual environment if not already on PATH"""
for tool in TOOLS:
if not which(tool):
c.run(f"{VENV}/bin/pip install {tool}")
@task
def precommit(c):
"""Install pre-commit hooks to .git/hooks/pre-commit"""
c.run(f"{PRECOMMIT} install")
@task
def setup(c):
c.run(f"{VENV}/bin/pip install -U pip")
tools(c)
c.run(f"{POETRY} install")
precommit(c)

3
tox.ini Normal file
View file

@ -0,0 +1,3 @@
[flake8]
max-line-length = 88
ignore = E203, W503