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 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.
----------------|-----
Author Email | talha131@gmail.com
Author Homepage | http://onCrashReboot.com
Github Account | https://github.com/talha131
### Contributors
* [Jonathan DEKHTIAR](https://github.com/DEKHTIARJonathan) - contact@jonathandekhtiar.eu Installation
* [Paolo Melchiorre](https://github.com/pauloxnet) - [www.paulox.net](https://www.paulox.net/) ------------
## Why do you need it? This plugin can be installed via:
Almost all website have share widgets to let readers share posts on social python -m pip install pelican-share-post
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.
`share_post` creates old school URLs for some popular sites which your theme Usage
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.
## 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 ``` python
article.share_post = {
```bash "facebook": "<URL>",
pip install beautifulsoup4 "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 ``` html+jinja
`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
{% if article.share_post and article.status != 'draft' %} {% if article.share_post and article.status != 'draft' %}
<section> <section>
<p id="post-share-links"> <p id="post-share-links">
Share on: 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> </p>
</section> </section>
{% endif %} {% 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 This plugin was originally created by
online tracking of your readers. 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 bs4 import BeautifulSoup
from pelican import contents, signals from pelican import contents, signals
from pelican.generators import ArticlesGenerator, PagesGenerator from pelican.generators import ArticlesGenerator, PagesGenerator
try: _create_link_functions = []
from urllib.parse import quote
except ImportError:
from urllib import quote
def article_title(content): # Use this decorator to mark a function as
main_title = BeautifulSoup(content.title, 'html.parser').get_text().strip() # a link creator. The function's prototype shall be
sub_title = '' # create_link_NAME(title, url, content)
if hasattr(content, 'subtitle'): # where
sub_title = ' ' + BeautifulSoup(content.subtitle, 'html.parser').get_text().strip() # noqa # NAME is the name of the target, e.g. "dispora" or "facebook"
return quote(('%s%s' % (main_title, sub_title)).encode('utf-8')) # 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): @create_link
site_url = content.settings['SITEURL'] def create_link_email(title, url, content):
return quote(('%s/%s' % (site_url, content.url)).encode('utf-8')) return f"mailto:?subject={title}&amp;body={url}"
def article_summary(content): @create_link
return quote(BeautifulSoup(content.summary, 'html.parser').get_text().strip().encode('utf-8')) # noqa def create_link_hacker_news(title, url, content):
return f"https://news.ycombinator.com/submitlink?t={title}&u={url}"
def twitter_hastags(content): @create_link
tags = getattr(content, 'tags', []) def create_link_diaspora(title, url, content):
hashtags = ','.join((tag.slug for tag in tags)) return f"https://sharetodiaspora.github.io/?title={title}&url={url}"
return '' if not hashtags else '&hashtags=%s' % hashtags
def twitter_via(content): @create_link
twitter_username = content.settings.get('TWITTER_USERNAME', '') def create_link_facebook(title, url, content):
return '' if not twitter_username else '&via=%s' % twitter_username 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): if isinstance(content, contents.Static):
return return
title = article_title(content) main_title = BeautifulSoup(content.title, "html.parser").get_text().strip()
url = article_url(content)
summary = article_summary(content)
hastags = twitter_hastags(content)
via = twitter_via(content)
mail_link = 'mailto:?subject=%s&amp;body=%s' % (title, url) try:
diaspora_link = 'https://sharetodiaspora.github.io/?title=%s&url=%s' % ( sub_title = (
title, url) " " + BeautifulSoup(content.subtitle, "html.parser").get_text().strip()
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
) )
reddit_link = 'https://www.reddit.com/submit?url=%s&title=%s' % ( except AttributeError:
url, title) sub_title = ""
content.share_post = { title = quote(f"{main_title}{sub_title}".encode("utf-8"))
'diaspora': diaspora_link,
'twitter': twitter_link, site_url = content.settings["SITEURL"]
'facebook': facebook_link, url = quote(f"{site_url}/{content.url}".encode("utf-8"))
'linkedin': linkedin_link,
'hacker-news': hackernews_link, content.share_post = {}
'email': mail_link, for func in _create_link_functions:
'reddit': reddit_link, key = func.__name__.replace("create_link_", "").replace("_", "-")
} content.share_post[key] = func(title, url, content)
def run_plugin(generators): def run_plugin(generators):
for generator in generators: for generator in generators:
if isinstance(generator, ArticlesGenerator): if isinstance(generator, ArticlesGenerator):
for article in generator.articles: for article in generator.articles:
share_post(article) create_share_links(article)
for translation in article.translations: for translation in article.translations:
share_post(translation) create_share_links(translation)
elif isinstance(generator, PagesGenerator): elif isinstance(generator, PagesGenerator):
for page in generator.pages: for page in generator.pages:
share_post(page) create_share_links(page)
def register(): def register():
try:
signals.all_generators_finalized.connect(run_plugin) 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