diff options
| author | 2024-04-23 00:37:58 -0400 | |
|---|---|---|
| committer | 2024-04-23 00:37:58 -0400 | |
| commit | 9b0e86a8e74768c4fe848fb5ce8d754292db4e3e (patch) | |
| tree | cfd424be8ecb68357e6e572033f08bc534bf724f /nikola | |
| parent | 393aa58f2c5afd51f92fd9bd4b6dfd0dc90cea41 (diff) | |
New upstream version 8.3.0.upstream/8.3.0upstream
Diffstat (limited to 'nikola')
135 files changed, 929 insertions, 426 deletions
diff --git a/nikola/__init__.py b/nikola/__init__.py index 200d23d..edf1c93 100644 --- a/nikola/__init__.py +++ b/nikola/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -29,7 +29,7 @@ import os import sys -__version__ = '8.2.3' +__version__ = '8.3.0' DEBUG = bool(os.getenv('NIKOLA_DEBUG')) SHOW_TRACEBACKS = bool(os.getenv('NIKOLA_SHOW_TRACEBACKS')) diff --git a/nikola/__main__.py b/nikola/__main__.py index 03c7a51..cb7ef0b 100644 --- a/nikola/__main__.py +++ b/nikola/__main__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/conf.py.in b/nikola/conf.py.in index d209eb4..d0d5e79 100644 --- a/nikola/conf.py.in +++ b/nikola/conf.py.in @@ -1046,8 +1046,9 @@ PRETTY_URLS = ${PRETTY_URLS} MARKDOWN_EXTENSIONS = ['markdown.extensions.fenced_code', 'markdown.extensions.codehilite', 'markdown.extensions.extra'] # Options to be passed to markdown extensions (See https://python-markdown.github.io/reference/) -# Default is {} (no config at all) -# MARKDOWN_EXTENSION_CONFIGS = {} +# Default is {DEFAULT_LANG: {}} (no config at all) +# (translatable) +# MARKDOWN_EXTENSION_CONFIGS = {DEFAULT_LANG: {}} # Extra options to pass to the pandoc command, empty by default. diff --git a/nikola/data/samplesite/galleries/demo/metadata.sample.yml b/nikola/data/samplesite/galleries/demo/metadata.sample.yml index f504573..9f89f33 100644 --- a/nikola/data/samplesite/galleries/demo/metadata.sample.yml +++ b/nikola/data/samplesite/galleries/demo/metadata.sample.yml @@ -7,7 +7,7 @@ order: 2 name: tesla4_lg.jpg order: 0 --- -name: tesla_conducts_lg.jpg +name: tesla_conducts_lg.webp caption: Nikola Tesla conducts electricity order: 1 --- diff --git a/nikola/data/samplesite/galleries/demo/tesla_conducts_lg.jpg b/nikola/data/samplesite/galleries/demo/tesla_conducts_lg.jpg Binary files differdeleted file mode 100644 index f47d2ae..0000000 --- a/nikola/data/samplesite/galleries/demo/tesla_conducts_lg.jpg +++ /dev/null diff --git a/nikola/data/samplesite/galleries/demo/tesla_conducts_lg.webp b/nikola/data/samplesite/galleries/demo/tesla_conducts_lg.webp Binary files differnew file mode 100644 index 0000000..084bfb3 --- /dev/null +++ b/nikola/data/samplesite/galleries/demo/tesla_conducts_lg.webp diff --git a/nikola/data/themes/base-jinja/templates/base.tmpl b/nikola/data/themes/base-jinja/templates/base.tmpl index 8b057db..94f3b34 100644 --- a/nikola/data/themes/base-jinja/templates/base.tmpl +++ b/nikola/data/themes/base-jinja/templates/base.tmpl @@ -30,7 +30,7 @@ {% endif %} {% block extra_js %}{% endblock %} <script> - baguetteBox.run('div#content', { + baguetteBox.run('main#content', { ignoreClass: 'islink', captions: function(element){var i=element.getElementsByTagName('img')[0];return i===undefined?'':i.alt;}}); </script> diff --git a/nikola/data/themes/base-jinja/templates/base_helper.tmpl b/nikola/data/themes/base-jinja/templates/base_helper.tmpl index 2d8c0c8..30faac6 100644 --- a/nikola/data/themes/base-jinja/templates/base_helper.tmpl +++ b/nikola/data/themes/base-jinja/templates/base_helper.tmpl @@ -82,7 +82,7 @@ lang="{{ lang }}"> <script src="https://polyfill.io/v3/polyfill.js?features=Intl.RelativeTimeFormat.%7Elocale.{{ luxon_locales[lang] }}"></script> {% endif %} {% if use_cdn %} - <script src="https://cdn.jsdelivr.net/npm/luxon@1.28.0/build/global/luxon.min.js" integrity="sha256-l1u7Y5ze+ENf/T9ORPa3E642/uMgHUFa1KnqzFCcWEY=" crossorigin="anonymous"></script> + <script src="https://cdn.jsdelivr.net/npm/luxon@3.3.0/build/global/luxon.min.js" integrity="sha256-Nn+JGDrq3PuTxcDfJmmI0Srj5LpfOFlKqEiPwQK7y40=" crossorigin="anonymous"></script> {% else %} <script src="/assets/js/luxon.min.js"></script> {% endif %} diff --git a/nikola/data/themes/base-jinja/templates/comments_helper_discourse.tmpl b/nikola/data/themes/base-jinja/templates/comments_helper_discourse.tmpl new file mode 100644 index 0000000..d02b0ee --- /dev/null +++ b/nikola/data/themes/base-jinja/templates/comments_helper_discourse.tmpl @@ -0,0 +1,27 @@ +{# -*- coding: utf-8 -*- #} + +{% macro comment_form(url, title, identifier) %} + {% if comment_system_id %} + <div id="discourse-comments"></div> + <script type="text/javascript"> + DiscourseEmbed = { discourseUrl: '{{ comment_system_id }}', + discourseEmbedUrl: '{{ url }}' }; + + (function() { + var d = document.createElement('script'); d.type = 'text/javascript'; d.async = true; + d.src = DiscourseEmbed.discourseUrl + 'javascripts/embed.js'; + (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(d); + })(); + </script> + {% endif %} +{% endmacro %} + +{% macro comment_link(link, identifier) %} + {% if comment_system_id %} + <a href="{{ link }}#discourse-comments">{{ messages("Comments") }}</a> + {% endif %} +{% endmacro %} + + +{% macro comment_link_script() %} +{% endmacro %} diff --git a/nikola/data/themes/base-jinja/templates/gallery.tmpl b/nikola/data/themes/base-jinja/templates/gallery.tmpl index d425106..40252ff 100644 --- a/nikola/data/themes/base-jinja/templates/gallery.tmpl +++ b/nikola/data/themes/base-jinja/templates/gallery.tmpl @@ -32,7 +32,7 @@ {% else %} <ul> {% for folder, ftitle in folders %} - <li><a href="{{ folder }}">📂 {{ ftitle|e }}</a></li> + <li><a href="{{ folder }}">📂 {{ ftitle|e }}</a></li> {% endfor %} </ul> {% endif %} diff --git a/nikola/data/themes/base/assets/css/rst_base.css b/nikola/data/themes/base/assets/css/rst_base.css index fcd7318..d5aa552 100644 --- a/nikola/data/themes/base/assets/css/rst_base.css +++ b/nikola/data/themes/base/assets/css/rst_base.css @@ -1,8 +1,8 @@ /* Minimal style sheet for the HTML output of Docutils. */ /* */ /* :Author: Günter Milde, based on html4css1.css by David Goodger */ -/* :Id: $Id: minimal.css 8642 2021-03-26 13:51:14Z milde $ */ -/* :Copyright: © 2015 Günter Milde. */ +/* :Id: $Id: minimal.css 9079 2022-06-19 14:00:56Z milde $ */ +/* :Copyright: © 2015, 2021 Günter Milde. */ /* :License: Released under the terms of the `2-Clause BSD license`_, */ /* in short: */ /* */ @@ -14,11 +14,10 @@ /* */ /* .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause */ -/* This CSS2.1_ stylesheet defines rules for Docutils elements without */ -/* HTML equivalent. It is required to make the document semantic visible. */ -/* */ -/* .. _CSS2.1: http://www.w3.org/TR/CSS2 */ -/* .. _validates: http://jigsaw.w3.org/css-validator/validator$link */ +/* This CSS3 stylesheet defines rules for Docutils elements without */ +/* HTML equivalent. It is required to make the document semantics visible. */ +/* */ +/* .. _validates: http://jigsaw.w3.org/css-validator/validator$link */ /* titles */ p.topic-title, @@ -43,8 +42,8 @@ p.sidebar-subtitle { h1 + p.subtitle { font-size: 1.6em; } -h2 + p.section-subtitle, a.toc-backref { - color: black; +a.toc-backref { + color: inherit; text-decoration: none; } @@ -73,12 +72,15 @@ span.problematic { margin-top: 0; margin-bottom: 0; } +/* Nested Paragraphs +p:first-child { margin-top: 0; } +p:last-child { margin-bottom: 0; } +details > p:last-child { margin-bottom: 1em; } + */ /* Table of Contents */ -.topic.contents { margin: 0.5em 0; } -.topic.contents ul.auto-toc { +.contents ul.auto-toc { /* section numbers present */ list-style-type: none; - padding-left: 1.5em; } /* Enumerated Lists */ @@ -97,6 +99,13 @@ dt .classifier:before { } /* Field Lists and similar */ /* bold field name, content starts on the same line */ +dl.field-list, +dl.option-list, +dl.docinfo, +dl.footnote, +dl.citation { + display: flow-root; +} dl.field-list > dt, dl.option-list > dt, dl.docinfo > dt, @@ -107,7 +116,7 @@ dl.citation > dt { float: left; margin: 0; padding: 0; - padding-right: 0.5em; + padding-right: 0.2em; } /* Offset for field content (corresponds to the --field-name-limit option) */ dl.field-list > dd, @@ -115,6 +124,12 @@ dl.option-list > dd, dl.docinfo > dd { margin-left: 9em; /* ca. 14 chars in the test examples, fit all Docinfo fields */ } +/* start nested lists on new line */ +dd > dl:first-child, +dd > ul:first-child, +dd > ol:first-child { + clear: left; +} /* start field-body on a new line after long field names */ dl.field-list > dd > *:first-child, dl.option-list > dd > *:first-child @@ -135,7 +150,6 @@ dl.docinfo > dd.authors > p { margin: 0; } dl.option-list > dt { font-weight: normal; } span.option { white-space: nowrap; } - /* Footnotes and Citations */ .footnote, .citation { margin: 1em 0; } /* default paragraph skip (Firefox) */ @@ -209,7 +223,6 @@ dt.label > span.fn-backref > a { font-style: italic; } margin-right: auto; } .align-center { - clear: both; text-align: center; margin-left: auto; margin-right: auto; @@ -222,6 +235,12 @@ dt.label > span.fn-backref > a { font-style: italic; } .align-middle { vertical-align: middle; } .align-bottom { vertical-align: bottom; } +img.align-left, img.align-center, img.align-right, +.figure.align-left, .figure.align-center, .figure.align-right, +object.align-left, object.align-center, object.align-right { + display: block; +} + /* reset inner alignment in figures and tables */ figure.align-left, figure.align-right, table.align-left, table.align-center, table.align-right { @@ -229,24 +248,14 @@ table.align-left, table.align-center, table.align-right { } /* Text Blocks */ -blockquote, -div.topic, -aside.topic { - margin: 1em 2em; -} +.topic { margin: 1em 2em; } .sidebar, .admonition, .system-message { - border: thin solid; margin: 1em 2em; + border: thin solid; padding: 0.5em 1em; } -.sidebar { - width: 30%; - max-width: 26em; - float: right; - clear: right; -} div.line-block { display: block; } div.line-block div.line-block, pre { margin-left: 2em; } @@ -258,7 +267,6 @@ pre.code code:before { } /* Tables */ - td > p:first-child, th > p:first-child { margin-top: 0; } td > p, th > p { margin-bottom: 0; } @@ -268,11 +276,20 @@ td > p, th > p { margin-bottom: 0; } padding-right: 0.5em /* separate table cells */ } +table > caption { + text-align: left; + margin-top: 0.2em; + margin-bottom: 0.2em; +} +table.captionbelow { + caption-side: bottom; +} + /* CSS31_ style sheet for the output of Docutils HTML writers. */ /* Rules for easy reading and pre-defined style variants. */ /* */ /* :Author: Günter Milde, based on html4css1.css by David Goodger */ -/* :Id: $Id: plain.css 8636 2021-03-19 00:23:33Z milde $ */ +/* :Id: $Id: plain.css 9081 2022-06-19 20:23:12Z milde $ */ /* :Copyright: © 2015 Günter Milde. */ /* :License: Released under the terms of the `2-Clause BSD license`_, */ /* in short: */ @@ -284,12 +301,27 @@ td > p, th > p { margin-bottom: 0; } /* This file is offered as-is, without any warranty. */ /* */ /* .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause */ -/* .. _CSS3: http://www.w3.org/TR/CSS3 */ +/* .. _CSS3: https://www.w3.org/Style/CSS/ */ /* Document Structure */ /* ****************** */ +/* Table of Contents */ +ul.auto-toc > li > p { + padding-left: 1em; + text-indent: -1em; +} +nav.contents ul { + padding-left: 1em; +} +main > nav.contents ul ul ul ul:not(.auto-toc) { + list-style-type: '\2B29\ '; +} +main > nav.contents ul ul ul ul ul:not(.auto-toc) { + list-style-type: '\2B1D\ '; +} + /* Transitions */ hr.docutils { width: 80%; @@ -305,30 +337,39 @@ dl > dd { /* Lists */ /* ===== */ -/* Separate list entries in compound lists */ -dl > dd, ol > li, - /* Definition Lists */ /* Indent lists nested in definition lists */ -/* (:only-child is new in CSS 3) */ dd > ul:only-child, dd > ol:only-child { padding-left: 1em; } /* Description Lists */ /* styled like in most dictionaries, encyclopedias etc. */ +dl.description { + display: flow-root; +} dl.description > dt { font-weight: bold; clear: left; float: left; margin: 0; padding: 0; - padding-right: 0.5em; + padding-right: 0.3em; +} +dl.description > dd:after { + display: table; + content: ""; + clear: left; /* clearfix for empty descriptions */ } /* Field Lists */ +dl.field-list > dd, +dl.docinfo > dd { + margin-left: var(--field-indent); /* adapted in media queries or HTML */ +} + /* example for custom field-name width */ dl.field-list.narrow > dd { - margin-left: 5em; + --field-indent: 5em; } /* run-in: start field-body on same line after long field names */ dl.field-list.run-in > dd p { @@ -337,8 +378,8 @@ dl.field-list.run-in > dd p { /* Bibliographic Fields */ -/* generally, bibliographic fields use special definition list dl.docinfo */ -/* but dedication and abstract are placed into "topic" divs */ +/* generally, bibliographic fields use dl.docinfo */ +/* but dedication and abstract are placed into divs */ div.abstract p.topic-title { text-align: center; } @@ -351,6 +392,10 @@ div.dedication p.topic-title { font-style: normal; } +/* disclosures */ +details { padding-left: 1em; } +summary { margin-left: -1em; } + /* Text Blocks */ /* =========== */ @@ -360,14 +405,9 @@ pre.math, pre.code { font-family: monospace; } -/* Block Quotes */ -blockquote > table, -div.topic > table { - margin-top: 0; - margin-bottom: 0; -} +/* Block Quotes and Topics */ blockquote p.attribution, -div.topic p.attribution { +.topic p.attribution { text-align: right; margin-left: 20%; } @@ -468,7 +508,9 @@ div.error { aside.sidebar { width: 30%; max-width: 26em; + float: right; + clear: right; margin-left: 1em; - margin-right: -2%; - background-color: #ffffee; + margin-right: -1%; + background-color: #fffffa; } diff --git a/nikola/data/themes/base/assets/css/theme.css b/nikola/data/themes/base/assets/css/theme.css index b198c2c..cd09792 100644 --- a/nikola/data/themes/base/assets/css/theme.css +++ b/nikola/data/themes/base/assets/css/theme.css @@ -1,7 +1,7 @@ @charset "UTF-8"; /* - Copyright © 2014-2022 Daniel Aleksandersen and others. + Copyright © 2014-2024 Daniel Aleksandersen and others. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/nikola/data/themes/base/messages/messages_bn.py b/nikola/data/themes/base/messages/messages_bn.py new file mode 100644 index 0000000..316e4ec --- /dev/null +++ b/nikola/data/themes/base/messages/messages_bn.py @@ -0,0 +1,49 @@ +# -*- encoding:utf-8 -*- +"""Autogenerated file, do not edit. Submit translations on Transifex.""" + +MESSAGES = { + "%d min remaining to read": "", + "(active)": "", + "Also available in:": "", + "Archive": "", + "Atom feed": "", + "Authors": "", + "Categories": "", + "Comments": "", + "LANGUAGE": "", + "Languages:": "", + "More posts about %s": "", + "Newer posts": "", + "Next post": "", + "Next": "", + "No posts found.": "", + "Nothing found.": "", + "Older posts": "", + "Original site": "", + "Posted:": "", + "Posts about %s": "", + "Posts by %s": "", + "Posts for year %s": "", + "Posts for {month_day_year}": "", + "Posts for {month_year}": "", + "Previous post": "", + "Previous": "", + "Publication date": "", + "RSS feed": "", + "Read in English": "", + "Read more": "", + "Skip to main content": "", + "Source": "", + "Subcategories:": "", + "Tags and Categories": "", + "Tags": "", + "Toggle navigation": "", + "Uncategorized": "", + "Up": "", + "Updates": "", + "Write your page here.": "", + "Write your post here.": "", + "old posts, page %d": "", + "page %d": "", + "updated": "", +} diff --git a/nikola/data/themes/base/messages/messages_el.py b/nikola/data/themes/base/messages/messages_el.py index 7021d84..3cba21a 100644 --- a/nikola/data/themes/base/messages/messages_el.py +++ b/nikola/data/themes/base/messages/messages_el.py @@ -2,12 +2,12 @@ """Autogenerated file, do not edit. Submit translations on Transifex.""" MESSAGES = { - "%d min remaining to read": "", - "(active)": "", + "%d min remaining to read": "%dmin απομένουν για ανάγνωση", + "(active)": "(ενεργό)", "Also available in:": "Διαθέσιμο και στα:", "Archive": "Αρχείο", - "Atom feed": "", - "Authors": "", + "Atom feed": "Ροή Atom", + "Authors": "Συγγραφείς", "Categories": "Κατηγορίες", "Comments": "Σχόλια", "LANGUAGE": "Ελληνικά", @@ -15,35 +15,35 @@ MESSAGES = { "More posts about %s": "Περισσότερες αναρτήσεις για %s", "Newer posts": "Νεότερες αναρτήσεις", "Next post": "Επόμενη ανάρτηση", - "Next": "", + "Next": "Επόμενο", "No posts found.": "Δε βρέθηκαν αναρτήσεις", "Nothing found.": "Δε βρέθηκε περιεχόμενο", "Older posts": "Παλαιότερες αναρτήσεις", "Original site": "Ιστοσελίδα αρχικής ανάρτησης", "Posted:": "Αναρτήθηκε:", "Posts about %s": "Αναρτήσεις για %s", - "Posts by %s": "", + "Posts by %s": "Αναρτήσεις από %s", "Posts for year %s": "Αναρτήσεις για το έτος %s", "Posts for {month_day_year}": "Αναρτήσεις στις {month_day_year}", "Posts for {month_year}": "Αναρτήσεις για τον {month_year}", "Previous post": "Προηγούμενη ανάρτηση", - "Previous": "", + "Previous": "Προηγούμενο", "Publication date": "Ημερομηνία δημοσίευσης", - "RSS feed": "", + "RSS feed": "Ροή RSS", "Read in English": "Διαβάστε στα Ελληνικά", "Read more": "Διαβάστε περισσότερα", - "Skip to main content": "", + "Skip to main content": "Παράλειψη στο κυρίως περιεχόμενο", "Source": "Πηγαίος κώδικας", - "Subcategories:": "", + "Subcategories:": "Υποκατηγορίες:", "Tags and Categories": "Ετικέτες και κατηγορίες", "Tags": "Ετικέτες", - "Toggle navigation": "", - "Uncategorized": "", - "Up": "", - "Updates": "", - "Write your page here.": "", - "Write your post here.": "", + "Toggle navigation": "Εναλλαγή πλοήγησης", + "Uncategorized": "Χωρίς κατηγορία.", + "Up": "Επάνω", + "Updates": "Ενημερώσεις", + "Write your page here.": "Γράψτε τη σελίδα σας εδώ.", + "Write your post here.": "Γράψτε τις αναρτήσεις σας εδώ.", "old posts, page %d": "σελίδα παλαιότερων αναρτήσεων %d", "page %d": "σελίδα %d", - "updated": "", + "updated": "ενημερωμένο", } diff --git a/nikola/data/themes/base/messages/messages_eo.py b/nikola/data/themes/base/messages/messages_eo.py index 2793ff5..a2ecf65 100644 --- a/nikola/data/themes/base/messages/messages_eo.py +++ b/nikola/data/themes/base/messages/messages_eo.py @@ -24,7 +24,7 @@ MESSAGES = { "Posts about %s": "Artikoloj pri %s", "Posts by %s": "Artikoloj de %s", "Posts for year %s": "Artikoloj de la jaro %s", - "Posts for {month_day_year}": "Artikoloj de la {month_day_year}", + "Posts for {month_day_year}": "Artikoloj de {month_day_year}", "Posts for {month_year}": "Artikoloj de {month_year}", "Previous post": "Antaŭa artikolo", "Previous": "Antaŭa", @@ -45,5 +45,5 @@ MESSAGES = { "Write your post here.": "Skribu tie vian artikolon.", "old posts, page %d": "%da paĝo de malnovaj artikoloj", "page %d": "paĝo %d", - "updated": "", + "updated": "ĝisdatigita", } diff --git a/nikola/data/themes/base/messages/messages_es.py b/nikola/data/themes/base/messages/messages_es.py index bb58c82..3bbb9d8 100644 --- a/nikola/data/themes/base/messages/messages_es.py +++ b/nikola/data/themes/base/messages/messages_es.py @@ -45,5 +45,5 @@ MESSAGES = { "Write your post here.": "Escriba su publicación aquí.", "old posts, page %d": "publicaciones antiguas, página %d", "page %d": "página %d", - "updated": "", + "updated": "actualizado", } diff --git a/nikola/data/themes/base/messages/messages_he.py b/nikola/data/themes/base/messages/messages_he.py index 2de9796..1ae7051 100644 --- a/nikola/data/themes/base/messages/messages_he.py +++ b/nikola/data/themes/base/messages/messages_he.py @@ -10,7 +10,7 @@ MESSAGES = { "Authors": "מחברים", "Categories": "קטגוריות", "Comments": "הערות", - "LANGUAGE": "אנגלית", + "LANGUAGE": "עברית", "Languages:": "שפות:", "More posts about %s": "עוד פוסטים אודות %s", "Newer posts": "פוסטים חדשים", diff --git a/nikola/data/themes/base/messages/messages_ja.py b/nikola/data/themes/base/messages/messages_ja.py index fd563ad..dbd38e3 100644 --- a/nikola/data/themes/base/messages/messages_ja.py +++ b/nikola/data/themes/base/messages/messages_ja.py @@ -4,43 +4,43 @@ MESSAGES = { "%d min remaining to read": "残りを読むのに必要な時間は%d分", "(active)": "(有効)", - "Also available in:": "他の言語で読む:", + "Also available in:": "他の言語で読む:", "Archive": "過去記事一覧", "Atom feed": "Atomフィード", "Authors": "著者一覧", - "Categories": "カテゴリ", + "Categories": "カテゴリー", "Comments": "コメント", "LANGUAGE": "日本語", - "Languages:": "言語:", + "Languages:": "言語:", "More posts about %s": "%sに関する記事一覧", "Newer posts": "新しい記事", "Next post": "次の記事", - "Next": "次", + "Next": "次へ", "No posts found.": "記事はありません。", "Nothing found.": "なにも見つかりませんでした。", "Older posts": "過去の記事", - "Original site": "翻訳元のサイト", - "Posted:": "公開日時:", + "Original site": "元のサイト", + "Posted:": "公開日時:", "Posts about %s": "%sについての記事", "Posts by %s": "%sの記事一覧", "Posts for year %s": "%s年の記事", "Posts for {month_day_year}": "{month_day_year}の記事", "Posts for {month_year}": "{month_year}の記事", "Previous post": "一つ前の記事", - "Previous": "前", + "Previous": "前へ", "Publication date": "公開日", "RSS feed": "RSSフィード", "Read in English": "日本語で読む", "Read more": "続きを読む", "Skip to main content": "本文を読み飛ばす", "Source": "ソース", - "Subcategories:": "サブカテゴリ", - "Tags and Categories": "カテゴリおよびタグ一覧", + "Subcategories:": "サブカテゴリー", + "Tags and Categories": "カテゴリーおよびタグ一覧", "Tags": "タグ", "Toggle navigation": "ナビゲーションを隠す", - "Uncategorized": "カテゴリなし", - "Up": "上", - "Updates": "フィード", + "Uncategorized": "カテゴリーなし", + "Up": "上へ", + "Updates": "更新", "Write your page here.": "ここに記事を記述してください。", "Write your post here.": "ここに記事を記述してください。", "old posts, page %d": "過去の記事 %dページ目", diff --git a/nikola/data/themes/base/messages/messages_ko.py b/nikola/data/themes/base/messages/messages_ko.py index 57b6cb9..bc33e9f 100644 --- a/nikola/data/themes/base/messages/messages_ko.py +++ b/nikola/data/themes/base/messages/messages_ko.py @@ -6,7 +6,7 @@ MESSAGES = { "(active)": "(활성됨)", "Also available in:": "이곳에서도 가능함:", "Archive": "저장소", - "Atom feed": "", + "Atom feed": "아톰 피드", "Authors": "작성자", "Categories": "분류", "Comments": "댓글", @@ -22,7 +22,7 @@ MESSAGES = { "Original site": "출처", "Posted:": "작성됨:", "Posts about %s": "%s에 대한 포스트", - "Posts by %s": "%s에 의해 작성된 글", + "Posts by %s": "%s의 작성된 포스트", "Posts for year %s": "%s년도 포스트", "Posts for {month_day_year}": "{month_day_year}에 작성된 포스트", "Posts for {month_year}": "{month_year}에 쓴 포스트", @@ -34,16 +34,16 @@ MESSAGES = { "Read more": "더 읽기", "Skip to main content": "주 콘텐츠로 바로가기", "Source": "원문", - "Subcategories:": "서브 카테고리", + "Subcategories:": "서브 카테고리:", "Tags and Categories": "태그와 분류", "Tags": "태그", - "Toggle navigation": "", + "Toggle navigation": "네비게이션 켜고 끄기", "Uncategorized": "카테고리 없음", - "Up": "", + "Up": "상위", "Updates": "업데이트", "Write your page here.": "여기에 페이지를 작성하세요.", "Write your post here.": "이곳에 글을 작성하세요.", "old posts, page %d": "이전 포스트, 페이지 %d", "page %d": "페이지 %d", - "updated": "", + "updated": "업데이트됨", } diff --git a/nikola/data/themes/base/messages/messages_mi.py b/nikola/data/themes/base/messages/messages_mi.py index 853744a..f9ed574 100644 --- a/nikola/data/themes/base/messages/messages_mi.py +++ b/nikola/data/themes/base/messages/messages_mi.py @@ -10,7 +10,7 @@ MESSAGES = { "Authors": "Kaituhi", "Categories": "Kāwai", "Comments": "Nga korero", - "LANGUAGE": " Māori", + "LANGUAGE": "Māori", "Languages:": "Reo", "More posts about %s": "Ētahi atu pou e pā ana ki %s", "Newer posts": "Nga pou hou", diff --git a/nikola/data/themes/base/messages/messages_ml.py b/nikola/data/themes/base/messages/messages_ml.py index a818320..e663d6a 100644 --- a/nikola/data/themes/base/messages/messages_ml.py +++ b/nikola/data/themes/base/messages/messages_ml.py @@ -2,7 +2,7 @@ """Autogenerated file, do not edit. Submit translations on Transifex.""" MESSAGES = { - "%d min remaining to read": "%d മിനിട്ട് കൂടി വായിച്ചു തീരാന് ", + "%d min remaining to read": "വായിച്ചു തീരാന് %dമിനിട്ട് കൂടി", "(active)": "(സജീവം)", "Also available in:": "ലഭ്യമായ ഭാഷകള്:", "Archive": "ആര്കൈവ്", @@ -12,7 +12,7 @@ MESSAGES = { "Comments": "കമന്റുകള്", "LANGUAGE": "മലയാളം", "Languages:": "ഭാഷകള്:", - "More posts about %s": "%s-നെ കുറിച്ചുള്ള കൂടുതല് രചനകള്", + "More posts about %s": "%s -നെ കുറിച്ചുള്ള കൂടുതല് രചനകള്", "Newer posts": "പുതിയ രചനകള്", "Next post": "അടുത്ത രചന", "Next": "അടുത്തത്", @@ -34,7 +34,7 @@ MESSAGES = { "Read more": "കൂടുതല് വായിക്കുക", "Skip to main content": "പ്രധാന ഉള്ളടക്കത്തിലേക്ക് നേരെ പോവുക", "Source": "സ്രോതസ്സ്", - "Subcategories:": "ഉപവിഭാഗങ്ങള്:", + "Subcategories:": "ഉപവിഭാഗങ്ങള്", "Tags and Categories": "ടാഗുകളും വിഭാഗങ്ങളും", "Tags": "ടാഗുകള്", "Toggle navigation": "നാവിഗേഷൻ മാറ്റുക", diff --git a/nikola/data/themes/base/messages/messages_ta.py b/nikola/data/themes/base/messages/messages_ta.py new file mode 100644 index 0000000..316e4ec --- /dev/null +++ b/nikola/data/themes/base/messages/messages_ta.py @@ -0,0 +1,49 @@ +# -*- encoding:utf-8 -*- +"""Autogenerated file, do not edit. Submit translations on Transifex.""" + +MESSAGES = { + "%d min remaining to read": "", + "(active)": "", + "Also available in:": "", + "Archive": "", + "Atom feed": "", + "Authors": "", + "Categories": "", + "Comments": "", + "LANGUAGE": "", + "Languages:": "", + "More posts about %s": "", + "Newer posts": "", + "Next post": "", + "Next": "", + "No posts found.": "", + "Nothing found.": "", + "Older posts": "", + "Original site": "", + "Posted:": "", + "Posts about %s": "", + "Posts by %s": "", + "Posts for year %s": "", + "Posts for {month_day_year}": "", + "Posts for {month_year}": "", + "Previous post": "", + "Previous": "", + "Publication date": "", + "RSS feed": "", + "Read in English": "", + "Read more": "", + "Skip to main content": "", + "Source": "", + "Subcategories:": "", + "Tags and Categories": "", + "Tags": "", + "Toggle navigation": "", + "Uncategorized": "", + "Up": "", + "Updates": "", + "Write your page here.": "", + "Write your post here.": "", + "old posts, page %d": "", + "page %d": "", + "updated": "", +} diff --git a/nikola/data/themes/base/messages/messages_tr.py b/nikola/data/themes/base/messages/messages_tr.py index 8249f42..1b81b37 100644 --- a/nikola/data/themes/base/messages/messages_tr.py +++ b/nikola/data/themes/base/messages/messages_tr.py @@ -6,7 +6,7 @@ MESSAGES = { "(active)": "(etkin)", "Also available in:": "Şu dilde de mevcut:", "Archive": "Arşiv", - "Atom feed": "", + "Atom feed": "Atom besleme", "Authors": "Yazarlar", "Categories": "Kategoriler", "Comments": "Yorumlar", @@ -15,7 +15,7 @@ MESSAGES = { "More posts about %s": "%s hakkında diğer yazılar", "Newer posts": "Daha yeni yazılar", "Next post": "Sonraki yazı", - "Next": "", + "Next": "Sonraki", "No posts found.": "Yazı bulunamadı.", "Nothing found.": "Hiçbir şey bulunamadı.", "Older posts": "Daha eski yazılar", @@ -27,7 +27,7 @@ MESSAGES = { "Posts for {month_day_year}": "{month_day_year} tarihinden itibaren yazılar", "Posts for {month_year}": "{month_year} göre yazılar", "Previous post": "Önceki yazı", - "Previous": "", + "Previous": "Önceki", "Publication date": "Yayınlanma tarihi", "RSS feed": "RSS kaynağı", "Read in English": "Türkçe olarak oku", @@ -37,13 +37,13 @@ MESSAGES = { "Subcategories:": "Alt kategoriler:", "Tags and Categories": "Etiketler ve Kategoriler", "Tags": "Etiketler", - "Toggle navigation": "", + "Toggle navigation": "Gezinme aç / kapa", "Uncategorized": "Kategorisiz", - "Up": "", + "Up": "Yukarı", "Updates": "Güncellemeler", "Write your page here.": "Sayfanızı buraya yazın.", "Write your post here.": "Yazınızı buraya yazın.", "old posts, page %d": "eski yazılar, sayfa %d", "page %d": "sayfa %d", - "updated": "", + "updated": "güncellendi", } diff --git a/nikola/data/themes/base/messages/messages_zh_cn.py b/nikola/data/themes/base/messages/messages_zh_cn.py index df8570b..c8bf3fc 100644 --- a/nikola/data/themes/base/messages/messages_zh_cn.py +++ b/nikola/data/themes/base/messages/messages_zh_cn.py @@ -3,8 +3,8 @@ MESSAGES = { "%d min remaining to read": "剩余阅读时间 %d 分钟", - "(active)": "(活跃)", - "Also available in:": "也可用于:", + "(active)": "(活跃)", + "Also available in:": "其他语言版本:", "Archive": "文章归档", "Atom feed": "Atom 源", "Authors": "作者", @@ -15,22 +15,22 @@ MESSAGES = { "More posts about %s": "关于%s 的更多文章 ", "Newer posts": "较新的文章", "Next post": "下一篇文章", - "Next": "项下", - "No posts found.": "没有找到文章", + "Next": "下一篇", + "No posts found.": "沒有找到文章。", "Nothing found.": "没有找到。", "Older posts": "以前的文章", "Original site": "原文地址", "Posted:": "发表于:", - "Posts about %s": "关于文章 %s", - "Posts by %s": "文章有%s发布", - "Posts for year %s": "%s年文章", - "Posts for {month_day_year}": "{month_day_year}文章", - "Posts for {month_year}": "{month_year}文章", + "Posts about %s": "关于 %s 的文章", + "Posts by %s": "由 %s 发布的文章", + "Posts for year %s": "%s 年的文章", + "Posts for {month_day_year}": "{month_day_year}的文章", + "Posts for {month_year}": "{month_year}的文章", "Previous post": "上一篇文章", "Previous": "以前", "Publication date": "发布日期", "RSS feed": "RSS 源", - "Read in English": "阅读简体中文", + "Read in English": "阅读简体中文版", "Read more": "阅读更多", "Skip to main content": "跳到主内容", "Source": "源文件", @@ -43,7 +43,7 @@ MESSAGES = { "Updates": "更新", "Write your page here.": "在这里书写你的页面。", "Write your post here.": "在这里书写你的文章。", - "old posts, page %d": "旧文章页 %d", + "old posts, page %d": "旧文章,第 %d 页", "page %d": "第 %d 页", - "updated": "更新", + "updated": "已更新", } diff --git a/nikola/data/themes/base/templates/base.tmpl b/nikola/data/themes/base/templates/base.tmpl index f071c95..ae9570a 100644 --- a/nikola/data/themes/base/templates/base.tmpl +++ b/nikola/data/themes/base/templates/base.tmpl @@ -30,7 +30,7 @@ ${template_hooks['extra_head']()} % endif <%block name="extra_js"></%block> <script> - baguetteBox.run('div#content', { + baguetteBox.run('main#content', { ignoreClass: 'islink', captions: function(element){var i=element.getElementsByTagName('img')[0];return i===undefined?'':i.alt;}}); </script> diff --git a/nikola/data/themes/base/templates/base_helper.tmpl b/nikola/data/themes/base/templates/base_helper.tmpl index 684165d..a03ae50 100644 --- a/nikola/data/themes/base/templates/base_helper.tmpl +++ b/nikola/data/themes/base/templates/base_helper.tmpl @@ -82,7 +82,7 @@ lang="${lang}"> <script src="https://polyfill.io/v3/polyfill.js?features=Intl.RelativeTimeFormat.%7Elocale.${luxon_locales[lang]}"></script> % endif % if use_cdn: - <script src="https://cdn.jsdelivr.net/npm/luxon@1.28.0/build/global/luxon.min.js" integrity="sha256-l1u7Y5ze+ENf/T9ORPa3E642/uMgHUFa1KnqzFCcWEY=" crossorigin="anonymous"></script> + <script src="https://cdn.jsdelivr.net/npm/luxon@3.3.0/build/global/luxon.min.js" integrity="sha256-Nn+JGDrq3PuTxcDfJmmI0Srj5LpfOFlKqEiPwQK7y40=" crossorigin="anonymous"></script> % else: <script src="/assets/js/luxon.min.js"></script> % endif diff --git a/nikola/data/themes/base/templates/comments_helper_discourse.tmpl b/nikola/data/themes/base/templates/comments_helper_discourse.tmpl new file mode 100644 index 0000000..0f92ea2 --- /dev/null +++ b/nikola/data/themes/base/templates/comments_helper_discourse.tmpl @@ -0,0 +1,27 @@ +## -*- coding: utf-8 -*- + +<%def name="comment_form(url, title, identifier)"> + %if comment_system_id: + <div id="discourse-comments"></div> + <script type="text/javascript"> + DiscourseEmbed = { discourseUrl: '${comment_system_id}', + discourseEmbedUrl: '${url}' }; + + (function() { + var d = document.createElement('script'); d.type = 'text/javascript'; d.async = true; + d.src = DiscourseEmbed.discourseUrl + 'javascripts/embed.js'; + (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(d); + })(); + </script> + %endif +</%def> + +<%def name="comment_link(link, identifier)"> + %if comment_system_id: + <a href="${link}#discourse-comments">${messages("Comments")}</a> + %endif +</%def> + + +<%def name="comment_link_script()"> +</%def> diff --git a/nikola/data/themes/base/templates/gallery.tmpl b/nikola/data/themes/base/templates/gallery.tmpl index fef3a86..a32f234 100644 --- a/nikola/data/themes/base/templates/gallery.tmpl +++ b/nikola/data/themes/base/templates/gallery.tmpl @@ -32,7 +32,7 @@ % else: <ul> % for folder, ftitle in folders: - <li><a href="${folder}">📂 ${ftitle|h}</a></li> + <li><a href="${folder}">📂 ${ftitle|h}</a></li> % endfor </ul> % endif diff --git a/nikola/data/themes/bootblog4-jinja/templates/base_helper.tmpl b/nikola/data/themes/bootblog4-jinja/templates/base_helper.tmpl index b35c8d2..be87913 100644 --- a/nikola/data/themes/bootblog4-jinja/templates/base_helper.tmpl +++ b/nikola/data/themes/bootblog4-jinja/templates/base_helper.tmpl @@ -64,9 +64,9 @@ lang="{{ lang }}"> {% macro late_load_js() %} {% if use_cdn %} - <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> + <script src="https://code.jquery.com/jquery-3.6.4.min.js" integrity="sha256-oP6HI9z1XaZNBrJURtCoUT5SUnxFr8s3BzRl+cbzUq8=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> - <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js" integrity="sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2" crossorigin="anonymous"></script> + <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js" integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/baguettebox.js/1.11.1/baguetteBox.min.js" integrity="sha256-ULQV01VS9LCI2ePpLsmka+W0mawFpEA0rtxnezUj4A4=" crossorigin="anonymous"></script> {% endif %} {% if use_bundles and use_cdn %} @@ -86,7 +86,7 @@ lang="{{ lang }}"> <script src="https://polyfill.io/v3/polyfill.js?features=Intl.RelativeTimeFormat.%7Elocale.{{ luxon_locales[lang] }}"></script> {% endif %} {% if use_cdn %} - <script src="https://cdn.jsdelivr.net/npm/luxon@1.28.0/build/global/luxon.min.js" integrity="sha256-l1u7Y5ze+ENf/T9ORPa3E642/uMgHUFa1KnqzFCcWEY=" crossorigin="anonymous"></script> + <script src="https://cdn.jsdelivr.net/npm/luxon@3.3.0/build/global/luxon.min.js" integrity="sha256-Nn+JGDrq3PuTxcDfJmmI0Srj5LpfOFlKqEiPwQK7y40=" crossorigin="anonymous"></script> {% else %} <script src="/assets/js/luxon.min.js"></script> {% endif %} @@ -100,7 +100,7 @@ lang="{{ lang }}"> {% macro html_stylesheets() %} {% if use_cdn %} - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/baguettebox.js/1.11.1/baguetteBox.min.css" integrity="sha256-cLMYWYYutHkt+KpNqjg7NVkYSQ+E2VbrXsEvOqU7mL0=" crossorigin="anonymous"> {% endif %} {% if use_bundles and use_cdn %} diff --git a/nikola/data/themes/bootblog4/templates/base_helper.tmpl b/nikola/data/themes/bootblog4/templates/base_helper.tmpl index 33414d8..7ba4e92 100644 --- a/nikola/data/themes/bootblog4/templates/base_helper.tmpl +++ b/nikola/data/themes/bootblog4/templates/base_helper.tmpl @@ -64,9 +64,9 @@ lang="${lang}"> <%def name="late_load_js()"> %if use_cdn: - <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> + <script src="https://code.jquery.com/jquery-3.6.4.min.js" integrity="sha256-oP6HI9z1XaZNBrJURtCoUT5SUnxFr8s3BzRl+cbzUq8=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> - <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js" integrity="sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2" crossorigin="anonymous"></script> + <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js" integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/baguettebox.js/1.11.1/baguetteBox.min.js" integrity="sha256-ULQV01VS9LCI2ePpLsmka+W0mawFpEA0rtxnezUj4A4=" crossorigin="anonymous"></script> % endif %if use_bundles and use_cdn: @@ -86,7 +86,7 @@ lang="${lang}"> <script src="https://polyfill.io/v3/polyfill.js?features=Intl.RelativeTimeFormat.%7Elocale.${luxon_locales[lang]}"></script> %endif %if use_cdn: - <script src="https://cdn.jsdelivr.net/npm/luxon@1.28.0/build/global/luxon.min.js" integrity="sha256-l1u7Y5ze+ENf/T9ORPa3E642/uMgHUFa1KnqzFCcWEY=" crossorigin="anonymous"></script> + <script src="https://cdn.jsdelivr.net/npm/luxon@3.3.0/build/global/luxon.min.js" integrity="sha256-Nn+JGDrq3PuTxcDfJmmI0Srj5LpfOFlKqEiPwQK7y40=" crossorigin="anonymous"></script> %else: <script src="/assets/js/luxon.min.js"></script> %endif @@ -100,7 +100,7 @@ lang="${lang}"> <%def name="html_stylesheets()"> % if use_cdn: - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/baguettebox.js/1.11.1/baguetteBox.min.css" integrity="sha256-cLMYWYYutHkt+KpNqjg7NVkYSQ+E2VbrXsEvOqU7mL0=" crossorigin="anonymous"> % endif %if use_bundles and use_cdn: diff --git a/nikola/data/themes/bootstrap4-jinja/README.md b/nikola/data/themes/bootstrap4-jinja/README.md index bb1b484..8cb5f56 100644 --- a/nikola/data/themes/bootstrap4-jinja/README.md +++ b/nikola/data/themes/bootstrap4-jinja/README.md @@ -6,5 +6,5 @@ content layout. For a more blog-style layout, check out `bootblog4`. Note that unlike previous versions of Bootstrap, icon fonts are not built-in. You can use Font Awesome for this. -This theme supports Bootswatch font/color schemes through the `nikola -bootwatch_theme` command. +This theme supports Bootswatch and HackerThemes font/color schemes +through the `nikola subtheme` command. diff --git a/nikola/data/themes/bootstrap4-jinja/templates/base_helper.tmpl b/nikola/data/themes/bootstrap4-jinja/templates/base_helper.tmpl index ff1bfa2..167e8df 100644 --- a/nikola/data/themes/bootstrap4-jinja/templates/base_helper.tmpl +++ b/nikola/data/themes/bootstrap4-jinja/templates/base_helper.tmpl @@ -64,9 +64,9 @@ lang="{{ lang }}"> {% macro late_load_js() %} {% if use_cdn %} - <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> + <script src="https://code.jquery.com/jquery-3.6.4.min.js" integrity="sha256-oP6HI9z1XaZNBrJURtCoUT5SUnxFr8s3BzRl+cbzUq8=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> - <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js" integrity="sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2" crossorigin="anonymous"></script> + <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js" integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/baguettebox.js/1.11.1/baguetteBox.min.js" integrity="sha256-ULQV01VS9LCI2ePpLsmka+W0mawFpEA0rtxnezUj4A4=" crossorigin="anonymous"></script> {% endif %} {% if use_bundles and use_cdn %} @@ -86,7 +86,7 @@ lang="{{ lang }}"> <script src="https://polyfill.io/v3/polyfill.js?features=Intl.RelativeTimeFormat.%7Elocale.{{ luxon_locales[lang] }}"></script> {% endif %} {% if use_cdn %} - <script src="https://cdn.jsdelivr.net/npm/luxon@1.28.0/build/global/luxon.min.js" integrity="sha256-l1u7Y5ze+ENf/T9ORPa3E642/uMgHUFa1KnqzFCcWEY=" crossorigin="anonymous"></script> + <script src="https://cdn.jsdelivr.net/npm/luxon@3.3.0/build/global/luxon.min.js" integrity="sha256-Nn+JGDrq3PuTxcDfJmmI0Srj5LpfOFlKqEiPwQK7y40=" crossorigin="anonymous"></script> {% else %} <script src="/assets/js/luxon.min.js"></script> {% endif %} @@ -100,7 +100,7 @@ lang="{{ lang }}"> {% macro html_stylesheets() %} {% if use_cdn %} - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/baguettebox.js/1.11.1/baguetteBox.min.css" integrity="sha256-cLMYWYYutHkt+KpNqjg7NVkYSQ+E2VbrXsEvOqU7mL0=" crossorigin="anonymous"> {% endif %} {% if use_bundles and use_cdn %} diff --git a/nikola/data/themes/bootstrap4-jinja/templates/listing.tmpl b/nikola/data/themes/bootstrap4-jinja/templates/listing.tmpl index 56a1b4f..f76b560 100644 --- a/nikola/data/themes/bootstrap4-jinja/templates/listing.tmpl +++ b/nikola/data/themes/bootstrap4-jinja/templates/listing.tmpl @@ -6,10 +6,10 @@ {% if folders or files %} <ul> {% for name in folders %} - <li><a href="{{ name|e }}">📂 {{ name|e }}</a> + <li><a href="{{ name|e }}">📂 {{ name|e }}</a> {% endfor %} {% for name in files %} - <li><a href="{{ name|e }}.html">📄 {{ name|e }}</a> + <li><a href="{{ name|e }}.html">📄 {{ name|e }}</a> {% endfor %} </ul> {% endif %} diff --git a/nikola/data/themes/bootstrap4/README.md b/nikola/data/themes/bootstrap4/README.md index bb1b484..8cb5f56 100644 --- a/nikola/data/themes/bootstrap4/README.md +++ b/nikola/data/themes/bootstrap4/README.md @@ -6,5 +6,5 @@ content layout. For a more blog-style layout, check out `bootblog4`. Note that unlike previous versions of Bootstrap, icon fonts are not built-in. You can use Font Awesome for this. -This theme supports Bootswatch font/color schemes through the `nikola -bootwatch_theme` command. +This theme supports Bootswatch and HackerThemes font/color schemes +through the `nikola subtheme` command. diff --git a/nikola/data/themes/bootstrap4/templates/base_helper.tmpl b/nikola/data/themes/bootstrap4/templates/base_helper.tmpl index 32ca42b..c5895cd 100644 --- a/nikola/data/themes/bootstrap4/templates/base_helper.tmpl +++ b/nikola/data/themes/bootstrap4/templates/base_helper.tmpl @@ -64,9 +64,9 @@ lang="${lang}"> <%def name="late_load_js()"> %if use_cdn: - <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> + <script src="https://code.jquery.com/jquery-3.6.4.min.js" integrity="sha256-oP6HI9z1XaZNBrJURtCoUT5SUnxFr8s3BzRl+cbzUq8=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> - <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js" integrity="sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2" crossorigin="anonymous"></script> + <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js" integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/baguettebox.js/1.11.1/baguetteBox.min.js" integrity="sha256-ULQV01VS9LCI2ePpLsmka+W0mawFpEA0rtxnezUj4A4=" crossorigin="anonymous"></script> % endif %if use_bundles and use_cdn: @@ -86,7 +86,7 @@ lang="${lang}"> <script src="https://polyfill.io/v3/polyfill.js?features=Intl.RelativeTimeFormat.%7Elocale.${luxon_locales[lang]}"></script> %endif %if use_cdn: - <script src="https://cdn.jsdelivr.net/npm/luxon@1.28.0/build/global/luxon.min.js" integrity="sha256-l1u7Y5ze+ENf/T9ORPa3E642/uMgHUFa1KnqzFCcWEY=" crossorigin="anonymous"></script> + <script src="https://cdn.jsdelivr.net/npm/luxon@3.3.0/build/global/luxon.min.js" integrity="sha256-Nn+JGDrq3PuTxcDfJmmI0Srj5LpfOFlKqEiPwQK7y40=" crossorigin="anonymous"></script> %else: <script src="/assets/js/luxon.min.js"></script> %endif @@ -100,7 +100,7 @@ lang="${lang}"> <%def name="html_stylesheets()"> %if use_cdn: - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/baguettebox.js/1.11.1/baguetteBox.min.css" integrity="sha256-cLMYWYYutHkt+KpNqjg7NVkYSQ+E2VbrXsEvOqU7mL0=" crossorigin="anonymous"> % endif %if use_bundles and use_cdn: diff --git a/nikola/data/themes/bootstrap4/templates/listing.tmpl b/nikola/data/themes/bootstrap4/templates/listing.tmpl index d9a4c56..6d22642 100644 --- a/nikola/data/themes/bootstrap4/templates/listing.tmpl +++ b/nikola/data/themes/bootstrap4/templates/listing.tmpl @@ -6,10 +6,10 @@ ${ui.breadcrumbs(crumbs)} %if folders or files: <ul> % for name in folders: - <li><a href="${name|h}">📂 ${name|h}</a> + <li><a href="${name|h}">📂 ${name|h}</a> % endfor % for name in files: - <li><a href="${name|h}.html">📄 ${name|h}</a> + <li><a href="${name|h}.html">📄 ${name|h}</a> % endfor </ul> %endif diff --git a/nikola/filters.py b/nikola/filters.py index 8deeb89..5bce4e0 100644 --- a/nikola/filters.py +++ b/nikola/filters.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -359,33 +359,33 @@ def php_template_injection(data): @apply_to_text_file def cssminify(data): - """Minify CSS using https://cssminifier.com/.""" + """Minify CSS using <https://www.toptal.com/developers/cssminifier>.""" try: - url = 'https://cssminifier.com/raw' + url = 'https://www.toptal.com/developers/cssminifier/api/raw' _data = {'input': data} response = requests.post(url, data=_data) if response.status_code != 200: - LOGGER.error("can't use cssminifier.com: HTTP status {}", response.status_code) + LOGGER.error("Can't use toptal.com CSS Minifier: HTTP status {}", response.status_code) return data return response.text except Exception as exc: - LOGGER.error("can't use cssminifier.com: {}", exc) + LOGGER.error("Can't use toptal.com CSS Minifier: {}", exc) return data @apply_to_text_file def jsminify(data): - """Minify JS using https://javascript-minifier.com/.""" + """Minify JS using <https://www.toptal.com/developers/javascript-minifier>.""" try: - url = 'https://javascript-minifier.com/raw' + url = 'https://www.toptal.com/developers/javascript-minifier/api/raw' _data = {'input': data} response = requests.post(url, data=_data) if response.status_code != 200: - LOGGER.error("can't use javascript-minifier.com: HTTP status {}", response.status_code) + LOGGER.error("Can't use toptal.com JavaScript Minifier: HTTP status {}", response.status_code) return data return response.text except Exception as exc: - LOGGER.error("can't use javascript-minifier.com: {}", exc) + LOGGER.error("Can't use toptal.com JavaScript Minifier: {}", exc) return data diff --git a/nikola/hierarchy_utils.py b/nikola/hierarchy_utils.py index 7e8d9b8..929138b 100644 --- a/nikola/hierarchy_utils.py +++ b/nikola/hierarchy_utils.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/image_processing.py b/nikola/image_processing.py index 4ac6d93..9f4d49c 100644 --- a/nikola/image_processing.py +++ b/nikola/image_processing.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2014 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -145,7 +145,7 @@ class ImageProcessor(object): if bigger_panoramas and w > 3 * h: size = min(w, max_size * 4), min(w, max_size * 4) try: - im.thumbnail(size, Image.ANTIALIAS) + im.thumbnail(size, Image.Resampling.LANCZOS) save_args = {} if icc_profile: save_args['icc_profile'] = icc_profile diff --git a/nikola/log.py b/nikola/log.py index eedee72..f998427 100644 --- a/nikola/log.py +++ b/nikola/log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/metadata_extractors.py b/nikola/metadata_extractors.py index 4e0ae10..1b33657 100644 --- a/nikola/metadata_extractors.py +++ b/nikola/metadata_extractors.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Chris Warrick, Roberto Alsina and others. +# Copyright © 2012-2024 Chris Warrick, Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/nikola.py b/nikola/nikola.py index 41eeac6..916ca69 100644 --- a/nikola/nikola.py +++ b/nikola/nikola.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -33,7 +33,9 @@ import functools import logging import operator import os +import pathlib import sys +import typing import mimetypes from collections import defaultdict from copy import copy @@ -46,27 +48,14 @@ import natsort import PyRSS2Gen as rss from pkg_resources import resource_filename from blinker import signal -from yapsy.PluginManager import PluginManager from . import DEBUG, SHOW_TRACEBACKS, filters, utils, hierarchy_utils, shortcodes from . import metadata_extractors from .metadata_extractors import default_metadata_extractors_by from .post import Post # NOQA +from .plugin_manager import PluginCandidate, PluginInfo, PluginManager from .plugin_categories import ( - Command, - LateTask, - PageCompiler, - CompilerExtension, - MarkdownExtension, - RestExtension, - MetadataExtractor, - ShortcodePlugin, - Task, - TaskMultiplier, TemplateSystem, - SignalHandler, - ConfigPlugin, - CommentSystem, PostScanner, Taxonomy, ) @@ -96,6 +85,7 @@ LEGAL_VALUES = { 'DEFAULT_THEME': 'bootblog4', 'COMMENT_SYSTEM': [ 'disqus', + 'discourse', 'facebook', 'intensedebate', 'isso', @@ -381,6 +371,9 @@ class Nikola(object): Takes a site config as argument on creation. """ + plugin_manager: PluginManager + _template_system: TemplateSystem + def __init__(self, **config): """Initialize proper environment for running tasks.""" # Register our own path handlers @@ -891,6 +884,7 @@ class Nikola(object): utils.LOGGER.warning('You are currently disabling "{}", but not the following new taxonomy plugins: {}'.format(old_plugin_name, ', '.join(missing_plugins))) utils.LOGGER.warning('Please also disable these new plugins or remove "{}" from the DISABLED_PLUGINS list.'.format(old_plugin_name)) self.config['DISABLED_PLUGINS'].extend(missing_plugins) + # Special-case logic for "render_indexes" to fix #2591 if 'render_indexes' in self.config['DISABLED_PLUGINS']: if 'generate_rss' in self.config['DISABLED_PLUGINS'] or self.config['GENERATE_RSS'] is False: @@ -1000,57 +994,50 @@ class Nikola(object): self.state._set_site(self) self.cache._set_site(self) - def _filter_duplicate_plugins(self, plugin_list): + # WebP files have no official MIME type yet, but we need to recognize them (Issue #3671) + mimetypes.add_type('image/webp', '.webp') + + def _filter_duplicate_plugins(self, plugin_list: typing.Iterable[PluginCandidate]): """Find repeated plugins and discard the less local copy.""" - def plugin_position_in_places(plugin): + def plugin_position_in_places(plugin: PluginInfo): # plugin here is a tuple: # (path to the .plugin file, path to plugin module w/o .py, plugin metadata) for i, place in enumerate(self._plugin_places): - if plugin[0].startswith(place): + place: pathlib.Path + try: + # Path.is_relative_to backport + plugin.source_dir.relative_to(place) return i - utils.LOGGER.warn("Duplicate plugin found in unexpected location: {}".format(plugin[0])) + except ValueError: + pass + utils.LOGGER.warning("Duplicate plugin found in unexpected location: {}".format(plugin.source_dir)) return len(self._plugin_places) plugin_dict = defaultdict(list) - for data in plugin_list: - plugin_dict[data[2].name].append(data) + for plugin in plugin_list: + plugin_dict[plugin.name].append(plugin) result = [] - for _, plugins in plugin_dict.items(): + for name, plugins in plugin_dict.items(): if len(plugins) > 1: # Sort by locality plugins.sort(key=plugin_position_in_places) utils.LOGGER.debug("Plugin {} exists in multiple places, using {}".format( - plugins[-1][2].name, plugins[-1][0])) + name, plugins[-1].source_dir)) result.append(plugins[-1]) return result def init_plugins(self, commands_only=False, load_all=False): """Load plugins as needed.""" - self.plugin_manager = PluginManager(categories_filter={ - "Command": Command, - "Task": Task, - "LateTask": LateTask, - "TemplateSystem": TemplateSystem, - "PageCompiler": PageCompiler, - "TaskMultiplier": TaskMultiplier, - "CompilerExtension": CompilerExtension, - "MarkdownExtension": MarkdownExtension, - "RestExtension": RestExtension, - "MetadataExtractor": MetadataExtractor, - "ShortcodePlugin": ShortcodePlugin, - "SignalHandler": SignalHandler, - "ConfigPlugin": ConfigPlugin, - "CommentSystem": CommentSystem, - "PostScanner": PostScanner, - "Taxonomy": Taxonomy, - }) - self.plugin_manager.getPluginLocator().setPluginInfoExtension('plugin') extra_plugins_dirs = self.config['EXTRA_PLUGINS_DIRS'] + self._loading_commands_only = commands_only self._plugin_places = [ resource_filename('nikola', 'plugins'), os.path.expanduser(os.path.join('~', '.nikola', 'plugins')), os.path.join(os.getcwd(), 'plugins'), ] + [path for path in extra_plugins_dirs if path] + self._plugin_places = [pathlib.Path(p) for p in self._plugin_places] + + self.plugin_manager = PluginManager(plugin_places=self._plugin_places) compilers = defaultdict(set) # Also add aliases for combinations with TRANSLATIONS_PATTERN @@ -1069,41 +1056,37 @@ class Nikola(object): self.disabled_compilers = {} self.disabled_compiler_extensions = defaultdict(list) - self.plugin_manager.getPluginLocator().setPluginPlaces(self._plugin_places) - self.plugin_manager.locatePlugins() - bad_candidates = set([]) + candidates = self.plugin_manager.locate_plugins() + good_candidates = set() if not load_all: - for p in self.plugin_manager._candidates: + for p in candidates: if commands_only: - if p[-1].details.has_option('Nikola', 'PluginCategory'): - # FIXME TemplateSystem should not be needed - if p[-1].details.get('Nikola', 'PluginCategory') not in {'Command', 'Template'}: - bad_candidates.add(p) - else: - bad_candidates.add(p) + if p.category != 'Command': + continue elif self.configured: # Not commands-only, and configured # Remove blacklisted plugins - if p[-1].name in self.config['DISABLED_PLUGINS']: - bad_candidates.add(p) - utils.LOGGER.debug('Not loading disabled plugin {}', p[-1].name) - # Remove compilers we don't use - if p[-1].details.has_option('Nikola', 'PluginCategory') and p[-1].details.get('Nikola', 'PluginCategory') in ('Compiler', 'PageCompiler'): - bad_candidates.add(p) - self.disabled_compilers[p[-1].name] = p + if p.name in self.config['DISABLED_PLUGINS']: + utils.LOGGER.debug('Not loading disabled plugin {}', p.name) + continue + # Remove compilers - will be loaded later based on usage + if p.category == "PageCompiler": + self.disabled_compilers[p.name] = p + continue # Remove compiler extensions we don't need - if p[-1].details.has_option('Nikola', 'compiler') and p[-1].details.get('Nikola', 'compiler') in self.disabled_compilers: - bad_candidates.add(p) - self.disabled_compiler_extensions[p[-1].details.get('Nikola', 'compiler')].append(p) - self.plugin_manager._candidates = list(set(self.plugin_manager._candidates) - bad_candidates) + if p.compiler and p.compiler in self.disabled_compilers: + self.disabled_compiler_extensions[p.compiler].append(p) + continue + good_candidates.add(p) - self.plugin_manager._candidates = self._filter_duplicate_plugins(self.plugin_manager._candidates) - self.plugin_manager.loadPlugins() + good_candidates = self._filter_duplicate_plugins(good_candidates) + self.plugin_manager.load_plugins(good_candidates) # Search for compiler plugins which we disabled but shouldn't have self._activate_plugins_of_category("PostScanner") if not load_all: file_extensions = set() - for post_scanner in [p.plugin_object for p in self.plugin_manager.getPluginsOfCategory('PostScanner')]: + for post_scanner in [p.plugin_object for p in self.plugin_manager.get_plugins_of_category('PostScanner')]: + post_scanner: PostScanner exts = post_scanner.supported_extensions() if exts is not None: file_extensions.update(exts) @@ -1122,13 +1105,13 @@ class Nikola(object): for p in self.disabled_compiler_extensions.pop(k, []): to_add.append(p) for _, p in self.disabled_compilers.items(): - utils.LOGGER.debug('Not loading unneeded compiler {}', p[-1].name) + utils.LOGGER.debug('Not loading unneeded compiler %s', p.name) for _, plugins in self.disabled_compiler_extensions.items(): for p in plugins: - utils.LOGGER.debug('Not loading compiler extension {}', p[-1].name) + utils.LOGGER.debug('Not loading compiler extension %s', p.name) if to_add: - self.plugin_manager._candidates = self._filter_duplicate_plugins(to_add) - self.plugin_manager.loadPlugins() + extra_candidates = self._filter_duplicate_plugins(to_add) + self.plugin_manager.load_plugins(extra_candidates) # Jupyter theme configuration. If a website has ipynb enabled in post_pages # we should enable the Jupyter CSS (leaving that up to the theme itself). @@ -1141,7 +1124,8 @@ class Nikola(object): self._activate_plugins_of_category("Taxonomy") self.taxonomy_plugins = {} - for taxonomy in [p.plugin_object for p in self.plugin_manager.getPluginsOfCategory('Taxonomy')]: + for taxonomy in [p.plugin_object for p in self.plugin_manager.get_plugins_of_category('Taxonomy')]: + taxonomy: Taxonomy if not taxonomy.is_enabled(): continue if taxonomy.classification_name in self.taxonomy_plugins: @@ -1167,10 +1151,9 @@ class Nikola(object): # Activate all required compiler plugins self.compiler_extensions = self._activate_plugins_of_category("CompilerExtension") - for plugin_info in self.plugin_manager.getPluginsOfCategory("PageCompiler"): + for plugin_info in self.plugin_manager.get_plugins_of_category("PageCompiler"): if plugin_info.name in self.config["COMPILERS"].keys(): - self.plugin_manager.activatePluginByName(plugin_info.name) - plugin_info.plugin_object.set_site(self) + self._activate_plugin(plugin_info) # Activate shortcode plugins self._activate_plugins_of_category("ShortcodePlugin") @@ -1179,10 +1162,8 @@ class Nikola(object): self.compilers = {} self.inverse_compilers = {} - for plugin_info in self.plugin_manager.getPluginsOfCategory( - "PageCompiler"): - self.compilers[plugin_info.name] = \ - plugin_info.plugin_object + for plugin_info in self.plugin_manager.get_plugins_of_category("PageCompiler"): + self.compilers[plugin_info.name] = plugin_info.plugin_object # Load comment systems, config plugins and register templated shortcodes self._activate_plugins_of_category("CommentSystem") @@ -1325,13 +1306,26 @@ class Nikola(object): self.ALL_PAGE_DEPS['index_read_more_link'] = self.config.get('INDEX_READ_MORE_LINK') self.ALL_PAGE_DEPS['feed_read_more_link'] = self.config.get('FEED_READ_MORE_LINK') - def _activate_plugins_of_category(self, category): + def _activate_plugin(self, plugin_info: PluginInfo) -> None: + plugin_info.plugin_object.set_site(self) + + if plugin_info.category == "TemplateSystem" or self._loading_commands_only: + return + + templates_directory_candidates = [ + plugin_info.source_dir / "templates" / self.template_system.name, + plugin_info.source_dir / plugin_info.module_name / "templates" / self.template_system.name + ] + for candidate in templates_directory_candidates: + if candidate.exists() and candidate.is_dir(): + self.template_system.inject_directory(str(candidate)) + + def _activate_plugins_of_category(self, category) -> typing.List[PluginInfo]: """Activate all the plugins of a given category and return them.""" # this code duplicated in tests/base.py plugins = [] - for plugin_info in self.plugin_manager.getPluginsOfCategory(category): - self.plugin_manager.activatePluginByName(plugin_info.name) - plugin_info.plugin_object.set_site(self) + for plugin_info in self.plugin_manager.get_plugins_of_category(category): + self._activate_plugin(plugin_info) plugins.append(plugin_info) return plugins @@ -1395,13 +1389,12 @@ class Nikola(object): if self._template_system is None: # Load template plugin template_sys_name = utils.get_template_engine(self.THEMES) - pi = self.plugin_manager.getPluginByName( - template_sys_name, "TemplateSystem") + pi = self.plugin_manager.get_plugin_by_name(template_sys_name, "TemplateSystem") if pi is None: sys.stderr.write("Error loading {0} template system " "plugin\n".format(template_sys_name)) sys.exit(1) - self._template_system = pi.plugin_object + self._template_system = typing.cast(TemplateSystem, pi.plugin_object) lookup_dirs = ['templates'] + [os.path.join(utils.get_theme_path(name), "templates") for name in self.THEMES] self._template_system.set_directories(lookup_dirs, @@ -1921,7 +1914,8 @@ class Nikola(object): else: return link else: - return os.path.join(*path) + # URLs should always use forward slash separators, even on Windows + return str(pathlib.PurePosixPath(*path)) def post_path(self, name, lang): """Link to the destination of an element in the POSTS/PAGES settings. @@ -2069,7 +2063,7 @@ class Nikola(object): yield ft task_dep = [] - for pluginInfo in self.plugin_manager.getPluginsOfCategory(plugin_category): + for pluginInfo in self.plugin_manager.get_plugins_of_category(plugin_category): for task in flatten(pluginInfo.plugin_object.gen_tasks()): if 'basename' not in task: raise ValueError("Task {0} does not have a basename".format(task)) @@ -2078,7 +2072,7 @@ class Nikola(object): task['task_dep'] = [] task['task_dep'].extend(self.injected_deps[task['basename']]) yield task - for multi in self.plugin_manager.getPluginsOfCategory("TaskMultiplier"): + for multi in self.plugin_manager.get_plugins_of_category("TaskMultiplier"): flag = False for task in multi.plugin_object.process(task, name): flag = True @@ -2187,7 +2181,7 @@ class Nikola(object): self.timeline = [] self.pages = [] - for p in sorted(self.plugin_manager.getPluginsOfCategory('PostScanner'), key=operator.attrgetter('name')): + for p in sorted(self.plugin_manager.get_plugins_of_category('PostScanner'), key=operator.attrgetter('name')): try: timeline = p.plugin_object.scan() except Exception: diff --git a/nikola/packages/datecond/LICENSE b/nikola/packages/datecond/LICENSE index fcc5730..07d9028 100644 --- a/nikola/packages/datecond/LICENSE +++ b/nikola/packages/datecond/LICENSE @@ -1,4 +1,4 @@ -Copyright © 2016-2022, Chris Warrick. +Copyright © 2016-2024, Chris Warrick. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/nikola/packages/datecond/__init__.py b/nikola/packages/datecond/__init__.py index 4e9566c..14065da 100644 --- a/nikola/packages/datecond/__init__.py +++ b/nikola/packages/datecond/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- # Date Conditionals v0.1.7 -# Copyright © 2015-2022, Chris Warrick. +# Copyright © 2015-2024, Chris Warrick. # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/nikola/packages/pygments_better_html/LICENSE b/nikola/packages/pygments_better_html/LICENSE index 8312fb8..7e06604 100644 --- a/nikola/packages/pygments_better_html/LICENSE +++ b/nikola/packages/pygments_better_html/LICENSE @@ -1,4 +1,4 @@ -Copyright © 2020-2022, Chris Warrick. +Copyright © 2020-2024, Chris Warrick. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/nikola/packages/pygments_better_html/__init__.py b/nikola/packages/pygments_better_html/__init__.py index 565dcb4..3fbc565 100644 --- a/nikola/packages/pygments_better_html/__init__.py +++ b/nikola/packages/pygments_better_html/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Better HTML formatter for Pygments. -Copyright © 2020-2022, Chris Warrick. +Copyright © 2020-2024, Chris Warrick. License: 3-clause BSD. Portions copyright © 2006-2019, the Pygments authors. (2-clause BSD). """ diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 14760df..b9eee3a 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -29,12 +29,10 @@ import io import logging import os -import sys import typing import doit from doit.cmd_base import Command as DoitCommand -from yapsy.IPlugin import IPlugin from .utils import LOGGER, first_line, get_logger, req_missing @@ -59,7 +57,7 @@ __all__ = ( ) -class BasePlugin(IPlugin): +class BasePlugin: """Base plugin class.""" logger = None @@ -67,28 +65,13 @@ class BasePlugin(IPlugin): def set_site(self, site): """Set site, which is a Nikola instance.""" self.site = site - self.inject_templates() self.logger = get_logger(self.name) if not site.debug: self.logger.level = logging.INFO - def inject_templates(self): - """Inject 'templates/<engine>' (if exists) very early in the theme chain.""" - try: - # Sorry, found no other way to get this - mod_path = sys.modules[self.__class__.__module__].__file__ - mod_dir = os.path.dirname(mod_path) - tmpl_dir = os.path.join( - mod_dir, 'templates', self.site.template_system.name - ) - if os.path.isdir(tmpl_dir): - # Inject tmpl_dir low in the theme chain - self.site.template_system.inject_directory(tmpl_dir) - except AttributeError: - # In some cases, __builtin__ becomes the module of a plugin. - # We couldn’t reproduce that, and really find the reason for this, - # so let’s just ignore it and be done with it. - pass + def set_module_path(self, module_path): + """Set the plugin's module path.""" + self.module_path = module_path def inject_dependency(self, target, dependency): """Add 'dependency' to the target task's task_deps.""" @@ -359,7 +342,7 @@ class PageCompiler(BasePlugin): """Activate all the compiler extension plugins for a given compiler and return them.""" plugins = [] for plugin_info in self.site.compiler_extensions: - if plugin_info.plugin_object.compiler_name == self.name: + if plugin_info.compiler == self.name or plugin_info.plugin_object.compiler_name == self.name: plugins.append(plugin_info) return plugins @@ -371,11 +354,8 @@ class CompilerExtension(BasePlugin): (a) create a new plugin class for them; or (b) use this class and filter them yourself. If you choose (b), you should the compiler name to the .plugin - file in the Nikola/Compiler section and filter all plugins of - this category, getting the compiler name with: - p.details.get('Nikola', 'Compiler') - Note that not all compiler plugins have this option and you might - need to catch configparser.NoOptionError exceptions. + file in the Nikola/compiler section and filter all plugins of + this category, getting the compiler name with `plugin_info.compiler`. """ name = "dummy_compiler_extension" @@ -904,3 +884,23 @@ class Taxonomy(BasePlugin): Provided is a set of classifications per language (`classifications_per_language`). """ return [] + + +CATEGORIES = { + "Command": Command, + "Task": Task, + "LateTask": LateTask, + "TemplateSystem": TemplateSystem, + "PageCompiler": PageCompiler, + "TaskMultiplier": TaskMultiplier, + "CompilerExtension": CompilerExtension, + "MarkdownExtension": MarkdownExtension, + "RestExtension": RestExtension, + "MetadataExtractor": MetadataExtractor, + "ShortcodePlugin": ShortcodePlugin, + "SignalHandler": SignalHandler, + "ConfigPlugin": ConfigPlugin, + "CommentSystem": CommentSystem, + "PostScanner": PostScanner, + "Taxonomy": Taxonomy, +} diff --git a/nikola/plugin_manager.py b/nikola/plugin_manager.py new file mode 100644 index 0000000..5c1a29a --- /dev/null +++ b/nikola/plugin_manager.py @@ -0,0 +1,264 @@ +# -*- coding: utf-8 -*- + +# Copyright © 2012-2024 Chris Warrick and others. + +# Permission is hereby granted, free of charge, to any +# person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the +# Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the +# Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice +# shall be included in all copies or substantial portions of +# the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +"""The Nikola plugin manager. Inspired by yapsy.""" + +import configparser +import importlib +import importlib.util +import time +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import Dict, List, Optional, Type, TYPE_CHECKING, Set + +from .plugin_categories import BasePlugin, CATEGORIES +from .utils import get_logger + +if TYPE_CHECKING: + import logging + +LEGACY_PLUGIN_NAMES: Dict[str, str] = { + "Compiler": "PageCompiler", + "Shortcode": "ShortcodePlugin", + "Template": "TemplateSystem", +} + +CATEGORY_NAMES: Set[str] = set(CATEGORIES.keys()) +CATEGORY_TYPES: Set[Type[BasePlugin]] = set(CATEGORIES.values()) + + +@dataclass(frozen=True) +class PluginCandidate: + """A candidate plugin that was located but not yet loaded (imported).""" + + name: str + description: Optional[str] + plugin_id: str + category: str + compiler: Optional[str] + source_dir: Path + module_name: str + + +@dataclass(frozen=True) +class PluginInfo: + """A plugin that was loaded (imported).""" + + name: str + description: Optional[str] + plugin_id: str + category: str + compiler: Optional[str] + source_dir: Path + module_name: str + module_object: object + plugin_object: BasePlugin + + +class PluginManager: + """The Nikola plugin manager.""" + + categories_filter: Dict[str, Type[BasePlugin]] + plugin_places: List[Path] + logger: "logging.Logger" + candidates: List[PluginCandidate] + plugins: List[PluginInfo] + _plugins_by_category: Dict[str, List[PluginInfo]] + has_warnings: bool = False + + def __init__(self, plugin_places: List[Path]): + """Initialize the plugin manager.""" + self.plugin_places = plugin_places + self.candidates = [] + self.plugins = [] + self._plugins_by_category = {} + self.logger = get_logger("PluginManager") + + def locate_plugins(self) -> List[PluginCandidate]: + """Locate plugins in plugin_places.""" + self.candidates = [] + + plugin_files: List[Path] = [] + for place in self.plugin_places: + plugin_files += place.rglob("*.plugin") + + for plugin_file in plugin_files: + source_dir = plugin_file.parent + config = configparser.ConfigParser() + config.read(plugin_file) + name = config["Core"]["name"] + module_name = config["Core"]["module"] + plugin_id = f"Plugin {name} from {plugin_file}" + description = None + if "Documentation" in config: + description = config["Documentation"].get("Description") + if "Nikola" not in config: + self.logger.warning(f"{plugin_id} does not specify Nikola configuration - plugin will not be loaded") + self.logger.warning("Please add a [Nikola] section to the {plugin_file} file with a PluginCategory entry") + self.has_warnings = True + continue + category = config["Nikola"].get("PluginCategory") + compiler = config["Nikola"].get("Compiler") + if not category: + self.logger.warning(f"{plugin_id} does not specify any category (Nikola.PluginCategory is missing in .plugin file) - plugin will not be loaded") + self.has_warnings = True + continue + if category in LEGACY_PLUGIN_NAMES: + category = LEGACY_PLUGIN_NAMES[category] + if category not in CATEGORY_NAMES: + self.logger.warning(f"{plugin_id} specifies invalid category '{category}' in the .plugin file - plugin will not be loaded") + self.has_warnings = True + continue + self.logger.debug(f"Discovered {plugin_id}") + self.candidates.append( + PluginCandidate( + name=name, + description=description, + plugin_id=plugin_id, + category=category, + compiler=compiler, + source_dir=source_dir, + module_name=module_name, + ) + ) + return self.candidates + + def load_plugins(self, candidates: List[PluginCandidate]) -> None: + """Load selected candidate plugins.""" + plugins_root = Path(__file__).parent.parent + + for candidate in candidates: + name = candidate.name + module_name = candidate.module_name + source_dir = candidate.source_dir + py_file_location = source_dir / f"{module_name}.py" + plugin_id = candidate.plugin_id + if not py_file_location.exists(): + py_file_location = source_dir / module_name / "__init__.py" + if not py_file_location.exists(): + self.logger.warning(f"{plugin_id} could not be loaded (no valid module detected)") + self.has_warnings = True + continue + + plugin_id += f" ({py_file_location})" + full_module_name = module_name + + try: + name_parts = list(py_file_location.relative_to(plugins_root).parts) + if name_parts[-1] == "__init__.py": + name_parts.pop(-1) + elif name_parts[-1].endswith(".py"): + name_parts[-1] = name_parts[-1][:-3] + full_module_name = ".".join(name_parts) + except ValueError: + pass + + try: + spec = importlib.util.spec_from_file_location(full_module_name, py_file_location) + module_object = importlib.util.module_from_spec(spec) + if full_module_name not in sys.modules: + sys.modules[full_module_name] = module_object + spec.loader.exec_module(module_object) + except Exception: + self.logger.exception(f"{plugin_id} threw an exception while loading") + self.has_warnings = True + continue + + plugin_classes = [ + c + for c in vars(module_object).values() + if isinstance(c, type) and issubclass(c, BasePlugin) and c not in CATEGORY_TYPES + ] + if len(plugin_classes) == 0: + self.logger.warning(f"{plugin_id} does not have any plugin classes - plugin will not be loaded") + self.has_warnings = True + continue + elif len(plugin_classes) > 1: + self.logger.warning(f"{plugin_id} has multiple plugin classes; this is not supported - plugin will not be loaded") + self.has_warnings = True + continue + + plugin_class = plugin_classes[0] + + if not issubclass(plugin_class, CATEGORIES[candidate.category]): + self.logger.warning(f"{plugin_id} has category '{candidate.category}' in the .plugin file, but the implementation class {plugin_class} does not inherit from this category - plugin will not be loaded") + self.has_warnings = True + continue + + try: + plugin_object = plugin_class() + except Exception: + self.logger.exception(f"{plugin_id} threw an exception while creating the instance") + self.has_warnings = True + continue + self.logger.debug(f"Loaded {plugin_id}") + info = PluginInfo( + name=name, + description=candidate.description, + plugin_id=candidate.plugin_id, + category=candidate.category, + compiler=candidate.compiler, + source_dir=source_dir, + module_name=module_name, + module_object=module_object, + plugin_object=plugin_object, + ) + self.plugins.append(info) + + self._plugins_by_category = {category: [] for category in CATEGORY_NAMES} + for plugin_info in self.plugins: + self._plugins_by_category[plugin_info.category].append(plugin_info) + + if self.has_warnings: + self.logger.warning("Some plugins failed to load. Please review the above warning messages.") + # TODO remove following messages and delay in v8.3.1 + self.logger.warning("You may need to update some plugins (from plugins.getnikola.com) or to fix their .plugin files.") + self.logger.warning("Waiting 2 seconds before continuing.") + time.sleep(2) + + def get_plugins_of_category(self, category: str) -> List[PluginInfo]: + """Get loaded plugins of a given category.""" + return self._plugins_by_category.get(category, []) + + def get_plugin_by_name(self, name: str, category: Optional[str] = None) -> Optional[PluginInfo]: + """Get a loaded plugin by name and optionally by category. Returns None if no such plugin is loaded.""" + for p in self.plugins: + if p.name == name and (category is None or p.category == category): + return p + + # Aliases for Yapsy compatibility + # TODO: remove in v9 + def getPluginsOfCategory(self, category: str) -> List[PluginInfo]: + """Get loaded plugins of a given category.""" + self.logger.warning("Legacy getPluginsOfCategory method was used, it may be removed in the future. Please change it to get_plugins_of_category.") + return self._plugins_by_category.get(category, []) + + # TODO: remove in v9 + def getPluginByName(self, name: str, category: Optional[str] = None) -> Optional[PluginInfo]: + """Get a loaded plugin by name and optionally by category. Returns None if no such plugin is loaded.""" + self.logger.warning("Legacy getPluginByName method was used, it may be removed in the future. Please change it to get_plugin_by_name.") + return self.get_plugin_by_name(name, category) diff --git a/nikola/plugins/basic_import.py b/nikola/plugins/basic_import.py index ca88cfb..ad53572 100644 --- a/nikola/plugins/basic_import.py +++ b/nikola/plugins/basic_import.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/command/__init__.py b/nikola/plugins/command/__init__.py index 6c8e81a..e5cd68a 100644 --- a/nikola/plugins/command/__init__.py +++ b/nikola/plugins/command/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/command/auto/__init__.py b/nikola/plugins/command/auto/__init__.py index b13b645..d272c23 100644 --- a/nikola/plugins/command/auto/__init__.py +++ b/nikola/plugins/command/auto/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Chris Warrick, Roberto Alsina and others. +# Copyright © 2012-2024 Chris Warrick, Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -35,6 +35,7 @@ import stat import subprocess import sys import typing +import urllib.parse import webbrowser import blinker @@ -67,6 +68,17 @@ if sys.platform == 'win32': asyncio.set_event_loop(asyncio.ProactorEventLoop()) +def base_path_from_siteuri(siteuri: str) -> str: + """Extract the path part from a URI such as site['SITE_URL']. + + The path never ends with a "/". (If only "/" is intended, it is empty.) + """ + path = urllib.parse.urlsplit(siteuri).path + if path.endswith("/"): + path = path[:-1] + return path + + class CommandAuto(Command): """Automatic rebuilds for Nikola.""" @@ -239,8 +251,10 @@ class CommandAuto(Command): # Server can be disabled (Issue #1883) self.has_server = not options['no-server'] + base_path = base_path_from_siteuri(self.site.config['SITE_URL']) + if self.has_server: - loop.run_until_complete(self.set_up_server(host, port, out_folder)) + loop.run_until_complete(self.set_up_server(host, port, base_path, out_folder)) # Run an initial build so we are up-to-date. The server is running, but we are not watching yet. loop.run_until_complete(self.run_initial_rebuild()) @@ -293,9 +307,12 @@ class CommandAuto(Command): if browser: # Some browsers fail to load 0.0.0.0 (Issue #2755) if host == '0.0.0.0': - server_url = "http://127.0.0.1:{0}/".format(port) - self.logger.info("Opening {0} in the default web browser...".format(server_url)) - webbrowser.open(server_url) + browser_url = "http://127.0.0.1:{0}/{1}".format(port, base_path.lstrip("/")) + else: + # server_url always ends with a "/": + browser_url = "{0}{1}".format(server_url, base_path.lstrip("/")) + self.logger.info("Opening {0} in the default web browser...".format(browser_url)) + webbrowser.open(browser_url) # Run the event loop forever and handle shutdowns. try: @@ -320,13 +337,13 @@ class CommandAuto(Command): self.wd_observer.join() loop.close() - async def set_up_server(self, host: str, port: int, out_folder: str) -> None: + async def set_up_server(self, host: str, port: int, base_path: str, out_folder: str) -> None: """Set up aiohttp server and start it.""" webapp = web.Application() webapp.router.add_get('/livereload.js', self.serve_livereload_js) webapp.router.add_get('/robots.txt', self.serve_robots_txt) webapp.router.add_route('*', '/livereload', self.websocket_handler) - resource = IndexHtmlStaticResource(True, self.snippet, '', out_folder) + resource = IndexHtmlStaticResource(True, self.snippet, base_path, out_folder) webapp.router.register_resource(resource) webapp.on_shutdown.append(self.remove_websockets) @@ -587,13 +604,11 @@ class NikolaEventHandler: self.function = function self.loop = loop - async def on_any_event(self, event): - """Handle all file events.""" - await self.function(event) - def dispatch(self, event): """Dispatch events to handler.""" - self.loop.call_soon_threadsafe(asyncio.ensure_future, self.on_any_event(event)) + if event.event_type in {"opened", "closed"}: + return + self.loop.call_soon_threadsafe(asyncio.ensure_future, self.function(event)) class ConfigEventHandler(NikolaEventHandler): @@ -601,11 +616,10 @@ class ConfigEventHandler(NikolaEventHandler): def __init__(self, configuration_filename, function, loop): """Initialize the handler.""" + super().__init__(function, loop) self.configuration_filename = configuration_filename - self.function = function - self.loop = loop - async def on_any_event(self, event): + def dispatch(self, event): """Handle file events if they concern the configuration file.""" if event._src_path == self.configuration_filename: - await self.function(event) + super().dispatch(event) diff --git a/nikola/plugins/command/check.py b/nikola/plugins/command/check.py index f9b701b..5bcbced 100644 --- a/nikola/plugins/command/check.py +++ b/nikola/plugins/command/check.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -148,6 +148,22 @@ class CommandCheck(Command): 'default': False, 'help': 'Check that remote links work.', }, + { + 'name': 'timeout', + 'long': 'timeout', + 'short': 't', + 'type': int, + 'default': 30, + 'help': 'Timeout (in seconds) for HTTP requests in remote checks.', + }, + { + 'name': 'ignore_query_strings', + 'long': 'ignore-query-strings', + 'short': 'q', + 'type': bool, + 'default': False, + 'help': 'Ignore query strings for internal links.', + } ] def _execute(self, options, args): @@ -160,8 +176,9 @@ class CommandCheck(Command): else: self.logger.level = logging.WARNING failure = False + self.timeout = options['timeout'] if options['links']: - failure |= self.scan_links(options['find_sources'], options['remote']) + failure |= self.scan_links(options['find_sources'], options['remote'], options['ignore_query_strings']) if options['files']: failure |= self.scan_files() if options['clean']: @@ -171,9 +188,10 @@ class CommandCheck(Command): existing_targets = set([]) checked_remote_targets = {} + timeout = None cache = {} - def analyze(self, fname, find_sources=False, check_remote=False): + def analyze(self, fname, find_sources=False, check_remote=False, ignore_query_strings=False): """Analyze links on a page.""" rv = False self.whitelist = [re.compile(x) for x in self.site.config['LINK_CHECK_WHITELIST']] @@ -279,19 +297,19 @@ class CommandCheck(Command): # Check the remote link works req_headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0 (Nikola)'} # I’m a real boy! - resp = requests.head(target, headers=req_headers, allow_redirects=False) + resp = requests.head(target, headers=req_headers, allow_redirects=False, timeout=self.timeout) # Retry client errors (4xx) as GET requests because many servers are broken if resp.status_code >= 400 and resp.status_code <= 499: time.sleep(0.5) - resp = requests.get(target, headers=req_headers, allow_redirects=False) + resp = requests.get(target, headers=req_headers, allow_redirects=False, timeout=self.timeout) # Follow redirects and see where they lead, redirects to errors will be reported twice if resp.status_code in [301, 302, 307, 308]: redir_status_code = resp.status_code time.sleep(0.5) # Known redirects are retested using GET because IIS servers otherwise get HEADaches - resp = requests.get(target, headers=req_headers, allow_redirects=True) + resp = requests.get(target, headers=req_headers, allow_redirects=True, timeout=self.timeout) # Permanent redirects should be updated if redir_status_code in [301, 308]: self.logger.warning("Remote link moved PERMANENTLY to \"{0}\" and should be updated in {1}: {2} [HTTP: {3}]".format(resp.url, filename, target, redir_status_code)) @@ -353,6 +371,10 @@ class CommandCheck(Command): else: target_filename_str = target_filename.decode("utf-8", errors="surrogateescape") + if ignore_query_strings and "?" in target_filename_str: + target_filename, _, _ = target_filename.rpartition("?") + target_filename_str, _, _ = target_filename_str.rpartition("?") + if any(pattern.search(target_filename_str) for pattern in self.whitelist): continue @@ -371,7 +393,7 @@ class CommandCheck(Command): self.logger.error(u"Error with: {0} {1}".format(filename, exc)) return rv - def scan_links(self, find_sources=False, check_remote=False): + def scan_links(self, find_sources=False, check_remote=False, ignore_query_strings=False): """Check links on the site.""" self.logger.debug("Checking Links:") self.logger.debug("===============\n") @@ -387,13 +409,13 @@ class CommandCheck(Command): for fname in _call_nikola_list(self.site, self.cache)[0]: if fname.startswith(output_folder): if '.html' == fname[-5:]: - if self.analyze(fname, find_sources, check_remote): + if self.analyze(fname, find_sources, check_remote, ignore_query_strings): failure = True if atom_extension == fname[-len(atom_extension):]: - if self.analyze(fname, find_sources, False): + if self.analyze(fname, find_sources, False, ignore_query_strings): failure = True if fname.endswith('sitemap.xml') or fname.endswith('sitemapindex.xml'): - if self.analyze(fname, find_sources, False): + if self.analyze(fname, find_sources, False, ignore_query_strings): failure = True if not failure: self.logger.debug("All links checked.") diff --git a/nikola/plugins/command/console.py b/nikola/plugins/command/console.py index 96fee3e..18428f3 100644 --- a/nikola/plugins/command/console.py +++ b/nikola/plugins/command/console.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Chris Warrick, Roberto Alsina and others. +# Copyright © 2012-2024 Chris Warrick, Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/command/default_config.py b/nikola/plugins/command/default_config.py index fddda26..f14c4c8 100644 --- a/nikola/plugins/command/default_config.py +++ b/nikola/plugins/command/default_config.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -48,7 +48,10 @@ class CommandShowConfig(Command): def _execute(self, options=None, args=None): """Show the default configuration.""" + init_plugin = self.site.plugin_manager.get_plugin_by_name("init", "Command").plugin_object + config = init_plugin.create_configuration_to_string() + try: - print(nikola.plugins.command.init.CommandInit.create_configuration_to_string()) + print(config) except Exception: - sys.stdout.buffer.write(nikola.plugins.command.init.CommandInit.create_configuration_to_string().encode('utf-8')) + sys.stdout.buffer.write(config.encode('utf-8')) diff --git a/nikola/plugins/command/deploy.py b/nikola/plugins/command/deploy.py index 1896a7a..ddb5a64 100644 --- a/nikola/plugins/command/deploy.py +++ b/nikola/plugins/command/deploy.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/command/github_deploy.py b/nikola/plugins/command/github_deploy.py index 1d3e9c0..aa2da74 100644 --- a/nikola/plugins/command/github_deploy.py +++ b/nikola/plugins/command/github_deploy.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2014-2022 Puneeth Chaganti and others. +# Copyright © 2014-2024 Puneeth Chaganti and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/command/import_wordpress.py b/nikola/plugins/command/import_wordpress.py index f3feab1..e6ec45b 100644 --- a/nikola/plugins/command/import_wordpress.py +++ b/nikola/plugins/command/import_wordpress.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -66,7 +66,7 @@ def install_plugin(site, plugin_name, output_dir=None, show_install_notes=False) """Install a Nikola plugin.""" LOGGER.info("Installing plugin '{0}'".format(plugin_name)) # Get hold of the 'plugin' plugin - plugin_installer_info = site.plugin_manager.getPluginByName('plugin', 'Command') + plugin_installer_info = site.plugin_manager.get_plugin_by_name('plugin', 'Command') if plugin_installer_info is None: LOGGER.error('Internal error: cannot find the "plugin" plugin which is supposed to come with Nikola!') return False @@ -236,10 +236,9 @@ to self._find_wordpress_compiler() if self.wordpress_page_compiler is not None: return self.wordpress_page_compiler - plugin_info = self.site.plugin_manager.getPluginByName('markdown', 'PageCompiler') + plugin_info = self.site.plugin_manager.get_plugin_by_name('markdown', 'PageCompiler') if plugin_info is not None: if not plugin_info.is_activated: - self.site.plugin_manager.activatePluginByName(plugin_info.name) plugin_info.plugin_object.set_site(self.site) return plugin_info.plugin_object else: @@ -249,7 +248,7 @@ to """Find WordPress compiler plugin.""" if self.wordpress_page_compiler is not None: return - plugin_info = self.site.plugin_manager.getPluginByName('wordpress', 'PageCompiler') + plugin_info = self.site.plugin_manager.get_plugin_by_name('wordpress', 'PageCompiler') if plugin_info is not None: if not plugin_info.is_activated: self.site.plugin_manager.activatePluginByName(plugin_info.name) diff --git a/nikola/plugins/command/init.py b/nikola/plugins/command/init.py index 4607758..cf22a44 100644 --- a/nikola/plugins/command/init.py +++ b/nikola/plugins/command/init.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/command/new_page.py b/nikola/plugins/command/new_page.py index 94fbc51..6587d70 100644 --- a/nikola/plugins/command/new_page.py +++ b/nikola/plugins/command/new_page.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina, Chris Warrick and others. +# Copyright © 2012-2024 Roberto Alsina, Chris Warrick and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -109,5 +109,5 @@ class CommandNewPage(Command): options['date-path'] = False # Even though stuff was split into `new_page`, it’s easier to do it # there not to duplicate the code. - p = self.site.plugin_manager.getPluginByName('new_post', 'Command').plugin_object + p = self.site.plugin_manager.get_plugin_by_name('new_post', 'Command').plugin_object return p.execute(options, args) diff --git a/nikola/plugins/command/new_post.py b/nikola/plugins/command/new_post.py index f66a188..aa96625 100644 --- a/nikola/plugins/command/new_post.py +++ b/nikola/plugins/command/new_post.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -216,9 +216,7 @@ class CommandNewPost(Command): def _execute(self, options, args): """Create a new post or page.""" global LOGGER - compiler_names = [p.name for p in - self.site.plugin_manager.getPluginsOfCategory( - "PageCompiler")] + compiler_names = [p.name for p in self.site.plugin_manager.get_plugins_of_category("PageCompiler")] if len(args) > 1: print(self.help()) @@ -298,7 +296,7 @@ class CommandNewPost(Command): self.print_compilers() return - compiler_plugin = self.site.plugin_manager.getPluginByName( + compiler_plugin = self.site.plugin_manager.get_plugin_by_name( content_format, "PageCompiler").plugin_object # Guess where we should put this diff --git a/nikola/plugins/command/orphans.py b/nikola/plugins/command/orphans.py index 169cbba..bde8425 100644 --- a/nikola/plugins/command/orphans.py +++ b/nikola/plugins/command/orphans.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina, Chris Warrick and others. +# Copyright © 2012-2024 Roberto Alsina, Chris Warrick and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/command/plugin.py b/nikola/plugins/command/plugin.py index ae0dead..4fd8e8f 100644 --- a/nikola/plugins/command/plugin.py +++ b/nikola/plugins/command/plugin.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/command/rst2html/__init__.py b/nikola/plugins/command/rst2html/__init__.py index 2bf329a..4fdd36f 100644 --- a/nikola/plugins/command/rst2html/__init__.py +++ b/nikola/plugins/command/rst2html/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2015-2022 Chris Warrick and others. +# Copyright © 2015-2024 Chris Warrick and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -44,7 +44,7 @@ class CommandRst2Html(Command): def _execute(self, options, args): """Compile reStructuredText to standalone HTML files.""" - compiler = self.site.plugin_manager.getPluginByName('rest', 'PageCompiler').plugin_object + compiler = self.site.plugin_manager.get_plugin_by_name('rest', 'PageCompiler').plugin_object if len(args) != 1: print("This command takes only one argument (input file name).") return 2 diff --git a/nikola/plugins/command/serve.py b/nikola/plugins/command/serve.py index cbf628c..32cd46b 100644 --- a/nikola/plugins/command/serve.py +++ b/nikola/plugins/command/serve.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/command/status.py b/nikola/plugins/command/status.py index ab6fc1e..a22f173 100644 --- a/nikola/plugins/command/status.py +++ b/nikola/plugins/command/status.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/command/subtheme.py b/nikola/plugins/command/subtheme.py index b5c5aff..c322336 100644 --- a/nikola/plugins/command/subtheme.py +++ b/nikola/plugins/command/subtheme.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/command/theme.py b/nikola/plugins/command/theme.py index 2f99dd8..f7608b5 100644 --- a/nikola/plugins/command/theme.py +++ b/nikola/plugins/command/theme.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina, Chris Warrick and others. +# Copyright © 2012-2024 Roberto Alsina, Chris Warrick and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/command/version.py b/nikola/plugins/command/version.py index 4ec8a46..bdc1357 100644 --- a/nikola/plugins/command/version.py +++ b/nikola/plugins/command/version.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/compile/__init__.py b/nikola/plugins/compile/__init__.py index c3abcd4..4517b31 100644 --- a/nikola/plugins/compile/__init__.py +++ b/nikola/plugins/compile/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/compile/html.py b/nikola/plugins/compile/html.py index ff7d37f..9dbf96e 100644 --- a/nikola/plugins/compile/html.py +++ b/nikola/plugins/compile/html.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/compile/ipynb.py b/nikola/plugins/compile/ipynb.py index 7d6e528..df1b29a 100644 --- a/nikola/plugins/compile/ipynb.py +++ b/nikola/plugins/compile/ipynb.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2013-2022 Damián Avila, Chris Warrick and others. +# Copyright © 2013-2024 Damián Avila, Chris Warrick and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/compile/markdown/__init__.py b/nikola/plugins/compile/markdown/__init__.py index 31a57d9..052413f 100644 --- a/nikola/plugins/compile/markdown/__init__.py +++ b/nikola/plugins/compile/markdown/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/compile/markdown/mdx_nikola.py b/nikola/plugins/compile/markdown/mdx_nikola.py index af30956..ba14319 100644 --- a/nikola/plugins/compile/markdown/mdx_nikola.py +++ b/nikola/plugins/compile/markdown/mdx_nikola.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/compile/markdown/mdx_podcast.py b/nikola/plugins/compile/markdown/mdx_podcast.py index e003f40..0be09a8 100644 --- a/nikola/plugins/compile/markdown/mdx_podcast.py +++ b/nikola/plugins/compile/markdown/mdx_podcast.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright © 2013-2022 Michael Rabbitt, Roberto Alsina and others. +# Copyright © 2013-2024 Michael Rabbitt, Roberto Alsina and others. # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the diff --git a/nikola/plugins/compile/pandoc.py b/nikola/plugins/compile/pandoc.py index a43c8b0..4082d1d 100644 --- a/nikola/plugins/compile/pandoc.py +++ b/nikola/plugins/compile/pandoc.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -62,10 +62,10 @@ class CompilePandoc(PageCompiler): try: pandoc_options = list(config_options[ext]) except KeyError: - self.logger.warn('Setting PANDOC_OPTIONS to [], because extension {} is not defined in PANDOC_OPTIONS: {}.'.format(ext, config_options)) + self.logger.warning('Setting PANDOC_OPTIONS to [], because extension {} is not defined in PANDOC_OPTIONS: {}.'.format(ext, config_options)) pandoc_options = [] else: - self.logger.warn('Setting PANDOC_OPTIONS to [], because PANDOC_OPTIONS is expected to be of type Union[List[str], Dict[str, List[str]]] but this is not: {}'.format(config_options)) + self.logger.warning('Setting PANDOC_OPTIONS to [], because PANDOC_OPTIONS is expected to be of type Union[List[str], Dict[str, List[str]]] but this is not: {}'.format(config_options)) pandoc_options = [] return pandoc_options diff --git a/nikola/plugins/compile/php.py b/nikola/plugins/compile/php.py index d4c67dc..09f2e09 100644 --- a/nikola/plugins/compile/php.py +++ b/nikola/plugins/compile/php.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/compile/rest/__init__.py b/nikola/plugins/compile/rest/__init__.py index e5d3998..e252bae 100644 --- a/nikola/plugins/compile/rest/__init__.py +++ b/nikola/plugins/compile/rest/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -77,7 +77,7 @@ class CompileRest(PageCompiler): meta = {} if 'title' in document: meta['title'] = document['title'] - for docinfo in document.traverse(docutils.nodes.docinfo): + for docinfo in document.findall(docutils.nodes.docinfo): for element in docinfo.children: if element.tagname == 'field': # custom fields (e.g. summary) name_elem, body_elem = element.children diff --git a/nikola/plugins/compile/rest/chart.py b/nikola/plugins/compile/rest/chart.py index 15ccee7..3f3c0dc 100644 --- a/nikola/plugins/compile/rest/chart.py +++ b/nikola/plugins/compile/rest/chart.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -153,7 +153,7 @@ class Chart(Directive): def run(self): """Run the directive.""" self.options['site'] = None - html = _site.plugin_manager.getPluginByName( + html = _site.plugin_manager.get_plugin_by_name( 'chart', 'ShortcodePlugin').plugin_object.handler( self.arguments[0], data='\n'.join(self.content), diff --git a/nikola/plugins/compile/rest/doc.py b/nikola/plugins/compile/rest/doc.py index 1d88472..b884aaa 100644 --- a/nikola/plugins/compile/rest/doc.py +++ b/nikola/plugins/compile/rest/doc.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/compile/rest/listing.py b/nikola/plugins/compile/rest/listing.py index 48dbe4c..d1b64f0 100644 --- a/nikola/plugins/compile/rest/listing.py +++ b/nikola/plugins/compile/rest/listing.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/compile/rest/media.py b/nikola/plugins/compile/rest/media.py index 6d0436d..0611f6f 100644 --- a/nikola/plugins/compile/rest/media.py +++ b/nikola/plugins/compile/rest/media.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/compile/rest/post_list.py b/nikola/plugins/compile/rest/post_list.py index 1799790..8f2b446 100644 --- a/nikola/plugins/compile/rest/post_list.py +++ b/nikola/plugins/compile/rest/post_list.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2013-2022 Udo Spallek, Roberto Alsina and others. +# Copyright © 2013-2024 Udo Spallek, Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -88,7 +88,7 @@ class PostListDirective(Directive): date = self.options.get('date') filename = self.state.document.settings._nikola_source_path - output, deps = self.site.plugin_manager.getPluginByName( + output, deps = self.site.plugin_manager.get_plugin_by_name( 'post_list', 'ShortcodePlugin').plugin_object.handler( start, stop, diff --git a/nikola/plugins/compile/rest/soundcloud.py b/nikola/plugins/compile/rest/soundcloud.py index 87b1483..71a82a1 100644 --- a/nikola/plugins/compile/rest/soundcloud.py +++ b/nikola/plugins/compile/rest/soundcloud.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/compile/rest/thumbnail.py b/nikola/plugins/compile/rest/thumbnail.py index 6f10a7f..af075bb 100644 --- a/nikola/plugins/compile/rest/thumbnail.py +++ b/nikola/plugins/compile/rest/thumbnail.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2014-2022 Pelle Nilsson and others. +# Copyright © 2014-2024 Pelle Nilsson and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/compile/rest/vimeo.py b/nikola/plugins/compile/rest/vimeo.py index b4f89ff..7c5dd6f 100644 --- a/nikola/plugins/compile/rest/vimeo.py +++ b/nikola/plugins/compile/rest/vimeo.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/compile/rest/youtube.py b/nikola/plugins/compile/rest/youtube.py index de3f2fa..ef46822 100644 --- a/nikola/plugins/compile/rest/youtube.py +++ b/nikola/plugins/compile/rest/youtube.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/misc/__init__.py b/nikola/plugins/misc/__init__.py index 0572088..41c38f7 100644 --- a/nikola/plugins/misc/__init__.py +++ b/nikola/plugins/misc/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/misc/scan_posts.plugin b/nikola/plugins/misc/scan_posts.plugin index f4af811..0fb946c 100644 --- a/nikola/plugins/misc/scan_posts.plugin +++ b/nikola/plugins/misc/scan_posts.plugin @@ -8,3 +8,5 @@ Version = 1.0 Website = https://getnikola.com/ Description = Scan posts and create timeline +[Nikola] +PluginCategory = PostScanner diff --git a/nikola/plugins/misc/scan_posts.py b/nikola/plugins/misc/scan_posts.py index efa797e..bc63631 100644 --- a/nikola/plugins/misc/scan_posts.py +++ b/nikola/plugins/misc/scan_posts.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index c6092b3..f53fe54 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/shortcode/chart.plugin b/nikola/plugins/shortcode/chart.plugin index edcbc13..be1fbc6 100644 --- a/nikola/plugins/shortcode/chart.plugin +++ b/nikola/plugins/shortcode/chart.plugin @@ -3,7 +3,7 @@ name = chart module = chart [Nikola] -PluginCategory = Shortcode +PluginCategory = ShortcodePlugin [Documentation] author = Roberto Alsina diff --git a/nikola/plugins/shortcode/chart.py b/nikola/plugins/shortcode/chart.py index 2b88cfb..fb45245 100644 --- a/nikola/plugins/shortcode/chart.py +++ b/nikola/plugins/shortcode/chart.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/shortcode/emoji.plugin b/nikola/plugins/shortcode/emoji.plugin index c9a272c..4c09f03 100644 --- a/nikola/plugins/shortcode/emoji.plugin +++ b/nikola/plugins/shortcode/emoji.plugin @@ -3,7 +3,7 @@ name = emoji module = emoji [Nikola] -PluginCategory = Shortcode +PluginCategory = ShortcodePlugin [Documentation] author = Roberto Alsina diff --git a/nikola/plugins/shortcode/gist.plugin b/nikola/plugins/shortcode/gist.plugin index b610763..ee62c27 100644 --- a/nikola/plugins/shortcode/gist.plugin +++ b/nikola/plugins/shortcode/gist.plugin @@ -3,7 +3,7 @@ name = gist module = gist [Nikola] -PluginCategory = Shortcode +PluginCategory = ShortcodePlugin [Documentation] author = Roberto Alsina diff --git a/nikola/plugins/shortcode/listing.plugin b/nikola/plugins/shortcode/listing.plugin index 90fb6eb..70fa1cf 100644 --- a/nikola/plugins/shortcode/listing.plugin +++ b/nikola/plugins/shortcode/listing.plugin @@ -3,7 +3,7 @@ name = listing_shortcode module = listing [Nikola] -PluginCategory = Shortcode +PluginCategory = ShortcodePlugin [Documentation] author = Roberto Alsina diff --git a/nikola/plugins/shortcode/listing.py b/nikola/plugins/shortcode/listing.py index 3046655..33b0d44 100644 --- a/nikola/plugins/shortcode/listing.py +++ b/nikola/plugins/shortcode/listing.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2017-2022 Roberto Alsina and others. +# Copyright © 2017-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/shortcode/post_list.plugin b/nikola/plugins/shortcode/post_list.plugin index 494a1d8..9c39eb9 100644 --- a/nikola/plugins/shortcode/post_list.plugin +++ b/nikola/plugins/shortcode/post_list.plugin @@ -3,7 +3,7 @@ name = post_list module = post_list [Nikola] -PluginCategory = Shortcode +PluginCategory = ShortcodePlugin [Documentation] author = Udo Spallek diff --git a/nikola/plugins/shortcode/post_list.py b/nikola/plugins/shortcode/post_list.py index 5b24cc6..df2d022 100644 --- a/nikola/plugins/shortcode/post_list.py +++ b/nikola/plugins/shortcode/post_list.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2013-2022 Udo Spallek, Roberto Alsina and others. +# Copyright © 2013-2024 Udo Spallek, Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/shortcode/thumbnail.plugin b/nikola/plugins/shortcode/thumbnail.plugin index e55d34f..bd36169 100644 --- a/nikola/plugins/shortcode/thumbnail.plugin +++ b/nikola/plugins/shortcode/thumbnail.plugin @@ -3,7 +3,7 @@ name = thumbnail module = thumbnail [Nikola] -PluginCategory = Shortcode +PluginCategory = ShortcodePlugin [Documentation] author = Chris Warrick diff --git a/nikola/plugins/shortcode/thumbnail.py b/nikola/plugins/shortcode/thumbnail.py index 7a05320..67b235c 100644 --- a/nikola/plugins/shortcode/thumbnail.py +++ b/nikola/plugins/shortcode/thumbnail.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2017-2022 Roberto Alsina, Chris Warrick and others. +# Copyright © 2017-2024 Roberto Alsina, Chris Warrick and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/__init__.py b/nikola/plugins/task/__init__.py index 10c54d0..174d82e 100644 --- a/nikola/plugins/task/__init__.py +++ b/nikola/plugins/task/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index f083394..44849fa 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index d966a4f..6a9ab81 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2015-2022 Juanjo Conti and others. +# Copyright © 2015-2024 Juanjo Conti and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/bundles.plugin b/nikola/plugins/task/bundles.plugin index 939065b..4afc3ec 100644 --- a/nikola/plugins/task/bundles.plugin +++ b/nikola/plugins/task/bundles.plugin @@ -9,5 +9,5 @@ website = https://getnikola.com/ description = Bundle assets [Nikola] -PluginCategory = Task +PluginCategory = LateTask diff --git a/nikola/plugins/task/bundles.py b/nikola/plugins/task/bundles.py index c71c255..b0aeb42 100644 --- a/nikola/plugins/task/bundles.py +++ b/nikola/plugins/task/bundles.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/categories.py b/nikola/plugins/task/categories.py index 11a0407..51b7f6f 100644 --- a/nikola/plugins/task/categories.py +++ b/nikola/plugins/task/categories.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/copy_assets.py b/nikola/plugins/task/copy_assets.py index fd22e0d..519a552 100644 --- a/nikola/plugins/task/copy_assets.py +++ b/nikola/plugins/task/copy_assets.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/copy_files.py b/nikola/plugins/task/copy_files.py index 3818808..23ba5cd 100644 --- a/nikola/plugins/task/copy_files.py +++ b/nikola/plugins/task/copy_files.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/galleries.py b/nikola/plugins/task/galleries.py index a780d76..50a2877 100644 --- a/nikola/plugins/task/galleries.py +++ b/nikola/plugins/task/galleries.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -32,6 +32,7 @@ import io import json import mimetypes import os +import pathlib from collections import OrderedDict from urllib.parse import urljoin @@ -544,7 +545,7 @@ class Galleries(Task, ImageProcessor): except IOError: excluded_image_name_list = [] - excluded_image_list = ["{0}/{1}".format(gallery_path, i) for i in excluded_image_name_list] + excluded_image_list = [os.path.join(gallery_path, i) for i in excluded_image_name_list] return excluded_image_list def get_image_list(self, gallery_path): @@ -737,6 +738,10 @@ class Galleries(Task, ImageProcessor): else: img_list, dest_img_list, img_titles = [], [], [] + def forward_slashes(path): + """Given a path, convert directory separators to forward slash, on all platforms.""" + return str(pathlib.PurePosixPath(*path.split(os.path.sep))) + items = [] for img, srcimg, title in list(zip(dest_img_list, img_list, img_titles))[:self.kw["feed_length"]]: img_size = os.stat( @@ -744,11 +749,11 @@ class Galleries(Task, ImageProcessor): self.site.config['OUTPUT_FOLDER'], img)).st_size args = { 'title': title, - 'link': make_url(img), - 'guid': rss.Guid(img, False), + 'link': make_url(forward_slashes(img)), + 'guid': rss.Guid(forward_slashes(img), False), 'pubDate': self.image_date(srcimg), 'enclosure': rss.Enclosure( - make_url(img), + make_url(forward_slashes(img)), img_size, mimetypes.guess_type(img)[0] ), diff --git a/nikola/plugins/task/gzip.plugin b/nikola/plugins/task/gzip.plugin index cc078b7..b1aab25 100644 --- a/nikola/plugins/task/gzip.plugin +++ b/nikola/plugins/task/gzip.plugin @@ -4,10 +4,10 @@ module = gzip [Documentation] author = Roberto Alsina -version = 1.0 +version = 1.1 website = https://getnikola.com/ description = Create gzipped copies of files [Nikola] -PluginCategory = Task +PluginCategory = TaskMultiplier diff --git a/nikola/plugins/task/gzip.py b/nikola/plugins/task/gzip.py index 62523c7..9061807 100644 --- a/nikola/plugins/task/gzip.py +++ b/nikola/plugins/task/gzip.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -30,6 +30,7 @@ import gzip import os import shlex import subprocess +import sys from nikola.plugin_categories import TaskMultiplier @@ -73,8 +74,11 @@ class GzipFiles(TaskMultiplier): def create_gzipped_copy(in_path, out_path, command=None): """Create gzipped copy of in_path and save it as out_path.""" if command: - subprocess.check_call(shlex.split(command.format(filename=in_path))) + if sys.platform == 'win32': + subprocess.check_call(command.format(filename=in_path)) + else: + subprocess.check_call(shlex.split(command.format(filename=in_path))) else: - with gzip.GzipFile(out_path, 'wb+') as outf: + with gzip.GzipFile(out_path, 'wb+', mtime=0) as outf: with open(in_path, 'rb') as inf: outf.write(inf.read()) diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index 93c119b..8af550d 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/listings.py b/nikola/plugins/task/listings.py index 510411a..dde1c69 100644 --- a/nikola/plugins/task/listings.py +++ b/nikola/plugins/task/listings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -107,12 +107,13 @@ class Listings(Task): """Render pretty code listings.""" # Things to ignore in listings ignored_extensions = (".pyc", ".pyo") + ignored_files = (".DS_Store",) def render_listing(in_name, out_name, input_folder, output_folder, folders=[], files=[]): needs_ipython_css = False if in_name and in_name.endswith('.ipynb'): # Special handling: render ipynbs in listings (Issue #1900) - ipynb_plugin = self.site.plugin_manager.getPluginByName("ipynb", "PageCompiler") + ipynb_plugin = self.site.plugin_manager.get_plugin_by_name("ipynb", "PageCompiler") if ipynb_plugin is None: msg = "To use .ipynb files as listings, you must set up the Jupyter compiler in COMPILERS and POSTS/PAGES." utils.LOGGER.error(msg) @@ -183,7 +184,9 @@ class Listings(Task): for input_folder, output_folder in self.kw['listings_folders'].items(): for root, dirs, files in os.walk(input_folder, followlinks=True): - files = [f for f in files if os.path.splitext(f)[-1] not in ignored_extensions] + files = [f for f in files + if os.path.splitext(f)[-1] not in ignored_extensions and + f not in ignored_files] uptodate = {'c': self.site.GLOBAL_CONTEXT} @@ -224,7 +227,7 @@ class Listings(Task): 'clean': True, }, self.kw["filters"]) for f in files: - if f == '.DS_Store': + if f in ignored_files: continue ext = os.path.splitext(f)[-1] if ext in ignored_extensions: diff --git a/nikola/plugins/task/page_index.py b/nikola/plugins/task/page_index.py index 4002e5c..9fb2a2d 100644 --- a/nikola/plugins/task/page_index.py +++ b/nikola/plugins/task/page_index.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -106,6 +106,6 @@ class PageIndex(Taxonomy): short_destination = dirname + '/' + self.site.config['INDEX_FILE'] for post in post_list: # If there is an index.html pending to be created from a page, do not generate the page index. - if post.destination_path(lang, sep='/') == short_destination: + if post.destination_path(lang, sep='/').lstrip('/') == short_destination.lstrip('/'): return False return True diff --git a/nikola/plugins/task/pages.py b/nikola/plugins/task/pages.py index d30cdd0..ae26735 100644 --- a/nikola/plugins/task/pages.py +++ b/nikola/plugins/task/pages.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/posts.py b/nikola/plugins/task/posts.py index 86c85ae..242ee76 100644 --- a/nikola/plugins/task/posts.py +++ b/nikola/plugins/task/posts.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -26,6 +26,7 @@ """Build HTML fragments from metadata and text.""" +import docutils import os from copy import copy @@ -57,6 +58,7 @@ class RenderPosts(Task): "default_lang": self.site.config["DEFAULT_LANG"], "show_untranslated_posts": self.site.config['SHOW_UNTRANSLATED_POSTS'], "demote_headers": self.site.config['DEMOTE_HEADERS'], + "docutils_version": docutils.__version__, } self.tl_changed = False diff --git a/nikola/plugins/task/redirect.py b/nikola/plugins/task/redirect.py index b1262a0..5861eba 100644 --- a/nikola/plugins/task/redirect.py +++ b/nikola/plugins/task/redirect.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/robots.plugin b/nikola/plugins/task/robots.plugin index 51f7781..81c4c9a 100644 --- a/nikola/plugins/task/robots.plugin +++ b/nikola/plugins/task/robots.plugin @@ -9,5 +9,5 @@ website = https://getnikola.com/ description = Generate /robots.txt exclusion file and promote sitemap. [Nikola] -PluginCategory = Task +PluginCategory = LateTask diff --git a/nikola/plugins/task/robots.py b/nikola/plugins/task/robots.py index e1d8d00..ff7f67f 100644 --- a/nikola/plugins/task/robots.py +++ b/nikola/plugins/task/robots.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/scale_images.py b/nikola/plugins/task/scale_images.py index f317a3f..8f1262a 100644 --- a/nikola/plugins/task/scale_images.py +++ b/nikola/plugins/task/scale_images.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2014-2022 Pelle Nilsson and others. +# Copyright © 2014-2024 Pelle Nilsson and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/sitemap.plugin b/nikola/plugins/task/sitemap.plugin index c8aa832..8367d8e 100644 --- a/nikola/plugins/task/sitemap.plugin +++ b/nikola/plugins/task/sitemap.plugin @@ -9,5 +9,5 @@ website = https://getnikola.com/ description = Generate google sitemap. [Nikola] -PluginCategory = Task +PluginCategory = LateTask diff --git a/nikola/plugins/task/sitemap.py b/nikola/plugins/task/sitemap.py index f99f2de..7d40fac 100644 --- a/nikola/plugins/task/sitemap.py +++ b/nikola/plugins/task/sitemap.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -309,7 +309,7 @@ class Sitemap(LateTask): # RFC 3339 (web ISO 8601 profile) represented in UTC with Zulu # zone desgignator as recommeded for sitemaps. Second and # microsecond precision is stripped for compatibility. - lastmod = datetime.datetime.utcfromtimestamp(os.stat(p).st_mtime).replace(tzinfo=dateutil.tz.gettz('UTC'), second=0, microsecond=0).isoformat().replace('+00:00', 'Z') + lastmod = datetime.datetime.fromtimestamp(os.stat(p).st_mtime, dateutil.tz.tzutc()).replace(second=0, microsecond=0).isoformat().replace('+00:00', 'Z') return lastmod diff --git a/nikola/plugins/task/sources.py b/nikola/plugins/task/sources.py index 107c8fb..4cf376c 100644 --- a/nikola/plugins/task/sources.py +++ b/nikola/plugins/task/sources.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/tags.py b/nikola/plugins/task/tags.py index 4b0cd8c..61bbd4d 100644 --- a/nikola/plugins/task/tags.py +++ b/nikola/plugins/task/tags.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 719cf46..55dfa36 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/template/__init__.py b/nikola/plugins/template/__init__.py index 66f98f7..e989a15 100644 --- a/nikola/plugins/template/__init__.py +++ b/nikola/plugins/template/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/template/jinja.plugin b/nikola/plugins/template/jinja.plugin index 629b20e..45dd621 100644 --- a/nikola/plugins/template/jinja.plugin +++ b/nikola/plugins/template/jinja.plugin @@ -9,5 +9,5 @@ website = https://getnikola.com/ description = Support for Jinja2 templates. [Nikola] -PluginCategory = Template +PluginCategory = TemplateSystem diff --git a/nikola/plugins/template/jinja.py b/nikola/plugins/template/jinja.py index 8aa32aa..e0ddf4a 100644 --- a/nikola/plugins/template/jinja.py +++ b/nikola/plugins/template/jinja.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/template/mako.plugin b/nikola/plugins/template/mako.plugin index 2d353bf..a465752 100644 --- a/nikola/plugins/template/mako.plugin +++ b/nikola/plugins/template/mako.plugin @@ -9,5 +9,5 @@ website = https://getnikola.com/ description = Support for Mako templates. [Nikola] -PluginCategory = Template +PluginCategory = TemplateSystem diff --git a/nikola/plugins/template/mako.py b/nikola/plugins/template/mako.py index 5dc9fe5..9517b05 100644 --- a/nikola/plugins/template/mako.py +++ b/nikola/plugins/template/mako.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/post.py b/nikola/post.py index 42df0eb..792e6f0 100644 --- a/nikola/post.py +++ b/nikola/post.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -350,8 +350,8 @@ class Post(object): if self.config['__invariant__']: default_metadata['date'] = datetime.datetime(2013, 12, 31, 23, 59, 59, tzinfo=self.config['__tzinfo__']) else: - default_metadata['date'] = datetime.datetime.utcfromtimestamp( - os.stat(self.source_path).st_ctime).replace(tzinfo=dateutil.tz.tzutc()).astimezone(self.config['__tzinfo__']) + default_metadata['date'] = datetime.datetime.fromtimestamp( + os.stat(self.source_path).st_ctime, dateutil.tz.tzutc()).astimezone(self.config['__tzinfo__']) # If time zone is set, build localized datetime. try: @@ -1213,12 +1213,12 @@ def get_meta(post, lang): if lang is None: # Only perform these checks for the default language - if 'slug' not in meta: + if 'slug' not in meta or not meta['slug']: # If no slug is found in the metadata use the filename meta['slug'] = slugify(os.path.splitext( os.path.basename(post.source_path))[0], post.default_lang) - if 'title' not in meta: + if 'title' not in meta or not meta['title']: # If no title is found, use the filename without extension meta['title'] = os.path.splitext( os.path.basename(post.source_path))[0] diff --git a/nikola/shortcodes.py b/nikola/shortcodes.py index cccea2d..8b9c643 100644 --- a/nikola/shortcodes.py +++ b/nikola/shortcodes.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/state.py b/nikola/state.py index a054d69..59cd3fc 100644 --- a/nikola/state.py +++ b/nikola/state.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/utils.py b/nikola/utils.py index 7157afb..20e3552 100644 --- a/nikola/utils.py +++ b/nikola/utils.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -669,11 +669,9 @@ def html_tostring_fragment(document): for start in start_fragments: if doc.startswith(start): doc = doc[len(start):].strip() - print(repr(doc)) for end in end_fragments: if doc.endswith(end): doc = doc[:-len(end)].strip() - print(repr(doc)) return doc @@ -731,7 +729,7 @@ def load_messages(themes, translations, default_lang, themes_dirs): del translation except ImportError as orig: last_exception = orig - del(english) + del english sys.path = oldpath if not all(found.values()): diff --git a/nikola/winutils.py b/nikola/winutils.py index b5b8ffa..8f4e8f5 100644 --- a/nikola/winutils.py +++ b/nikola/winutils.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2022 Roberto Alsina and others. +# Copyright © 2012-2024 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated |
