From b826c6042f8e6a321fba125fdb3e09212699bdef Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 15 Jan 2025 09:47:42 +0100 Subject: [PATCH 1/6] Use PDM+Ruff instead of Poetry+Black+Flake8+isort --- .gitignore | 3 +- .pre-commit-config.yaml | 28 ++------ pyproject.toml | 137 +++++++++++++++++++++++++++------------- tox.ini | 3 - 4 files changed, 102 insertions(+), 69 deletions(-) delete mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index 5463c9d..068ef7c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vscode/ *__pycache__ -poetry.lock +.pdm-python +pdm.lock diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b296b72..7e149dd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ ci: # See https://pre-commit.com/hooks.html for info on hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v5.0.0 hooks: - id: check-added-large-files - id: check-ast @@ -20,25 +20,11 @@ repos: - id: forbid-new-submodules - id: trailing-whitespace - - repo: https://github.com/psf/black - rev: 23.7.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.9.1 hooks: - - id: black + - id: ruff + - id: ruff-format + args: ["--check"] - - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 - hooks: - - id: flake8 - args: [--max-line-length=88] - language_version: python3 - - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort - - - repo: https://github.com/asottile/pyupgrade - rev: v3.9.0 - hooks: - - id: pyupgrade - args: [--py38-plus] +exclude: "tests/" diff --git a/pyproject.toml b/pyproject.toml index 2f026ed..0090325 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,51 +1,63 @@ -[tool.poetry] +[project] name = "pelican-seo" version = "1.2.2" -description = "Pelican plugin to improve SEO (Search Engine Optimization) to reach top positions on search engines." -authors = ["Maëva Brunelles "] -license = "AGPL-3.0" +description = "Pelican plugin to improve search engine optimization (SEO)" +authors = [{name = "Maëva Brunelles", email = "contact@tremacorp.fr"}, {name = "Justin Mayer", email = "entroP@gmail.com"}] +license = {text = "AGPL-3.0"} readme = "README.md" keywords = ["pelican", "plugin", "seo", "search", "optimization"] -repository = "https://github.com/pelican-plugins/seo" -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 :: GNU Affero General Public License v3", "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP", "Topic :: Software Development :: Libraries :: Python Modules", ] +requires-python = "~=3.9" +dependencies = [ + "pelican>=4.5", + "beautifulsoup4>=4.9", +] -[tool.poetry.urls] -"Funding" = "https://donate.getpelican.com/" +[project.urls] +"Homepage" = "https://github.com/pelican-plugins/seo" "Issue Tracker" = "https://github.com/pelican-plugins/seo/issues" +"Changelog" = "https://github.com/pelican-plugins/seo/blob/main/CHANGELOG.md" +"Funding" = "https://donate.getpelican.com/" -[tool.poetry.dependencies] -python = ">=3.6.2,<4.0" -pelican = ">=4.5" -markdown = {version = ">=3.2", optional = true} -beautifulsoup4 = "^4.9" +[project.optional-dependencies] +markdown = ["markdown>=3.4"] -[tool.poetry.dev-dependencies] -black = {version = "^21.11b0", allow-prereleases = true} -flake8 = "^4.0" -flake8-black = "^0.2" -invoke = "^2.0" -isort = "^5.4" -markdown = "^3.2" -pytest = "^6.0" -pytest-cov = "^3.0" -pytest-sugar = "^0.9" +[dependency-groups] +lint = [ + "invoke>=2.2", + "ruff>=0.9.1,<1.0.0", +] +test = [ + "invoke>=2.2", + "markdown>=3.4", + "pytest>=7.0", + "pytest-cov>=4.0", + "pytest-sugar>=1.0", +] -[tool.poetry.extras] -markdown = ["markdown"] +[tool.pdm.build] +source-includes = [ + "CHANGELOG.md", + "CONTRIBUTING.md", +] +includes = ["pelican/"] +excludes = ["**/.DS_Store", "**/test_data/**", "tasks.py"] [tool.autopub] project-name = "SEO" @@ -53,23 +65,60 @@ git-username = "botpub" git-email = "52496925+botpub@users.noreply.github.com" 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 +[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 +] -# Designate "pelican" and "seo" as separate import sections -known_pelican = "pelican" -known_seo = "seo" -sections = "FUTURE,STDLIB,THIRDPARTY,PELICAN,SEO,FIRSTPARTY,LOCALFOLDER" +ignore = [ + "D100", # missing docstring in public module + "D104", # missing docstring in public package + "D107", # missing docstring in __init__ + "D202", # blank line before function docstring + "D203", # blank line before class docstring + "D205", # blank line summary and description + "D212", # multi-line docstring summary should start at the first line + "D213", # multi-line docstring summary should start at the second line + "D400", # first line should end with a period + "ISC001", # disabled so `ruff format` works without warning + "PLR0913", # too many arguments in function definition + "PLR2004", # magic value used in comparison + "RET504", # unnecessary assignment before return statement + "SIM103", # return condition directly + "SIM110", # loop that can be replaced with `any` builtin + "TRY002", # create your own exception + "TRY003", # long messages outside the exception class +] -known_third_party = [ - "pytest", +exclude = [ + "**/tests/*" ] +[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/tox.ini b/tox.ini deleted file mode 100644 index cadcae0..0000000 --- a/tox.ini +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -max-line-length = 88 -ignore = E203, E501, W503 From 34aca568c5d73d1a14d2dcda0c1495c1ffbfc9e8 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 15 Jan 2025 09:51:33 +0100 Subject: [PATCH 2/6] Apply updated code style configuration to code --- pelican/plugins/seo/__init__.py | 2 +- pelican/plugins/seo/seo.py | 8 +++++--- pelican/plugins/seo/seo_enhancer/__init__.py | 20 +++++++------------ .../seo_enhancer/html_enhancer/__init__.py | 6 +++--- .../html_enhancer/article_schema_creator.py | 4 ++-- .../breadcrumb_schema_creator.py | 8 ++++---- .../html_enhancer/canonical_url_creator.py | 2 +- .../html_enhancer/twitter_cards.py | 7 ++----- .../seo/seo_enhancer/robots_file_creator.py | 2 +- pelican/plugins/seo/seo_report/__init__.py | 5 +---- .../seo/seo_report/seo_analyzer/__init__.py | 2 +- .../seo_analyzer/content_title_analyzer.py | 2 +- .../seo_analyzer/internal_link_analyzer.py | 2 +- .../seo_analyzer/page_description_analyzer.py | 2 +- .../seo_analyzer/page_title_analyzer.py | 2 +- pelican/plugins/seo/settings.py | 4 +--- pelican/plugins/seo/tests/conftest.py | 2 +- .../seo/tests/test_article_schema_creator.py | 2 +- .../tests/test_breadcrumb_schema_creator.py | 2 +- .../seo/tests/test_canonical_url_creator.py | 8 ++++---- .../seo/tests/test_content_title_analyzer.py | 2 +- .../seo/tests/test_internal_link_analyzer.py | 2 +- pelican/plugins/seo/tests/test_open_graph.py | 2 +- .../tests/test_page_description_analyzer.py | 2 +- .../seo/tests/test_page_title_analyzer.py | 2 +- .../seo/tests/test_robots_file_creator.py | 2 +- .../plugins/seo/tests/test_seo_enhancer.py | 2 +- pelican/plugins/seo/tests/test_seo_report.py | 2 +- .../plugins/seo/tests/test_twitter_cards.py | 2 +- 29 files changed, 49 insertions(+), 61 deletions(-) diff --git a/pelican/plugins/seo/__init__.py b/pelican/plugins/seo/__init__.py index 76b6eb7..5b6cc56 100644 --- a/pelican/plugins/seo/__init__.py +++ b/pelican/plugins/seo/__init__.py @@ -1 +1 @@ -from .seo import * # NOQA +from .seo import * # noqa: F403,PGH004,RUF100 diff --git a/pelican/plugins/seo/seo.py b/pelican/plugins/seo/seo.py index 0844952..42b3f6d 100644 --- a/pelican/plugins/seo/seo.py +++ b/pelican/plugins/seo/seo.py @@ -25,7 +25,7 @@ def plugin_initializer(settings): - """Raises if SITEURL parameter is not set in Pelican settings""" + """Raise if SITEURL parameter is not set in Pelican settings.""" if not settings.settings.get("SITEURL"): raise Exception( @@ -37,7 +37,7 @@ def plugin_initializer(settings): def get_plugin_settings(context): - """Get settings in the Pelican configuration file from the given context + """Get settings in the Pelican configuration file from the given context. .. note:: Pelican's settings take precedence over the plugin's default settings. @@ -58,7 +58,8 @@ def get_plugin_settings(context): """ def _get_seo_variables(settings): - """Get variables starting with 'SEO_' & values in the given dictionary + """Get variables starting with 'SEO_' & values in the given dictionary. + :rtype: """ return { @@ -173,6 +174,7 @@ def run_html_enhancer(path, context): def register(): + """Register the plugin.""" signals.initialized.connect(plugin_initializer) diff --git a/pelican/plugins/seo/seo_enhancer/__init__.py b/pelican/plugins/seo/seo_enhancer/__init__.py index 012bb40..98b547c 100644 --- a/pelican/plugins/seo/seo_enhancer/__init__.py +++ b/pelican/plugins/seo/seo_enhancer/__init__.py @@ -1,4 +1,4 @@ -""" Improve SEO technical for each article and page : HTML code and robots.txt file. """ +"""Improve SEO technical for each article and page : HTML code and robots.txt file.""" import json import logging @@ -13,9 +13,7 @@ class SEOEnhancer: - """ - Improve SEO technical for each article and page : HTML code and robots.txt file. - """ + """Improve SEO for each article and page : HTML code and robots.txt file.""" def launch_html_enhancer( self, file, output_path, path, open_graph=False, twitter_cards=False @@ -46,9 +44,9 @@ def launch_html_enhancer( html_enhancements["open_graph"] = html_enhancer.open_graph.create_tags() if twitter_cards: - html_enhancements[ - "twitter_cards" - ] = html_enhancer.twitter_cards.create_tags() + html_enhancements["twitter_cards"] = ( + html_enhancer.twitter_cards.create_tags() + ) return html_enhancements @@ -67,9 +65,7 @@ def populate_robots(self, document): } def generate_robots(self, rules, output_path): - """ - Create robots.txt file, with noindex and disallow rules for each document URL. - """ + """Create robots.txt, with noindex and disallow rules for each document URL.""" if not os.path.isdir(output_path): os.mkdir(output_path) @@ -86,9 +82,7 @@ def generate_robots(self, rules, output_path): logger.info("SEO plugin - SEO Enhancement: robots.txt file created") def add_html_to_file(self, enhancements, path): - """ - Open HTML file, add HTML enhancements with bs4 and create the new HTML files. - """ + """Open HTML file, add enhancements with bs4 and create the new HTML files.""" with open(path, encoding="utf8") as html_file: html_content = html_file.read() diff --git a/pelican/plugins/seo/seo_enhancer/html_enhancer/__init__.py b/pelican/plugins/seo/seo_enhancer/html_enhancer/__init__.py index 30fa1d5..6051393 100644 --- a/pelican/plugins/seo/seo_enhancer/html_enhancer/__init__.py +++ b/pelican/plugins/seo/seo_enhancer/html_enhancer/__init__.py @@ -1,4 +1,4 @@ -""" HTML Enhancer : get instances of HTML enhancements. """ +"""HTML Enhancer : get instances of HTML enhancements.""" from pelican.contents import Article, Page @@ -19,7 +19,7 @@ def __init__(self, file, output_path, path, open_graph=False, twitter_cards=Fals elif isinstance(file, Page): _file_type = "page" - _settings = getattr(file, "settings") + _settings = file.settings _author = getattr(file, "author", None) _date = getattr(file, "date", None) _title = getattr(file, "title", None) @@ -48,7 +48,7 @@ def __init__(self, file, output_path, path, open_graph=False, twitter_cards=Fals ) else: save_as = _metadata.get("save_as") - _fileurl = save_as if save_as else getattr(file, "url") + _fileurl = save_as if save_as else file.url self.canonical_link = CanonicalURLCreator( siteurl=_settings.get("SITEURL"), fileurl=_fileurl, diff --git a/pelican/plugins/seo/seo_enhancer/html_enhancer/article_schema_creator.py b/pelican/plugins/seo/seo_enhancer/html_enhancer/article_schema_creator.py index 2c3d786..ee0f9d5 100644 --- a/pelican/plugins/seo/seo_enhancer/html_enhancer/article_schema_creator.py +++ b/pelican/plugins/seo/seo_enhancer/html_enhancer/article_schema_creator.py @@ -31,8 +31,8 @@ def _convert_date(self, date): return date_time.strftime("%Y-%m-%d %H:%M") def create_schema(self): - """ - Create Article schema. + """Create Article schema. + Schema : { "@context": "https://schema.org", "@type": "Article", diff --git a/pelican/plugins/seo/seo_enhancer/html_enhancer/breadcrumb_schema_creator.py b/pelican/plugins/seo/seo_enhancer/html_enhancer/breadcrumb_schema_creator.py index 022e11b..0baf988 100644 --- a/pelican/plugins/seo/seo_enhancer/html_enhancer/breadcrumb_schema_creator.py +++ b/pelican/plugins/seo/seo_enhancer/html_enhancer/breadcrumb_schema_creator.py @@ -34,8 +34,8 @@ def _extract_file_path_from_path(self) -> tuple: return file_path.parts def _create_paths(self): - """ - Build all paths for the breadcrumb. + """Build all paths for the breadcrumb. + Example with a file_path == ("category", "file.html") Position begins at 2, as number 1 is dedicated to the index page. Returns list of dicts : @@ -58,7 +58,6 @@ def _create_paths(self): position = 2 for item in range(1, len(file_path) + 1): - name = file_path[item - 1] name = name.replace("-", " ").capitalize() if name.endswith(".html"): @@ -74,7 +73,8 @@ def _create_paths(self): return breadcrumb_paths def create_schema(self): - """ + """Create the schema. + Schema = { "@context": "https://schema.org", "@type": "BreadcrumbList", diff --git a/pelican/plugins/seo/seo_enhancer/html_enhancer/canonical_url_creator.py b/pelican/plugins/seo/seo_enhancer/html_enhancer/canonical_url_creator.py index dc4c8e6..3628bf0 100644 --- a/pelican/plugins/seo/seo_enhancer/html_enhancer/canonical_url_creator.py +++ b/pelican/plugins/seo/seo_enhancer/html_enhancer/canonical_url_creator.py @@ -1,4 +1,4 @@ -""" Canonical URL creator. """ +"""Canonical URL creator.""" import os diff --git a/pelican/plugins/seo/seo_enhancer/html_enhancer/twitter_cards.py b/pelican/plugins/seo/seo_enhancer/html_enhancer/twitter_cards.py index 56efef9..bf7f307 100644 --- a/pelican/plugins/seo/seo_enhancer/html_enhancer/twitter_cards.py +++ b/pelican/plugins/seo/seo_enhancer/html_enhancer/twitter_cards.py @@ -1,6 +1,5 @@ class TwitterCards: - """ - Add specifics Twitter tags according to + """Add specific Twitter tags according to https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/summary. Missing tags are filled by Open Graph feature as Twitter falls back on it. """ @@ -9,9 +8,7 @@ def __init__(self, tw_account) -> None: self.tw_account = tw_account def create_tags(self) -> dict: - """ - Compute all Twitter Cards elements and return them in a ready-to-use dict. - """ + """Compute all Twitter Cards elements and return them in a ready-to-use dict.""" twitter_tags = {} twitter_tags["card"] = "summary" diff --git a/pelican/plugins/seo/seo_enhancer/robots_file_creator.py b/pelican/plugins/seo/seo_enhancer/robots_file_creator.py index db60745..88f08b0 100644 --- a/pelican/plugins/seo/seo_enhancer/robots_file_creator.py +++ b/pelican/plugins/seo/seo_enhancer/robots_file_creator.py @@ -1,4 +1,4 @@ -""" Return elements to build the robots.txt file. """ +"""Return elements to build the robots.txt file.""" class RobotsFileCreator: diff --git a/pelican/plugins/seo/seo_report/__init__.py b/pelican/plugins/seo/seo_report/__init__.py index d6fde5f..e406eb3 100644 --- a/pelican/plugins/seo/seo_report/__init__.py +++ b/pelican/plugins/seo/seo_report/__init__.py @@ -1,6 +1,4 @@ -""" -Generate a SEO report by calling SEO analyzers for each content. -""" +"""Generate a SEO report by calling SEO analyzers for each content.""" import datetime import logging @@ -237,7 +235,6 @@ def generate(self, site_name, documents_analysis): seo_reports = [] for document_analysis in documents_analysis: - document_report = self._launch_report(document_analysis) documents_reports = { diff --git a/pelican/plugins/seo/seo_report/seo_analyzer/__init__.py b/pelican/plugins/seo/seo_report/seo_analyzer/__init__.py index 6b00e73..7156467 100644 --- a/pelican/plugins/seo/seo_report/seo_analyzer/__init__.py +++ b/pelican/plugins/seo/seo_report/seo_analyzer/__init__.py @@ -1,4 +1,4 @@ -""" Launch micro SEO analysis. """ +"""Launch micro SEO analysis.""" from .content_title_analyzer import ContentTitleAnalyzer from .internal_link_analyzer import InternalLinkAnalyzer diff --git a/pelican/plugins/seo/seo_report/seo_analyzer/content_title_analyzer.py b/pelican/plugins/seo/seo_report/seo_analyzer/content_title_analyzer.py index 81e7413..11f051b 100644 --- a/pelican/plugins/seo/seo_report/seo_analyzer/content_title_analyzer.py +++ b/pelican/plugins/seo/seo_report/seo_analyzer/content_title_analyzer.py @@ -1,4 +1,4 @@ -""" Analyze the content title. """ +"""Analyze the content title.""" from bs4 import BeautifulSoup diff --git a/pelican/plugins/seo/seo_report/seo_analyzer/internal_link_analyzer.py b/pelican/plugins/seo/seo_report/seo_analyzer/internal_link_analyzer.py index f4715ec..b004b34 100644 --- a/pelican/plugins/seo/seo_report/seo_analyzer/internal_link_analyzer.py +++ b/pelican/plugins/seo/seo_report/seo_analyzer/internal_link_analyzer.py @@ -1,4 +1,4 @@ -""" Analyze the internal link of an article. """ +"""Analyze the internal link of an article.""" from bs4 import BeautifulSoup diff --git a/pelican/plugins/seo/seo_report/seo_analyzer/page_description_analyzer.py b/pelican/plugins/seo/seo_report/seo_analyzer/page_description_analyzer.py index 115f5b7..de1c5d4 100644 --- a/pelican/plugins/seo/seo_report/seo_analyzer/page_description_analyzer.py +++ b/pelican/plugins/seo/seo_report/seo_analyzer/page_description_analyzer.py @@ -1,4 +1,4 @@ -""" Analyze the page description. """ +"""Analyze the page description.""" class PageDescriptionAnalyzer: diff --git a/pelican/plugins/seo/seo_report/seo_analyzer/page_title_analyzer.py b/pelican/plugins/seo/seo_report/seo_analyzer/page_title_analyzer.py index 52858cc..55d59c7 100644 --- a/pelican/plugins/seo/seo_report/seo_analyzer/page_title_analyzer.py +++ b/pelican/plugins/seo/seo_report/seo_analyzer/page_title_analyzer.py @@ -1,4 +1,4 @@ -""" Analyze the page title. """ +"""Analyze the page title.""" class PageTitleAnalyzer: diff --git a/pelican/plugins/seo/settings.py b/pelican/plugins/seo/settings.py index fc824e5..d1d7c0b 100644 --- a/pelican/plugins/seo/settings.py +++ b/pelican/plugins/seo/settings.py @@ -1,6 +1,4 @@ -""" -Plugin settings : activate or deactivate SEO features -""" +"""Plugin settings : activate or deactivate SEO features.""" SEO_REPORT = True SEO_ENHANCER = False diff --git a/pelican/plugins/seo/tests/conftest.py b/pelican/plugins/seo/tests/conftest.py index 71ecbc0..ddae16b 100644 --- a/pelican/plugins/seo/tests/conftest.py +++ b/pelican/plugins/seo/tests/conftest.py @@ -1,4 +1,4 @@ -""" Mocks Pelican objects required for the units tests. """ +"""Mocks Pelican objects required for the units tests.""" import pytest diff --git a/pelican/plugins/seo/tests/test_article_schema_creator.py b/pelican/plugins/seo/tests/test_article_schema_creator.py index a0c717e..46144d0 100644 --- a/pelican/plugins/seo/tests/test_article_schema_creator.py +++ b/pelican/plugins/seo/tests/test_article_schema_creator.py @@ -1,4 +1,4 @@ -""" Units tests for Article Schema Creator. """ +"""Units tests for Article Schema Creator.""" from seo.seo_enhancer.html_enhancer import ArticleSchemaCreator diff --git a/pelican/plugins/seo/tests/test_breadcrumb_schema_creator.py b/pelican/plugins/seo/tests/test_breadcrumb_schema_creator.py index 62ac5d4..0625c07 100644 --- a/pelican/plugins/seo/tests/test_breadcrumb_schema_creator.py +++ b/pelican/plugins/seo/tests/test_breadcrumb_schema_creator.py @@ -1,4 +1,4 @@ -""" Units tests for Breadcrumb Schema Creator. """ +"""Units tests for Breadcrumb Schema Creator.""" import pytest diff --git a/pelican/plugins/seo/tests/test_canonical_url_creator.py b/pelican/plugins/seo/tests/test_canonical_url_creator.py index 995e638..cc280a8 100644 --- a/pelican/plugins/seo/tests/test_canonical_url_creator.py +++ b/pelican/plugins/seo/tests/test_canonical_url_creator.py @@ -1,4 +1,4 @@ -""" Units tests for Canonical URL Creator. """ +"""Units tests for Canonical URL Creator.""" import pytest @@ -57,9 +57,9 @@ def test_create_url_with_external_canonical_and_save_as_metadata( ): """Test that canonical URL is build with :external_canonical: metadata value, even when :save_as: metadata is filled.""" - fake_article.metadata[ - "external_canonical" - ] = "https://www.example.com/external_canonical_article.html" + fake_article.metadata["external_canonical"] = ( + "https://www.example.com/external_canonical_article.html" + ) fake_article.metadata["save_as"] = "custom_file_name.html" html_enhancements = fake_seo_enhancer.launch_html_enhancer( diff --git a/pelican/plugins/seo/tests/test_content_title_analyzer.py b/pelican/plugins/seo/tests/test_content_title_analyzer.py index cc497d4..2f04e77 100644 --- a/pelican/plugins/seo/tests/test_content_title_analyzer.py +++ b/pelican/plugins/seo/tests/test_content_title_analyzer.py @@ -1,4 +1,4 @@ -""" Unit tests for Content Title Analyzer. """ +"""Unit tests for Content Title Analyzer.""" from seo.seo_report.seo_analyzer import ContentTitleAnalyzer diff --git a/pelican/plugins/seo/tests/test_internal_link_analyzer.py b/pelican/plugins/seo/tests/test_internal_link_analyzer.py index 99001c8..02efd3f 100644 --- a/pelican/plugins/seo/tests/test_internal_link_analyzer.py +++ b/pelican/plugins/seo/tests/test_internal_link_analyzer.py @@ -1,4 +1,4 @@ -""" Unit tests for Internal Link Analyzer. """ +"""Unit tests for Internal Link Analyzer.""" from seo.seo_report.seo_analyzer import InternalLinkAnalyzer diff --git a/pelican/plugins/seo/tests/test_open_graph.py b/pelican/plugins/seo/tests/test_open_graph.py index 561bac1..b103cae 100644 --- a/pelican/plugins/seo/tests/test_open_graph.py +++ b/pelican/plugins/seo/tests/test_open_graph.py @@ -1,4 +1,4 @@ -""" Units tests for OpenGraph. """ +"""Units tests for OpenGraph.""" import locale diff --git a/pelican/plugins/seo/tests/test_page_description_analyzer.py b/pelican/plugins/seo/tests/test_page_description_analyzer.py index 9b76490..d6f312b 100644 --- a/pelican/plugins/seo/tests/test_page_description_analyzer.py +++ b/pelican/plugins/seo/tests/test_page_description_analyzer.py @@ -1,4 +1,4 @@ -""" Unit tests for Page Description Analyzer. """ +"""Unit tests for Page Description Analyzer.""" from seo.seo_report.seo_analyzer import PageDescriptionAnalyzer diff --git a/pelican/plugins/seo/tests/test_page_title_analyzer.py b/pelican/plugins/seo/tests/test_page_title_analyzer.py index 7314304..334ee03 100644 --- a/pelican/plugins/seo/tests/test_page_title_analyzer.py +++ b/pelican/plugins/seo/tests/test_page_title_analyzer.py @@ -1,4 +1,4 @@ -""" Unit tests for Page Title Analyzer. """ +"""Unit tests for Page Title Analyzer.""" from seo.seo_report.seo_analyzer import PageTitleAnalyzer diff --git a/pelican/plugins/seo/tests/test_robots_file_creator.py b/pelican/plugins/seo/tests/test_robots_file_creator.py index d0c6a5f..2037f3d 100644 --- a/pelican/plugins/seo/tests/test_robots_file_creator.py +++ b/pelican/plugins/seo/tests/test_robots_file_creator.py @@ -1,4 +1,4 @@ -""" Units tests for Robots File Creator. """ +"""Units tests for Robots File Creator.""" from seo.seo_enhancer.robots_file_creator import RobotsFileCreator diff --git a/pelican/plugins/seo/tests/test_seo_enhancer.py b/pelican/plugins/seo/tests/test_seo_enhancer.py index fc76180..316e5bd 100644 --- a/pelican/plugins/seo/tests/test_seo_enhancer.py +++ b/pelican/plugins/seo/tests/test_seo_enhancer.py @@ -1,4 +1,4 @@ -""" Units tests for SEO Enhancer. """ +"""Units tests for SEO Enhancer.""" from unittest.mock import mock_open, patch diff --git a/pelican/plugins/seo/tests/test_seo_report.py b/pelican/plugins/seo/tests/test_seo_report.py index 5291810..4832b70 100644 --- a/pelican/plugins/seo/tests/test_seo_report.py +++ b/pelican/plugins/seo/tests/test_seo_report.py @@ -1,4 +1,4 @@ -""" Unit tests for SEO Report. """ +"""Unit tests for SEO Report.""" from unittest.mock import mock_open, patch diff --git a/pelican/plugins/seo/tests/test_twitter_cards.py b/pelican/plugins/seo/tests/test_twitter_cards.py index 9bd1e7a..56f325b 100644 --- a/pelican/plugins/seo/tests/test_twitter_cards.py +++ b/pelican/plugins/seo/tests/test_twitter_cards.py @@ -1,4 +1,4 @@ -""" Units tests for Twitter Cards. """ +"""Units tests for Twitter Cards.""" from seo.seo_enhancer.html_enhancer import TwitterCards From 1bb31abbfd9777de87910a39b3718275e55749cb Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 15 Jan 2025 10:00:01 +0100 Subject: [PATCH 3/6] ci: Use PDM+Ruff in GitHub Actions workflow --- .github/FUNDING.yml | 2 + .github/workflows/main.yml | 110 +++++++++++++++++-------------------- 2 files changed, 53 insertions(+), 59 deletions(-) create mode 100644 .github/FUNDING.yml 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 07bb016..0ae4def 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,103 +5,95 @@ on: [push, pull_request] env: PYTEST_ADDOPTS: "--color=yes" +permissions: + contents: read + jobs: test: - name: Test - ${{ matrix.python-version }} + name: Test - Python ${{ matrix.python-version }} runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8, 3.9] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 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 + - uses: actions/checkout@v4 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 + persist-credentials: false + + - name: Set up Python ${{ matrix.python-version }} & PDM + uses: pdm-project/setup-pdm@v4 with: - path: ~/.cache/pypoetry/virtualenvs - key: poetry-${{ hashFiles('**/poetry.lock') }} + python-version: ${{ matrix.python-version }} + cache: true + cache-dependency-path: ./pyproject.toml + - name: Install dependencies - run: | - poetry run pip install --upgrade pip - poetry install - - name: Run tests - run: poetry run invoke tests + run: pdm install + - name: Run tests + run: pdm run invoke tests lint: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Validate links in Markdown files uses: JustinBeckwith/linkinator-action@v1 with: retry: true - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.7 - - name: Set Poetry cache - uses: actions/cache@v2 - id: poetry-cache + + - name: Set up Python & PDM + uses: pdm-project/setup-pdm@v4 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 + python-version: "3.10" + - name: Install dependencies - run: | - poetry run pip install --upgrade pip - poetry install - - name: Run linters - run: poetry run invoke lint + 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 + + - name: Set up Python + uses: actions/setup-python@v5 with: - python-version: 3.7 + 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[github] + 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 }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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 From 27b03f544565159723a46b20a7141e06637ce513 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 15 Jan 2025 10:00:57 +0100 Subject: [PATCH 4/6] Use PDM+Ruff in Invoke tasks --- tasks.py | 104 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/tasks.py b/tasks.py index d94da36..201972b 100644 --- a/tasks.py +++ b/tasks.py @@ -1,92 +1,106 @@ +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 = "seo" PKG_PATH = Path(f"pelican/plugins/{PKG_NAME}") -BIN_DIR = "bin" if os.name != "nt" else "Scripts" + 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 = ["black", "flake8", "isort", "poetry", "pre-commit", "pytest"] - - -def find_tool(tool): - """Locate a tool and return its path, preferring the local virtual environment""" - t = VENV_BIN / tool - t = t if t.is_file() else which(tool) - return t +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""" - pytest = find_tool("pytest") - c.run(f"{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 and/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" - black = find_tool("black") - c.run(f"{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): - """Run isort import sorter, optionally with -c and/or --diff""" - check_flag, diff_flag = "", "" - if check: - check_flag = "-c" +def ruff(c, concise=False, fix=False, diff=False): + """Run Ruff to ensure code meets project standards.""" + concise_flag, fix_flag, diff_flag = "", "", "" + if concise: + concise_flag = "--output-format=concise" + if fix: + fix_flag = "--fix" if diff: diff_flag = "--diff" - isort = find_tool("isort") - c.run(f"{isort} {check_flag} {diff_flag} .") - - -@task -def flake8(c): - flake8 = find_tool("flake8") - c.run(f"{flake8} {PKG_PATH} tasks.py") + c.run( + f"{CMD_PREFIX}ruff check {concise_flag} {diff_flag} {fix_flag} {PKG_PATH}", + pty=PTY, + ) @task -def lint(c, diff=False): +def lint(c, concise=False, fix=False, diff=False): """Check code style via linting tools.""" - isort(c, check=True, diff=diff) - black(c, check=True, diff=diff) - flake8(c) + ruff(c, concise=concise, 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""" - precommit = find_tool("pre-commit") - c.run(f"{precommit} install") + """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) - poetry = find_tool("poetry") - 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)) From 4d3c46c17384c8e27bd5364cd468c5621d4889cf Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 15 Jan 2025 10:03:45 +0100 Subject: [PATCH 5/6] docs: Clarify plugin installation instructions --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index c2ca3d9..35a1d15 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![Build Status](https://img.shields.io/github/actions/workflow/status/pelican-plugins/seo/main.yml?branch=main)](https://github.com/pelican-plugins/seo/actions) [![PyPI Version](https://img.shields.io/pypi/v/pelican-seo)](https://pypi.org/project/pelican-seo/) +[![Downloads](https://img.shields.io/pypi/dm/pelican-seo)](https://pypi.org/project/pelican-seo/) ![License](https://img.shields.io/pypi/l/pelican-seo?color=blue) This plugin helps you improve your Pelican site SEO (Search Engine Optimization) to reach the top positions on search engines. To see what this SEO plugin can do for you, visit the [Usage](#usage) section. @@ -31,6 +32,8 @@ This plugin can be installed via: python -m pip install pelican-seo +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 `seo` 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. + ## Requirements The above installation step should automatically install Beautiful Soup 4, which the SEO plugin requires. The following command will manually install this dependency: @@ -314,3 +317,8 @@ To start contributing to this plugin, review the [Contributing to Pelican][] doc [existing issues]: https://github.com/pelican-plugins/seo/issues [Contributing to Pelican]: https://docs.getpelican.com/en/latest/contribute.html + +License +------- + +This project is licensed under the AGPL-3.0 license. From e71419d134f80bff79a8cbe6c2e82aeb4675757d Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 15 Jan 2025 10:11:40 +0100 Subject: [PATCH 6/6] chore: Link to upstream plugin template via Cruft --- .cruft.json | 28 ++++++++++++++++++++++++++++ tasks.py | 19 ++++++++++++++----- 2 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 .cruft.json diff --git a/.cruft.json b/.cruft.json new file mode 100644 index 0000000..ce0d8f2 --- /dev/null +++ b/.cruft.json @@ -0,0 +1,28 @@ +{ + "template": "https://github.com/getpelican/cookiecutter-pelican-plugin", + "commit": "8a4f543e999c7a2b4ab49c724bf84ff4192f4c94", + "checkout": null, + "context": { + "cookiecutter": { + "plugin_name": "SEO", + "repo_name": "seo", + "package_name": "seo", + "distribution_name": "pelican-seo", + "version": "1.2.2", + "description": "Pelican plugin to improve search engine optimization (SEO)", + "authors": "{name = \"Maëva Brunelles\", email = \"contact@tremacorp.fr\"}, {name = \"Justin Mayer\", email = \"entroP@gmail.com\"}", + "keywords": "\"pelican\", \"plugin\", \"seo\", \"search\", \"optimization\"", + "readme": "README.md", + "contributing": "CONTRIBUTING.md", + "license": "GNU Affero General Public License v3|AGPL-3.0", + "repo_url": "https://github.com/pelican-plugins/seo", + "dev_status": "5 - Production/Stable", + "tests_exist": true, + "python_version": "~=3.9", + "pelican_version": ">=4.5", + "_template": "https://github.com/getpelican/cookiecutter-pelican-plugin", + "_commit": "f51b3c4a5160fc8f36cff3baf9da7e2b5f59e8d7" + } + }, + "directory": null +} diff --git a/tasks.py b/tasks.py index 201972b..3f506b8 100644 --- a/tasks.py +++ b/tasks.py @@ -18,9 +18,10 @@ BIN_DIR = "bin" if os.name != "nt" else "Scripts" VENV_BIN = Path(VENV) / Path(BIN_DIR) -TOOLS = ("pdm", "pre-commit") +TOOLS = ("cruft", "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 " +CRUFT = which("cruft") if which("cruft") else f"{CMD_PREFIX}cruft" PRECOMMIT = which("pre-commit") if which("pre-commit") else f"{CMD_PREFIX}pre-commit" PTY = os.name != "nt" @@ -55,10 +56,7 @@ def ruff(c, concise=False, fix=False, diff=False): fix_flag = "--fix" if diff: diff_flag = "--diff" - c.run( - f"{CMD_PREFIX}ruff check {concise_flag} {diff_flag} {fix_flag} {PKG_PATH}", - pty=PTY, - ) + c.run(f"{CMD_PREFIX}ruff check {concise_flag} {diff_flag} {fix_flag} .", pty=PTY) @task @@ -84,6 +82,17 @@ def precommit(c): c.run(f"{PRECOMMIT} install") +@task +def update(c, check=False): + """Apply upstream plugin template changes to this project.""" + if check: + logger.info("** Checking for upstream template changes **") + c.run(f"{CRUFT} check", pty=PTY) + else: + logger.info("** Updating project from upstream template **") + c.run(f"{CRUFT} update", pty=PTY) + + @task def setup(c): """Set up the development environment."""