diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..c2e5ca8 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: justinmayer +liberapay: pelican diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c5b40b3..ba41c23 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,98 +7,89 @@ env: jobs: test: - name: Test - ${{ matrix.python-version }} + name: Test - Python ${{ matrix.python-version }} runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} & PDM + uses: pdm-project/setup-pdm@v4 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 + cache: true + cache-dependency-path: ./pyproject.toml + - name: Install dependencies + run: pdm install + + - name: Run tests + run: pdm 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 + - uses: actions/checkout@v4 + - name: Validate links in Markdown files + uses: JustinBeckwith/linkinator-action@v1 + with: + retry: true + + - name: Set up Python & PDM + uses: pdm-project/setup-pdm@v4 + with: + python-version: "3.10" + + - name: Install dependencies + run: pdm install + + - name: Run linters + run: pdm run invoke lint --diff deploy: name: Deploy environment: Deployment needs: [test, lint] runs-on: ubuntu-latest - if: ${{ github.ref=='refs/heads/main' && github.event_name!='pull_request' }} + if: github.ref=='refs/heads/main' && github.event_name!='pull_request' + + permissions: + contents: write + id-token: write steps: - - uses: actions/checkout@v2 - - name: Setup Python - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 with: - python-version: "3.x" + token: ${{ secrets.GH_TOKEN }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + - 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)" + python -m pip install autopub httpx + python -m pip install https://github.com/scikit-build/github-release/archive/master.zip + autopub check + - name: Publish - if: ${{ steps.check_release.outputs.release=='' }} + if: ${{ steps.check_release.outputs.autopub_release=='true' }} 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 build autopub githubrelease - poetry publish -u __token__ -p $PYPI_PASSWORD + + - name: Upload package to PyPI + if: ${{ steps.check_release.outputs.autopub_release=='true' }} + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.gitignore b/.gitignore index c04bc49..12568d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -poetry.lock +.pdm-python +pdm.lock diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3338bab..9cdbf84 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,31 +1,28 @@ +--- +ci: + autoupdate_schedule: quarterly + # 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 + rev: v4.6.0 hooks: - id: check-added-large-files - id: check-ast - id: check-case-conflict + - id: check-docstring-first + - id: check-merge-conflict - id: check-toml - id: check-yaml - id: debug-statements - id: detect-private-key - id: end-of-file-fixer + - id: forbid-new-submodules - id: trailing-whitespace - - repo: https://github.com/psf/black - rev: 19.10b0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.0 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 + - id: ruff + - id: ruff-format + args: ["--check"] diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bb40ec..a7c7fc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,30 @@ CHANGELOG ========= +1.2.1 - 2025-03-23 +------------------ + +Add title to Bluesky + +Contributed by [Daniel Lemos](https://github.com/xspager) via [PR #34](https://github.com/pelican-plugins/share-post/pull/34/) + + +1.2.0 - 2025-01-29 +------------------ + +Add Bluesky support + +Contributed by [Leonardo Giordani](https://github.com/lgiordani) via [PR #33](https://github.com/pelican-plugins/share-post/pull/33/) + + +1.1.0 - 2023-08-31 +------------------ + +Add Mastodon share link + 1.0.0 - 2021-03-19 ------------------ Initial release as namespace plugin [Justin Mayer](https://github.com/justinmayer) [PR #1](https://github.com/pelican-plugins/share-post/pull/1/) - - diff --git a/README.md b/README.md index 926e737..8e26419 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # Share Post: A Plugin for Pelican -[![Build Status](https://img.shields.io/github/workflow/status/pelican-plugins/share-post/build)](https://github.com/pelican-plugins/share-post/actions) +[![Build Status](https://img.shields.io/github/actions/workflow/status/pelican-plugins/share-post/main.yml?branch=main)](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/) +[![Downloads](https://img.shields.io/pypi/dm/pelican-share-post)](https://pypi.org/project/pelican-share-post/) ![License](https://img.shields.io/pypi/l/pelican-share-post?color=blue) 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. @@ -18,43 +19,51 @@ This plugin can be installed via: python -m pip install pelican-share-post +As long as you have not explicitly added a `PLUGINS` setting to your Pelican settings file, then the newly-installed plugin should be automatically detected and enabled. Otherwise, you must add `share_post` to your existing `PLUGINS` list. For more information, please see the [How to Use Plugins](https://docs.getpelican.com/en/latest/plugins.html#how-to-use-plugins) documentation. + Usage ----- 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: -``` python +```python article.share_post = { - "facebook": "", "email": "", - "twitter": "", "diaspora": "", - "linkedin": "", + "facebook": "", "hacker-news": "", + "linkedin": "", + "mastodon": "", "reddit": "", + "twitter": "", + "bluesky": "", } ``` You can then access those variables in your template. For example: -``` html+jinja +```html+jinja {% if article.share_post and article.status != 'draft' %}

