From 34edcdf33d9a05f99bac0688014c9cd4b99b3be6 Mon Sep 17 00:00:00 2001 From: Peter Sabaini Date: Sat, 9 May 2020 18:15:16 +0200 Subject: [PATCH] Override settings from the commandline Add a --setting-overrides KEY=VAL commandline option to override arbitrary items from pelicanconf.py settings files. This adds flexibility in running pelican and helps reduce sprawl of settings files. Cast int and str setting overrides to their respective types. Support other setting types by treating them as json. Fallback to json when an override typecast errors. This should make it possible to set int values to None, resp. to json 'none' --- .gitignore | 1 + pelican/__init__.py | 27 ++++++++++++++++++++++++++- pelican/settings.py | 21 +++++++++++++++++++++ pelican/tests/test_settings.py | 19 +++++++++++++++++-- 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index b94526d6..ff4fda0a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .*.swo *.pyc .DS_Store +.idea docs/_build docs/fr/_build build diff --git a/pelican/__init__.py b/pelican/__init__.py index 6469c607..9204d952 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -23,7 +23,7 @@ from pelican.plugins import signals from pelican.plugins._utils import load_plugins from pelican.readers import Readers from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer -from pelican.settings import read_settings +from pelican.settings import coerce_overrides, read_settings from pelican.utils import (FileSystemWatcher, clean_output_dir, maybe_pluralize) from pelican.writers import Writer @@ -230,6 +230,18 @@ class PrintSettings(argparse.Action): parser.exit() +class ParseDict(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) + + def parse_arguments(argv=None): parser = argparse.ArgumentParser( description='A tool to generate a static blog, ' @@ -323,6 +335,18 @@ def parse_arguments(argv=None): help='IP to bind to when serving files via HTTP ' '(default: 127.0.0.1)') + parser.add_argument('-c', '--setting-overrides', dest='overrides', + help='Specify one ore more SETTING=VALUE pairs ' + '(without spaces around =) to override ' + 'settings files. If VALUE contains spaces, add quotes: ' + 'SETTING="VALUE". ' + 'Integers and strings are autoconverted, other values ' + 'can be passed in in json notation, ' + 'e.g. SETTING=none', + nargs='*', + action=ParseDict + ) + args = parser.parse_args(argv) if args.port is not None and not args.listen: @@ -358,6 +382,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)) return config diff --git a/pelican/settings.py b/pelican/settings.py index d219a8f2..28dbf2c0 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -1,6 +1,7 @@ import copy import importlib.util import inspect +import json import locale import logging import os @@ -660,3 +661,23 @@ def configure_settings(settings): continue # setting not specified, nothing to do return settings + + +def coerce_overrides(overrides): + coerced = {} + types_to_cast = {int, str} + for k, v in overrides.items(): + if k not in overrides: + 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/test_settings.py b/pelican/tests/test_settings.py index 708c0981..83203ae5 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -7,8 +7,8 @@ from sys import platform from pelican.settings import (DEFAULT_CONFIG, DEFAULT_THEME, _printf_s_to_format_field, - configure_settings, handle_deprecated_settings, - read_settings) + coerce_overrides, configure_settings, + handle_deprecated_settings, read_settings) from pelican.tests.support import unittest @@ -304,3 +304,18 @@ 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)