summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPierre <pierre.equoy@protonmail.com>2022-02-20 10:29:06 +0800
committerGitHub <noreply@github.com>2022-02-20 10:29:06 +0800
commit34ca2e1de23a7b3afa87bd7afcf5801801938dcd (patch)
tree171e5cce85de88a59eeb515fa5042385af6222c4
parent4794752dd9ecd4f80eac52f84bd15abbf5bb9d4b (diff)
parent77e5381be73347d7f1ad6e616d1460ca023e920f (diff)
downloadpelican-34ca2e1de23a7b3afa87bd7afcf5801801938dcd.tar.gz
Merge branch 'getpelican:master' into master
-rw-r--r--docs/changelog.rst7
-rw-r--r--docs/settings.rst9
-rw-r--r--pelican/__init__.py50
-rw-r--r--pelican/cache.py2
-rw-r--r--pelican/settings.py23
-rw-r--r--pelican/tests/output/basic/categories.html6
-rw-r--r--pelican/tests/output/custom/categories.html6
-rw-r--r--pelican/tests/output/custom_locale/categories.html6
-rw-r--r--pelican/tests/test_cli.py72
-rw-r--r--pelican/tests/test_settings.py17
-rw-r--r--pelican/themes/notmyidea/templates/categories.html16
-rw-r--r--pyproject.toml4
-rw-r--r--requirements/docs.pip2
-rwxr-xr-xsetup.py2
14 files changed, 154 insertions, 68 deletions
diff --git a/docs/changelog.rst b/docs/changelog.rst
index b76a4575..576e3d0c 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,6 +1,13 @@
Release history
###############
+4.7.2 - 2022-02-09
+==================
+
+* Fix incorrect parsing of parameters specified via `-e` / `--extra-settings` option flags `(#2938) <https://github.com/getpelican/pelican/pull/2938>`_
+* Add ``categories.html`` template to default theme `(#2973) <https://github.com/getpelican/pelican/pull/2973>`_
+* Document how to use plugins to inject content `(#2922) <https://github.com/getpelican/pelican/pull/2922>`_
+
4.7.1 - 2021-10-12
==================
diff --git a/docs/settings.rst b/docs/settings.rst
index 47852527..675e6b8f 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -9,11 +9,12 @@ line::
If you used the ``pelican-quickstart`` command, your primary settings file will
be named ``pelicanconf.py`` by default.
-You can also specify extra settings via ``-e`` / ``--extra-settings`` option
-flags, which will override default settings as well as any defined within
-settings files::
+You can also specify settings via ``-e`` / ``--extra-settings`` option
+flags. It will override default settings as well as any defined within the
+setting file. Note that values must follow JSON notation::
+
+ pelican content -e SITENAME='"A site"' READERS='{"html": null}' CACHE_CONTENT=true
- pelican content -e DELETE_OUTPUT_DIRECTORY=true
.. note::
diff --git a/pelican/__init__.py b/pelican/__init__.py
index 99aa2776..9858dbd3 100644
--- a/pelican/__init__.py
+++ b/pelican/__init__.py
@@ -1,4 +1,5 @@
import argparse
+import json
import logging
import multiprocessing
import os
@@ -24,7 +25,7 @@ from pelican.plugins import signals
from pelican.plugins._utils import get_plugin_name, load_plugins
from pelican.readers import Readers
from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer
-from pelican.settings import coerce_overrides, read_settings
+from pelican.settings import read_settings
from pelican.utils import (FileSystemWatcher, clean_output_dir, maybe_pluralize)
from pelican.writers import Writer
@@ -259,16 +260,29 @@ class PrintSettings(argparse.Action):
parser.exit()
-class ParseDict(argparse.Action):
+class ParseOverrides(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
- d = {}
- if values:
- for item in values:
- split_items = item.split("=", 1)
- key = split_items[0].strip()
- value = split_items[1].strip()
- d[key] = value
- setattr(namespace, self.dest, d)
+ overrides = {}
+ for item in values:
+ try:
+ k, v = item.split("=", 1)
+ except ValueError:
+ raise ValueError(
+ 'Extra settings must be specified as KEY=VALUE pairs '
+ f'but you specified {item}'
+ )
+ try:
+ overrides[k] = json.loads(v)
+ except json.decoder.JSONDecodeError:
+ raise ValueError(
+ f'Invalid JSON value: {v}. '
+ 'Values specified via -e / --extra-settings flags '
+ 'must be in JSON notation. '
+ 'Use -e KEY=\'"string"\' to specify a string value; '
+ '-e KEY=null to specify None; '
+ '-e KEY=false (or true) to specify False (or True).'
+ )
+ setattr(namespace, self.dest, overrides)
def parse_arguments(argv=None):
@@ -366,13 +380,13 @@ def parse_arguments(argv=None):
parser.add_argument('-e', '--extra-settings', dest='overrides',
help='Specify one or more SETTING=VALUE pairs to '
- 'override settings. If VALUE contains spaces, '
- 'add quotes: SETTING="VALUE". Values other than '
- 'integers and strings can be specified via JSON '
- 'notation. (e.g., SETTING=none)',
+ 'override settings. VALUE must be in JSON notation: '
+ 'specify string values as SETTING=\'"some string"\'; '
+ 'booleans as SETTING=true or SETTING=false; '
+ 'None as SETTING=null.',
nargs='*',
- action=ParseDict
- )
+ action=ParseOverrides,
+ default={})
args = parser.parse_args(argv)
@@ -385,6 +399,8 @@ def parse_arguments(argv=None):
def get_config(args):
+ """Builds a config dictionary based on supplied `args`.
+ """
config = {}
if args.path:
config['PATH'] = os.path.abspath(os.path.expanduser(args.path))
@@ -409,7 +425,7 @@ def get_config(args):
if args.bind is not None:
config['BIND'] = args.bind
config['DEBUG'] = args.verbosity == logging.DEBUG
- config.update(coerce_overrides(args.overrides))
+ config.update(args.overrides)
return config
diff --git a/pelican/cache.py b/pelican/cache.py
index c1d4a55d..d2665691 100644
--- a/pelican/cache.py
+++ b/pelican/cache.py
@@ -74,7 +74,7 @@ class FileStampDataCacher(FileDataCacher):
"""Subclass that also caches the stamp of the file"""
def __init__(self, settings, cache_name, caching_policy, load_policy):
- """This sublcass additionally sets filestamp function
+ """This subclass additionally sets filestamp function
and base path for filestamping operations
"""
diff --git a/pelican/settings.py b/pelican/settings.py
index ea3ee8eb..5b495e86 100644
--- a/pelican/settings.py
+++ b/pelican/settings.py
@@ -1,7 +1,6 @@
import copy
import importlib.util
import inspect
-import json
import locale
import logging
import os
@@ -659,25 +658,3 @@ def configure_settings(settings):
continue # setting not specified, nothing to do
return settings
-
-
-def coerce_overrides(overrides):
- if overrides is None:
- return {}
- coerced = {}
- types_to_cast = {int, str, bool}
- for k, v in overrides.items():
- if k not in DEFAULT_CONFIG:
- logger.warning('Override for unknown setting %s, ignoring', k)
- continue
- setting_type = type(DEFAULT_CONFIG[k])
- if setting_type not in types_to_cast:
- coerced[k] = json.loads(v)
- else:
- try:
- coerced[k] = setting_type(v)
- except ValueError:
- logger.debug('ValueError for %s override with %s, try to '
- 'load as json', k, v)
- coerced[k] = json.loads(v)
- return coerced
diff --git a/pelican/tests/output/basic/categories.html b/pelican/tests/output/basic/categories.html
index 930503a8..f6c17dd5 100644
--- a/pelican/tests/output/basic/categories.html
+++ b/pelican/tests/output/basic/categories.html
@@ -22,13 +22,17 @@
<li><a href="/category/yeah.html">yeah</a></li>
</ul></nav>
</header><!-- /#banner -->
- <h1>Categories on A Pelican Blog</h1>
+
+ <section id="content" class="body">
+ <h1>Categories for A Pelican Blog</h1>
<ul>
<li><a href="/category/bar.html">bar</a> (1)</li>
<li><a href="/category/cat1.html">cat1</a> (4)</li>
<li><a href="/category/misc.html">misc</a> (4)</li>
<li><a href="/category/yeah.html">yeah</a> (1)</li>
</ul>
+ </section>
+
<section id="extras" class="body">
<div class="social">
<h2>social</h2>
diff --git a/pelican/tests/output/custom/categories.html b/pelican/tests/output/custom/categories.html
index 543eb1ee..74990d4b 100644
--- a/pelican/tests/output/custom/categories.html
+++ b/pelican/tests/output/custom/categories.html
@@ -26,13 +26,17 @@
<li><a href="./category/bar.html">bar</a></li>
</ul></nav>
</header><!-- /#banner -->
- <h1>Categories on Alexis' log</h1>
+
+ <section id="content" class="body">
+ <h1>Categories for Alexis' log</h1>
<ul>
<li><a href="./category/bar.html">bar</a> (1)</li>
<li><a href="./category/cat1.html">cat1</a> (4)</li>
<li><a href="./category/misc.html">misc</a> (4)</li>
<li><a href="./category/yeah.html">yeah</a> (1)</li>
</ul>
+ </section>
+
<section id="extras" class="body">
<div class="blogroll">
<h2>links</h2>
diff --git a/pelican/tests/output/custom_locale/categories.html b/pelican/tests/output/custom_locale/categories.html
index 0d46b9a0..1decaabd 100644
--- a/pelican/tests/output/custom_locale/categories.html
+++ b/pelican/tests/output/custom_locale/categories.html
@@ -26,13 +26,17 @@
<li><a href="./category/bar.html">bar</a></li>
</ul></nav>
</header><!-- /#banner -->
- <h1>Categories on Alexis' log</h1>
+
+ <section id="content" class="body">
+ <h1>Categories for Alexis' log</h1>
<ul>
<li><a href="./category/bar.html">bar</a> (1)</li>
<li><a href="./category/cat1.html">cat1</a> (4)</li>
<li><a href="./category/misc.html">misc</a> (4)</li>
<li><a href="./category/yeah.html">yeah</a> (1)</li>
</ul>
+ </section>
+
<section id="extras" class="body">
<div class="blogroll">
<h2>links</h2>
diff --git a/pelican/tests/test_cli.py b/pelican/tests/test_cli.py
new file mode 100644
index 00000000..13b307e7
--- /dev/null
+++ b/pelican/tests/test_cli.py
@@ -0,0 +1,72 @@
+import unittest
+
+from pelican import get_config, parse_arguments
+
+
+class TestParseOverrides(unittest.TestCase):
+ def test_flags(self):
+ for flag in ['-e', '--extra-settings']:
+ args = parse_arguments([flag, 'k=1'])
+ self.assertDictEqual(args.overrides, {'k': 1})
+
+ def test_parse_multiple_items(self):
+ args = parse_arguments('-e k1=1 k2=2'.split())
+ self.assertDictEqual(args.overrides, {'k1': 1, 'k2': 2})
+
+ def test_parse_valid_json(self):
+ json_values_python_values_map = {
+ '""': '',
+ 'null': None,
+ '"string"': 'string',
+ '["foo", 12, "4", {}]': ['foo', 12, '4', {}]
+ }
+ for k, v in json_values_python_values_map.items():
+ args = parse_arguments(['-e', 'k=' + k])
+ self.assertDictEqual(args.overrides, {'k': v})
+
+ def test_parse_invalid_syntax(self):
+ invalid_items = ['k= 1', 'k =1', 'k', 'k v']
+ for item in invalid_items:
+ with self.assertRaises(ValueError):
+ parse_arguments(f'-e {item}'.split())
+
+ def test_parse_invalid_json(self):
+ invalid_json = {
+ '', 'False', 'True', 'None', 'some other string',
+ '{"foo": bar}', '[foo]'
+ }
+ for v in invalid_json:
+ with self.assertRaises(ValueError):
+ parse_arguments(['-e ', 'k=' + v])
+
+
+class TestGetConfigFromArgs(unittest.TestCase):
+ def test_overrides_known_keys(self):
+ args = parse_arguments([
+ '-e',
+ 'DELETE_OUTPUT_DIRECTORY=false',
+ 'OUTPUT_RETENTION=["1.txt"]',
+ 'SITENAME="Title"'
+ ])
+ config = get_config(args)
+ config_must_contain = {
+ 'DELETE_OUTPUT_DIRECTORY': False,
+ 'OUTPUT_RETENTION': ['1.txt'],
+ 'SITENAME': 'Title'
+ }
+ self.assertDictEqual(config, {**config, **config_must_contain})
+
+ def test_overrides_non_default_type(self):
+ args = parse_arguments([
+ '-e',
+ 'DISPLAY_PAGES_ON_MENU=123',
+ 'PAGE_TRANSLATION_ID=null',
+ 'TRANSLATION_FEED_RSS_URL="someurl"'
+ ])
+ config = get_config(args)
+ config_must_contain = {
+ 'DISPLAY_PAGES_ON_MENU': 123,
+ 'PAGE_TRANSLATION_ID': None,
+ 'TRANSLATION_FEED_RSS_URL': 'someurl'
+ }
+ self.assertDictEqual(config, {**config, **config_must_contain})
diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py
index 83203ae5..c407f7c8 100644
--- a/pelican/tests/test_settings.py
+++ b/pelican/tests/test_settings.py
@@ -7,7 +7,7 @@ from sys import platform
from pelican.settings import (DEFAULT_CONFIG, DEFAULT_THEME,
_printf_s_to_format_field,
- coerce_overrides, configure_settings,
+ configure_settings,
handle_deprecated_settings, read_settings)
from pelican.tests.support import unittest
@@ -304,18 +304,3 @@ class TestSettingsConfiguration(unittest.TestCase):
[(r'C\+\+', 'cpp')] +
self.settings['SLUG_REGEX_SUBSTITUTIONS'])
self.assertNotIn('SLUG_SUBSTITUTIONS', settings)
-
- def test_coerce_overrides(self):
- overrides = coerce_overrides({
- 'ARTICLE_EXCLUDES': '["testexcl"]',
- 'READERS': '{"foo": "bar"}',
- 'STATIC_EXCLUDE_SOURCES': 'true',
- 'THEME_STATIC_DIR': 'theme',
- })
- expected = {
- 'ARTICLE_EXCLUDES': ["testexcl"],
- 'READERS': {"foo": "bar"},
- 'STATIC_EXCLUDE_SOURCES': True,
- 'THEME_STATIC_DIR': 'theme',
- }
- self.assertDictEqual(overrides, expected)
diff --git a/pelican/themes/notmyidea/templates/categories.html b/pelican/themes/notmyidea/templates/categories.html
new file mode 100644
index 00000000..07f6290a
--- /dev/null
+++ b/pelican/themes/notmyidea/templates/categories.html
@@ -0,0 +1,16 @@
+{% extends "base.html" %}
+
+{% block title %}{{ SITENAME }} - Categories{% endblock %}
+
+{% block content %}
+
+<section id="content" class="body">
+ <h1>Categories for {{ SITENAME }}</h1>
+ <ul>
+ {% for category, articles in categories|sort %}
+ <li><a href="{{ SITEURL }}/{{ category.url }}">{{ category }}</a> ({{ articles|count }})</li>
+ {% endfor %}
+ </ul>
+</section>
+
+{% endblock %}
diff --git a/pyproject.toml b/pyproject.toml
index 8d7749b0..3d226c5c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pelican"
-version = "4.7.1"
+version = "4.7.2"
description = "Static site generator supporting Markdown and reStructuredText"
authors = ["Justin Mayer <entrop@gmail.com>"]
license = "AGPLv3"
@@ -48,7 +48,7 @@ jinja2 = "~2.11"
lxml = "^4.3"
markdown = "~3.3.4"
typogrify = "^2.0"
-sphinx = "^3.0"
+sphinx = "<4.4.0"
sphinx_rtd_theme = "^0.5"
livereload = "^2.6"
psutil = {version = "^5.7", optional = true}
diff --git a/requirements/docs.pip b/requirements/docs.pip
index bd25c939..a5a184ef 100644
--- a/requirements/docs.pip
+++ b/requirements/docs.pip
@@ -1,3 +1,3 @@
-sphinx
+sphinx<4.4.0
sphinx_rtd_theme
livereload
diff --git a/setup.py b/setup.py
index 3b538992..0b536e52 100755
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@ from os.path import join, relpath
from setuptools import find_packages, setup
-version = "4.7.1"
+version = "4.7.2"
requires = ['feedgenerator >= 1.9', 'jinja2 >= 2.7', 'pygments',
'docutils>=0.15', 'pytz >= 0a', 'blinker', 'unidecode',