Share on: - Diaspora* + Email ❄ - Twitter + DiasporaFacebook ❄ + Hacker News + ❄ LinkedIn ❄ - HackerNews - ❄ - Email + MastodonReddit + ❄ + Twitter + ❄ + Bluesky

{% endif %} @@ -77,7 +86,8 @@ 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) +* [Leonardo Giordani](https://github.com/lgiordani) +* [Maurizio Paglia](https://github.com/mpaglia0) License diff --git a/pelican/plugins/share_post/__init__.py b/pelican/plugins/share_post/__init__.py index 4185ce8..40c66ed 100644 --- a/pelican/plugins/share_post/__init__.py +++ b/pelican/plugins/share_post/__init__.py @@ -1 +1 @@ -from .share_post import * # noqa +from .share_post import * # noqa: F403,PGH004,RUF100 diff --git a/pelican/plugins/share_post/share_post.py b/pelican/plugins/share_post/share_post.py index f30b088..2f829c1 100644 --- a/pelican/plugins/share_post/share_post.py +++ b/pelican/plugins/share_post/share_post.py @@ -1,6 +1,4 @@ -""" -Share Post -========== +"""Share Post plugin for Pelican. This plugin was originally created by Talha Mansoor @@ -21,6 +19,8 @@ from pelican.generators import ArticlesGenerator, PagesGenerator _create_link_functions = [] +newline = "%0D%0A" + # Use this decorator to mark a function as # a link creator. The function's prototype shall be @@ -55,6 +55,17 @@ def create_link_facebook(title, url, content): return f"https://www.facebook.com/sharer/sharer.php?u={url}" +@create_link +def create_link_mastodon(title, url, content): + hashtags = "" + + if hasattr(content, "tags"): + new_taglist = [f"%23{str(i).replace(' ', '')}" for i in content.tags] + hashtags = " ".join(new_taglist) + + return f"https://toot.kytta.dev/?text={title}{newline}{url}{newline}{hashtags}" + + @create_link def create_link_twitter(title, url, content): twitter_username = content.settings.get("TWITTER_USERNAME", "") @@ -85,6 +96,12 @@ def create_link_linkedin(title, url, content): ) +@create_link +def create_link_bluesky(title, url, content): + # https://docs.bsky.app/docs/advanced-guides/intent-links + return f"https://bsky.app/intent/compose?text={title}%20{url}" + + def create_share_links(content): if isinstance(content, contents.Static): return @@ -98,10 +115,10 @@ def create_share_links(content): except AttributeError: sub_title = "" - title = quote(f"{main_title}{sub_title}".encode("utf-8")) + title = quote(f"{main_title}{sub_title}".encode()) site_url = content.settings["SITEURL"] - url = quote(f"{site_url}/{content.url}".encode("utf-8")) + url = quote(f"{site_url}/{content.url}".encode()) content.share_post = {} for func in _create_link_functions: diff --git a/pelican/plugins/share_post/test_share_post.py b/pelican/plugins/share_post/test_share_post.py index a35fc59..5ff33f9 100644 --- a/pelican/plugins/share_post/test_share_post.py +++ b/pelican/plugins/share_post/test_share_post.py @@ -1,7 +1,5 @@ import os -from share_post import run_plugin - from pelican.generators import ArticlesGenerator from pelican.tests.support import get_context, get_settings @@ -26,7 +24,7 @@ def test_share_post(tmp_folder): ) generator.generate_context() - run_plugin([generator]) + share_post.run_plugin([generator]) share_links = generator.articles[0].share_post @@ -63,3 +61,8 @@ def test_share_post(tmp_folder): share_links["reddit"] == "https://www.reddit.com/submit?url=/test-post.html&title=Test%20post" ) + + assert ( + share_links["bluesky"] + == "https://bsky.app/intent/compose?text=Test%20post%20/test-post.html" + ) diff --git a/pyproject.toml b/pyproject.toml index 920a711..90ed39b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,73 +1,109 @@ -[tool.poetry] +[project] name = "pelican-share-post" -version = "1.0.0" +version = "1.2.1" description = "A Pelican plugin to create share URLs of article" -authors = ["Talha Mansoor "] -license = "MIT" +authors = [{name = "Talha Mansoor", email = "talha131@gmail.com"}, {name = "Justin Mayer", email = "entroP@gmail.com"}] +license = {text = "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", + "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Internet :: WWW/HTTP", "Topic :: Software Development :: Libraries :: Python Modules", ] +requires-python = ">=3.8.1,<4.0" +dependencies = [ + "pelican>=4.5", + "beautifulsoup4>=4.9.3", +] -[tool.poetry.urls] -"Funding" = "https://donate.getpelican.com/" +[project.urls] +Homepage = "https://github.com/pelican-plugins/share-post" "Issue Tracker" = "https://github.com/pelican-plugins/share-post/issues" +Funding = "https://donate.getpelican.com/" -[tool.poetry.dependencies] -python = "^3.6" -pelican = "^4.5" -markdown = {version = "^3.2.2", optional = true} -beautifulsoup4 = "^4.9.3" +[project.optional-dependencies] +markdown = ["markdown>=3.4"] -[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.pdm] -[tool.poetry.extras] -markdown = ["markdown"] +[tool.pdm.dev-dependencies] +lint = [ + "invoke>=2.2", + "ruff>=0.5.0,<0.6.0" +] +test = [ + "markdown>=3.4", + "pytest>=7.0", + "pytest-cov>=4.0", + "pytest-sugar>=1.0", +] + +[tool.pdm.build] +source-includes = [ + "CHANGELOG.md", + "CONTRIBUTING.md", +] +includes = ["pelican/"] +excludes = ["**/.DS_Store", "**/test_data/**", "tasks.py"] [tool.autopub] project-name = "Share Post" git-username = "botpub" -git-email = "botpub@autopub.rocks" +git-email = "52496925+botpub@users.noreply.github.com" append-github-contributor = true -[tool.isort] -# Maintain compatibility with Black -profile = "black" -multi_line_output = 3 +[tool.ruff.lint] +select = [ + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "C4", # flake8-comprehensions + "D", # pydocstyle + "E", # pycodestyle + "F", # pyflakes + "I", # isort + "ICN", # flake8-import-conventions + "ISC", # flake8-implicit-str-concat + "PGH", # pygrep-hooks + "PL", # pylint + "RET", # flake8-return + "RUF", # ruff-specific rules + "SIM", # flake8-simplify + "T10", # flake8-debugger + "T20", # flake8-print + "TID", # flake8-tidy-imports + "TRY", # tryceratops + "UP", # pyupgrade + "W", # pycodestyle + "YTT", # flake8-2020 +] -# Sort imports within their section independent of the import type -force_sort_within_sections = true +ignore = [ + "D100", # missing docstring in public module + "D103", # missing docstring in public method + "D104", # missing docstring in public package + "D203", # blank line before class docstring + "D213", # multi-line docstring summary should start at the second line + "ISC001", # disabled so `ruff format` works without warning +] -# Designate "pelican" as separate import section -known_pelican = "pelican" -sections = "FUTURE,STDLIB,THIRDPARTY,PELICAN,FIRSTPARTY,LOCALFOLDER" +[tool.ruff.lint.isort] +combine-as-imports = true +force-sort-within-sections = true +known-first-party = ["pelican"] [build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" +requires = ["pdm-backend"] +build-backend = "pdm.backend" diff --git a/tasks.py b/tasks.py index 0377b73..3a8b167 100644 --- a/tasks.py +++ b/tasks.py @@ -1,79 +1,101 @@ +from inspect import cleandoc +import logging import os from pathlib import Path from shutil import which from invoke import task +logger = logging.getLogger(__name__) + 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_PATH = Path(ACTIVE_VENV) if ACTIVE_VENV else (VENV_HOME.expanduser() / PKG_NAME) VENV = str(VENV_PATH.expanduser()) +BIN_DIR = "bin" if os.name != "nt" else "Scripts" +VENV_BIN = Path(VENV) / Path(BIN_DIR) -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") -) +TOOLS = ("pdm", "pre-commit") +PDM = which("pdm") if which("pdm") else (VENV_BIN / "pdm") +CMD_PREFIX = f"{VENV_BIN}/" if ACTIVE_VENV else f"{PDM} run " +PRECOMMIT = which("pre-commit") if which("pre-commit") else f"{CMD_PREFIX}pre-commit" +PTY = os.name != "nt" @task -def tests(c): - """Run the test suite""" - c.run(f"{VENV}/bin/pytest", pty=True) +def tests(c, deprecations=False): + """Run the test suite, optionally with `--deprecations`.""" + deprecations_flag = "" if deprecations else "-W ignore::DeprecationWarning" + c.run(f"{CMD_PREFIX}pytest {deprecations_flag}", pty=PTY) @task -def black(c, check=False, diff=False): - """Run Black auto-formatter, optionally with --check or --diff""" +def format(c, check=False, diff=False): + """Run Ruff's 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") + c.run( + f"{CMD_PREFIX}ruff format {check_flag} {diff_flag} {PKG_PATH} tasks.py", pty=PTY + ) @task -def isort(c, check=False, diff=False): - check_flag, diff_flag = "", "" - if check: - check_flag = "-c" +def ruff(c, fix=False, diff=False): + """Run Ruff to ensure code meets project standards.""" + diff_flag, fix_flag = "", "" + if fix: + fix_flag = "--fix" if diff: diff_flag = "--diff" - c.run(f"{VENV}/bin/isort {check_flag} {diff_flag} .") + c.run(f"{CMD_PREFIX}ruff check {diff_flag} {fix_flag} .", pty=PTY) @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) +def lint(c, fix=False, diff=False): + """Check code style via linting tools.""" + ruff(c, fix=fix, diff=diff) + format(c, check=(not fix), diff=diff) @task def tools(c): - """Install tools in the virtual environment if not already on PATH""" + """Install development 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}") + logger.info(f"** Installing {tool} **") + c.run(f"{CMD_PREFIX}pip install {tool}") @task def precommit(c): - """Install pre-commit hooks to .git/hooks/pre-commit""" + """Install pre-commit hooks to .git/hooks/pre-commit.""" + logger.info("** Installing pre-commit hooks **") 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) + """Set up the development environment.""" + if which("pdm") or ACTIVE_VENV: + tools(c) + c.run(f"{CMD_PREFIX}python -m pip install --upgrade pip", pty=PTY) + c.run(f"{PDM} update --dev", pty=PTY) + precommit(c) + logger.info("\nDevelopment environment should now be set up and ready!\n") + else: + error_message = """ + PDM is not installed, and there is no active virtual environment available. + You can either manually create and activate a virtual environment, or you can + install PDM via: + + curl -sSL https://raw.githubusercontent.com/pdm-project/pdm/main/install-pdm.py | python3 - + + Once you have taken one of the above two steps, run `invoke setup` again. + """ # noqa: E501 + raise SystemExit(cleandoc(error_message)) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index abbd0dc..0000000 --- a/tox.ini +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -max-line-length = 88 -ignore = E203, W503