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 7b333de8..40804e0b 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 @@ -658,3 +659,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)