import copy
import importlib.util
import inspect
import locale
import logging
import os
import re
import sys
from os.path import isabs
from pathlib import Path

from pelican.log import LimitFilter


def load_source(name, path):
    spec = importlib.util.spec_from_file_location(name, path)
    mod = importlib.util.module_from_spec(spec)
    sys.modules[name] = mod
    spec.loader.exec_module(mod)
    return mod


logger = logging.getLogger(__name__)

DEFAULT_THEME = os.path.join(
    os.path.dirname(os.path.abspath(__file__)), "themes", "notmyidea"
)
DEFAULT_CONFIG = {
    "PATH": os.curdir,
    "ARTICLE_PATHS": [""],
    "ARTICLE_EXCLUDES": [],
    "PAGE_PATHS": ["pages"],
    "PAGE_EXCLUDES": [],
    "THEME": DEFAULT_THEME,
    "OUTPUT_PATH": "output",
    "READERS": {},
    "STATIC_PATHS": ["images"],
    "STATIC_EXCLUDES": [],
    "STATIC_EXCLUDE_SOURCES": True,
    "THEME_STATIC_DIR": "theme",
    "THEME_STATIC_PATHS": [
        "static",
    ],
    "FEED_ALL_ATOM": "feeds/all.atom.xml",
    "CATEGORY_FEED_ATOM": "feeds/{slug}.atom.xml",
    "AUTHOR_FEED_ATOM": "feeds/{slug}.atom.xml",
    "AUTHOR_FEED_RSS": "feeds/{slug}.rss.xml",
    "TRANSLATION_FEED_ATOM": "feeds/all-{lang}.atom.xml",
    "FEED_MAX_ITEMS": 100,
    "RSS_FEED_SUMMARY_ONLY": True,
    "SITEURL": "",
    "SITENAME": "A Pelican Blog",
    "DISPLAY_PAGES_ON_MENU": True,
    "DISPLAY_CATEGORIES_ON_MENU": True,
    "DOCUTILS_SETTINGS": {},
    "OUTPUT_SOURCES": False,
    "OUTPUT_SOURCES_EXTENSION": ".text",
    "USE_FOLDER_AS_CATEGORY": True,
    "DEFAULT_CATEGORY": "misc",
    "WITH_FUTURE_DATES": True,
    "CSS_FILE": "main.css",
    "NEWEST_FIRST_ARCHIVES": True,
    "REVERSE_CATEGORY_ORDER": False,
    "DELETE_OUTPUT_DIRECTORY": False,
    "OUTPUT_RETENTION": [],
    "INDEX_SAVE_AS": "index.html",
    "ARTICLE_URL": "{slug}.html",
    "ARTICLE_SAVE_AS": "{slug}.html",
    "ARTICLE_ORDER_BY": "reversed-date",
    "ARTICLE_LANG_URL": "{slug}-{lang}.html",
    "ARTICLE_LANG_SAVE_AS": "{slug}-{lang}.html",
    "DRAFT_URL": "drafts/{slug}.html",
    "DRAFT_SAVE_AS": "drafts/{slug}.html",
    "DRAFT_LANG_URL": "drafts/{slug}-{lang}.html",
    "DRAFT_LANG_SAVE_AS": "drafts/{slug}-{lang}.html",
    "PAGE_URL": "pages/{slug}.html",
    "PAGE_SAVE_AS": "pages/{slug}.html",
    "PAGE_ORDER_BY": "basename",
    "PAGE_LANG_URL": "pages/{slug}-{lang}.html",
    "PAGE_LANG_SAVE_AS": "pages/{slug}-{lang}.html",
    "DRAFT_PAGE_URL": "drafts/pages/{slug}.html",
    "DRAFT_PAGE_SAVE_AS": "drafts/pages/{slug}.html",
    "DRAFT_PAGE_LANG_URL": "drafts/pages/{slug}-{lang}.html",
    "DRAFT_PAGE_LANG_SAVE_AS": "drafts/pages/{slug}-{lang}.html",
    "STATIC_URL": "{path}",
    "STATIC_SAVE_AS": "{path}",
    "STATIC_CREATE_LINKS": False,
    "STATIC_CHECK_IF_MODIFIED": False,
    "CATEGORY_URL": "category/{slug}.html",
    "CATEGORY_SAVE_AS": "category/{slug}.html",
    "TAG_URL": "tag/{slug}.html",
    "TAG_SAVE_AS": "tag/{slug}.html",
    "AUTHOR_URL": "author/{slug}.html",
    "AUTHOR_SAVE_AS": "author/{slug}.html",
    "PAGINATION_PATTERNS": [
        (1, "{name}{extension}", "{name}{extension}"),
        (2, "{name}{number}{extension}", "{name}{number}{extension}"),
    ],
    "YEAR_ARCHIVE_URL": "",
    "YEAR_ARCHIVE_SAVE_AS": "",
    "MONTH_ARCHIVE_URL": "",
    "MONTH_ARCHIVE_SAVE_AS": "",
    "DAY_ARCHIVE_URL": "",
    "DAY_ARCHIVE_SAVE_AS": "",
    "RELATIVE_URLS": False,
    "DEFAULT_LANG": "en",
    "ARTICLE_TRANSLATION_ID": "slug",
    "PAGE_TRANSLATION_ID": "slug",
    "DIRECT_TEMPLATES": ["index", "tags", "categories", "authors", "archives"],
    "THEME_TEMPLATES_OVERRIDES": [],
    "PAGINATED_TEMPLATES": {
        "index": None,
        "tag": None,
        "category": None,
        "author": None,
    },
    "PELICAN_CLASS": "pelican.Pelican",
    "DEFAULT_DATE_FORMAT": "%a %d %B %Y",
    "DATE_FORMATS": {},
    "MARKDOWN": {
        "extension_configs": {
            "markdown.extensions.codehilite": {"css_class": "highlight"},
            "markdown.extensions.extra": {},
            "markdown.extensions.meta": {},
        },
        "output_format": "html5",
    },
    "JINJA_FILTERS": {},
    "JINJA_GLOBALS": {},
    "JINJA_TESTS": {},
    "JINJA_ENVIRONMENT": {
        "trim_blocks": True,
        "lstrip_blocks": True,
        "extensions": [],
    },
    "LOG_FILTER": [],
    "LOCALE": [""],  # defaults to user locale
    "DEFAULT_PAGINATION": False,
    "DEFAULT_ORPHANS": 0,
    "DEFAULT_METADATA": {},
    "FILENAME_METADATA": r"(?P<date>\d{4}-\d{2}-\d{2}).*",
    "PATH_METADATA": "",
    "EXTRA_PATH_METADATA": {},
    "ARTICLE_PERMALINK_STRUCTURE": "",
    "TYPOGRIFY": False,
    "TYPOGRIFY_IGNORE_TAGS": [],
    "TYPOGRIFY_DASHES": "default",
    "SUMMARY_END_SUFFIX": "…",
    "SUMMARY_MAX_LENGTH": 50,
    "PLUGIN_PATHS": [],
    "PLUGINS": None,
    "PYGMENTS_RST_OPTIONS": {},
    "TEMPLATE_PAGES": {},
    "TEMPLATE_EXTENSIONS": [".html"],
    "IGNORE_FILES": [".#*"],
    "SLUG_REGEX_SUBSTITUTIONS": [
        (r"[^\w\s-]", ""),  # remove non-alphabetical/whitespace/'-' chars
        (r"(?u)\A\s*", ""),  # strip leading whitespace
        (r"(?u)\s*\Z", ""),  # strip trailing whitespace
        (r"[-\s]+", "-"),  # reduce multiple whitespace or '-' to single '-'
    ],
    "INTRASITE_LINK_REGEX": "[{|](?P<what>.*?)[|}]",
    "SLUGIFY_SOURCE": "title",
    "SLUGIFY_USE_UNICODE": False,
    "SLUGIFY_PRESERVE_CASE": False,
    "CACHE_CONTENT": False,
    "CONTENT_CACHING_LAYER": "reader",
    "CACHE_PATH": "cache",
    "GZIP_CACHE": True,
    "CHECK_MODIFIED_METHOD": "mtime",
    "LOAD_CONTENT_CACHE": False,
    "FORMATTED_FIELDS": ["summary"],
    "PORT": 8000,
    "BIND": "127.0.0.1",
}

PYGMENTS_RST_OPTIONS = None


def read_settings(path=None, override=None):
    settings = override or {}

    if path:
        settings = dict(get_settings_from_file(path), **settings)

    if settings:
        settings = handle_deprecated_settings(settings)

    if path:
        # Make relative paths absolute
        def getabs(maybe_relative, base_path=path):
            if isabs(maybe_relative):
                return maybe_relative
            return os.path.abspath(
                os.path.normpath(
                    os.path.join(os.path.dirname(base_path), maybe_relative)
                )
            )

        for p in ["PATH", "OUTPUT_PATH", "THEME", "CACHE_PATH"]:
            if settings.get(p) is not None:
                absp = getabs(settings[p])
                # THEME may be a name rather than a path
                if p != "THEME" or os.path.exists(absp):
                    settings[p] = absp

        if settings.get("PLUGIN_PATHS") is not None:
            settings["PLUGIN_PATHS"] = [
                getabs(pluginpath) for pluginpath in settings["PLUGIN_PATHS"]
            ]

    settings = dict(copy.deepcopy(DEFAULT_CONFIG), **settings)
    settings = configure_settings(settings)

    # This is because there doesn't seem to be a way to pass extra
    # parameters to docutils directive handlers, so we have to have a
    # variable here that we'll import from within Pygments.run (see
    # rstdirectives.py) to see what the user defaults were.
    global PYGMENTS_RST_OPTIONS
    PYGMENTS_RST_OPTIONS = settings.get("PYGMENTS_RST_OPTIONS", None)
    return settings


def get_settings_from_module(module=None):
    """Loads settings from a module, returns a dictionary."""

    context = {}
    if module is not None:
        context.update((k, v) for k, v in inspect.getmembers(module) if k.isupper())
    return context


def get_settings_from_file(path):
    """Loads settings from a file path, returning a dict."""

    name, ext = os.path.splitext(os.path.basename(path))
    module = load_source(name, path)
    return get_settings_from_module(module)


def get_jinja_environment(settings):
    """Sets the environment for Jinja"""

    jinja_env = settings.setdefault(
        "JINJA_ENVIRONMENT", DEFAULT_CONFIG["JINJA_ENVIRONMENT"]
    )

    # Make sure we include the defaults if the user has set env variables
    for key, value in DEFAULT_CONFIG["JINJA_ENVIRONMENT"].items():
        if key not in jinja_env:
            jinja_env[key] = value

    return settings


def _printf_s_to_format_field(printf_string, format_field):
    """Tries to replace %s with {format_field} in the provided printf_string.
    Raises ValueError in case of failure.
    """
    TEST_STRING = "PELICAN_PRINTF_S_DEPRECATION"
    expected = printf_string % TEST_STRING

    result = printf_string.replace("{", "{{").replace("}", "}}") % "{{{}}}".format(
        format_field
    )
    if result.format(**{format_field: TEST_STRING}) != expected:
        raise ValueError(f"Failed to safely replace %s with {{{format_field}}}")

    return result


def handle_deprecated_settings(settings):
    """Converts deprecated settings and issues warnings. Issues an exception
    if both old and new setting is specified.
    """

    # PLUGIN_PATH -> PLUGIN_PATHS
    if "PLUGIN_PATH" in settings:
        logger.warning(
            "PLUGIN_PATH setting has been replaced by "
            "PLUGIN_PATHS, moving it to the new setting name."
        )
        settings["PLUGIN_PATHS"] = settings["PLUGIN_PATH"]
        del settings["PLUGIN_PATH"]

    # PLUGIN_PATHS: str -> [str]
    if isinstance(settings.get("PLUGIN_PATHS"), str):
        logger.warning(
            "Defining PLUGIN_PATHS setting as string "
            "has been deprecated (should be a list)"
        )
        settings["PLUGIN_PATHS"] = [settings["PLUGIN_PATHS"]]

    # JINJA_EXTENSIONS -> JINJA_ENVIRONMENT > extensions
    if "JINJA_EXTENSIONS" in settings:
        logger.warning(
            "JINJA_EXTENSIONS setting has been deprecated, "
            "moving it to JINJA_ENVIRONMENT setting."
        )
        settings["JINJA_ENVIRONMENT"]["extensions"] = settings["JINJA_EXTENSIONS"]
        del settings["JINJA_EXTENSIONS"]

    # {ARTICLE,PAGE}_DIR -> {ARTICLE,PAGE}_PATHS
    for key in ["ARTICLE", "PAGE"]:
        old_key = key + "_DIR"
        new_key = key + "_PATHS"
        if old_key in settings:
            logger.warning(
                "Deprecated setting %s, moving it to %s list", old_key, new_key
            )
            settings[new_key] = [settings[old_key]]  # also make a list
            del settings[old_key]

    # EXTRA_TEMPLATES_PATHS -> THEME_TEMPLATES_OVERRIDES
    if "EXTRA_TEMPLATES_PATHS" in settings:
        logger.warning(
            "EXTRA_TEMPLATES_PATHS is deprecated use "
            "THEME_TEMPLATES_OVERRIDES instead."
        )
        if (
            "THEME_TEMPLATES_OVERRIDES" in settings
            and settings["THEME_TEMPLATES_OVERRIDES"]
        ):
            raise Exception(
                "Setting both EXTRA_TEMPLATES_PATHS and "
                "THEME_TEMPLATES_OVERRIDES is not permitted. Please move to "
                "only setting THEME_TEMPLATES_OVERRIDES."
            )
        settings["THEME_TEMPLATES_OVERRIDES"] = settings["EXTRA_TEMPLATES_PATHS"]
        del settings["EXTRA_TEMPLATES_PATHS"]

    # MD_EXTENSIONS -> MARKDOWN
    if "MD_EXTENSIONS" in settings:
        logger.warning(
            "MD_EXTENSIONS is deprecated use MARKDOWN "
            "instead. Falling back to the default."
        )
        settings["MARKDOWN"] = DEFAULT_CONFIG["MARKDOWN"]

    # LESS_GENERATOR -> Webassets plugin
    # FILES_TO_COPY -> STATIC_PATHS, EXTRA_PATH_METADATA
    for old, new, doc in [
        ("LESS_GENERATOR", "the Webassets plugin", None),
        (
            "FILES_TO_COPY",
            "STATIC_PATHS and EXTRA_PATH_METADATA",
            "https://github.com/getpelican/pelican/"
            "blob/master/docs/settings.rst#path-metadata",
        ),
    ]:
        if old in settings:
            message = f"The {old} setting has been removed in favor of {new}"
            if doc:
                message += f", see {doc} for details"
            logger.warning(message)

    # PAGINATED_DIRECT_TEMPLATES -> PAGINATED_TEMPLATES
    if "PAGINATED_DIRECT_TEMPLATES" in settings:
        message = "The {} setting has been removed in favor of {}".format(
            "PAGINATED_DIRECT_TEMPLATES", "PAGINATED_TEMPLATES"
        )
        logger.warning(message)

        # set PAGINATED_TEMPLATES
        if "PAGINATED_TEMPLATES" not in settings:
            settings["PAGINATED_TEMPLATES"] = {
                "tag": None,
                "category": None,
                "author": None,
            }

        for t in settings["PAGINATED_DIRECT_TEMPLATES"]:
            if t not in settings["PAGINATED_TEMPLATES"]:
                settings["PAGINATED_TEMPLATES"][t] = None
        del settings["PAGINATED_DIRECT_TEMPLATES"]

    # {SLUG,CATEGORY,TAG,AUTHOR}_SUBSTITUTIONS ->
    # {SLUG,CATEGORY,TAG,AUTHOR}_REGEX_SUBSTITUTIONS
    url_settings_url = "http://docs.getpelican.com/en/latest/settings.html#url-settings"
    flavours = {"SLUG", "CATEGORY", "TAG", "AUTHOR"}
    old_values = {
        f: settings[f + "_SUBSTITUTIONS"]
        for f in flavours
        if f + "_SUBSTITUTIONS" in settings
    }
    new_values = {
        f: settings[f + "_REGEX_SUBSTITUTIONS"]
        for f in flavours
        if f + "_REGEX_SUBSTITUTIONS" in settings
    }
    if old_values and new_values:
        raise Exception(
            "Setting both {new_key} and {old_key} (or variants thereof) is "
            "not permitted. Please move to only setting {new_key}.".format(
                old_key="SLUG_SUBSTITUTIONS", new_key="SLUG_REGEX_SUBSTITUTIONS"
            )
        )
    if old_values:
        message = (
            "{} and variants thereof are deprecated and will be "
            "removed in the future. Please use {} and variants thereof "
            "instead. Check {}.".format(
                "SLUG_SUBSTITUTIONS", "SLUG_REGEX_SUBSTITUTIONS", url_settings_url
            )
        )
        logger.warning(message)
        if old_values.get("SLUG"):
            for f in {"CATEGORY", "TAG"}:
                if old_values.get(f):
                    old_values[f] = old_values["SLUG"] + old_values[f]
            old_values["AUTHOR"] = old_values.get("AUTHOR", [])
        for f in flavours:
            if old_values.get(f) is not None:
                regex_subs = []
                # by default will replace non-alphanum characters
                replace = True
                for tpl in old_values[f]:
                    try:
                        src, dst, skip = tpl
                        if skip:
                            replace = False
                    except ValueError:
                        src, dst = tpl
                    regex_subs.append((re.escape(src), dst.replace("\\", r"\\")))

                if replace:
                    regex_subs += [
                        (r"[^\w\s-]", ""),
                        (r"(?u)\A\s*", ""),
                        (r"(?u)\s*\Z", ""),
                        (r"[-\s]+", "-"),
                    ]
                else:
                    regex_subs += [
                        (r"(?u)\A\s*", ""),
                        (r"(?u)\s*\Z", ""),
                    ]
                settings[f + "_REGEX_SUBSTITUTIONS"] = regex_subs
            settings.pop(f + "_SUBSTITUTIONS", None)

    # `%s` -> '{slug}` or `{lang}` in FEED settings
    for key in ["TRANSLATION_FEED_ATOM", "TRANSLATION_FEED_RSS"]:
        if (
            settings.get(key)
            and not isinstance(settings[key], Path)
            and "%s" in settings[key]
        ):
            logger.warning("%%s usage in %s is deprecated, use {lang} " "instead.", key)
            try:
                settings[key] = _printf_s_to_format_field(settings[key], "lang")
            except ValueError:
                logger.warning(
                    "Failed to convert %%s to {lang} for %s. "
                    "Falling back to default.",
                    key,
                )
                settings[key] = DEFAULT_CONFIG[key]
    for key in [
        "AUTHOR_FEED_ATOM",
        "AUTHOR_FEED_RSS",
        "CATEGORY_FEED_ATOM",
        "CATEGORY_FEED_RSS",
        "TAG_FEED_ATOM",
        "TAG_FEED_RSS",
    ]:
        if (
            settings.get(key)
            and not isinstance(settings[key], Path)
            and "%s" in settings[key]
        ):
            logger.warning("%%s usage in %s is deprecated, use {slug} " "instead.", key)
            try:
                settings[key] = _printf_s_to_format_field(settings[key], "slug")
            except ValueError:
                logger.warning(
                    "Failed to convert %%s to {slug} for %s. "
                    "Falling back to default.",
                    key,
                )
                settings[key] = DEFAULT_CONFIG[key]

    # CLEAN_URLS
    if settings.get("CLEAN_URLS", False):
        logger.warning(
            "Found deprecated `CLEAN_URLS` in settings."
            " Modifying the following settings for the"
            " same behaviour."
        )

        settings["ARTICLE_URL"] = "{slug}/"
        settings["ARTICLE_LANG_URL"] = "{slug}-{lang}/"
        settings["PAGE_URL"] = "pages/{slug}/"
        settings["PAGE_LANG_URL"] = "pages/{slug}-{lang}/"

        for setting in ("ARTICLE_URL", "ARTICLE_LANG_URL", "PAGE_URL", "PAGE_LANG_URL"):
            logger.warning("%s = '%s'", setting, settings[setting])

    # AUTORELOAD_IGNORE_CACHE -> --ignore-cache
    if settings.get("AUTORELOAD_IGNORE_CACHE"):
        logger.warning(
            "Found deprecated `AUTORELOAD_IGNORE_CACHE` in "
            "settings. Use --ignore-cache instead."
        )
        settings.pop("AUTORELOAD_IGNORE_CACHE")

    # ARTICLE_PERMALINK_STRUCTURE
    if settings.get("ARTICLE_PERMALINK_STRUCTURE", False):
        logger.warning(
            "Found deprecated `ARTICLE_PERMALINK_STRUCTURE` in"
            " settings.  Modifying the following settings for"
            " the same behaviour."
        )

        structure = settings["ARTICLE_PERMALINK_STRUCTURE"]

        # Convert %(variable) into {variable}.
        structure = re.sub(r"%\((\w+)\)s", r"{\g<1>}", structure)

        # Convert %x into {date:%x} for strftime
        structure = re.sub(r"(%[A-z])", r"{date:\g<1>}", structure)

        # Strip a / prefix
        structure = re.sub("^/", "", structure)

        for setting in (
            "ARTICLE_URL",
            "ARTICLE_LANG_URL",
            "PAGE_URL",
            "PAGE_LANG_URL",
            "DRAFT_URL",
            "DRAFT_LANG_URL",
            "ARTICLE_SAVE_AS",
            "ARTICLE_LANG_SAVE_AS",
            "DRAFT_SAVE_AS",
            "DRAFT_LANG_SAVE_AS",
            "PAGE_SAVE_AS",
            "PAGE_LANG_SAVE_AS",
        ):
            settings[setting] = os.path.join(structure, settings[setting])
            logger.warning("%s = '%s'", setting, settings[setting])

    # {,TAG,CATEGORY,TRANSLATION}_FEED -> {,TAG,CATEGORY,TRANSLATION}_FEED_ATOM
    for new, old in [
        ("FEED", "FEED_ATOM"),
        ("TAG_FEED", "TAG_FEED_ATOM"),
        ("CATEGORY_FEED", "CATEGORY_FEED_ATOM"),
        ("TRANSLATION_FEED", "TRANSLATION_FEED_ATOM"),
    ]:
        if settings.get(new, False):
            logger.warning(
                "Found deprecated `%(new)s` in settings. Modify %(new)s "
                "to %(old)s in your settings and theme for the same "
                "behavior. Temporarily setting %(old)s for backwards "
                "compatibility.",
                {"new": new, "old": old},
            )
            settings[old] = settings[new]

    # Warn if removed WRITE_SELECTED is present
    if "WRITE_SELECTED" in settings:
        logger.warning(
            "WRITE_SELECTED is present in settings but this functionality was removed. "
            "It will have no effect."
        )

    return settings


def configure_settings(settings):
    """Provide optimizations, error checking, and warnings for the given
    settings.
    Also, specify the log messages to be ignored.
    """
    if "PATH" not in settings or not os.path.isdir(settings["PATH"]):
        raise Exception(
            "You need to specify a path containing the content"
            " (see pelican --help for more information)"
        )

    # specify the log messages to be ignored
    log_filter = settings.get("LOG_FILTER", DEFAULT_CONFIG["LOG_FILTER"])
    LimitFilter._ignore.update(set(log_filter))

    # lookup the theme in "pelican/themes" if the given one doesn't exist
    if not os.path.isdir(settings["THEME"]):
        theme_path = os.path.join(
            os.path.dirname(os.path.abspath(__file__)), "themes", settings["THEME"]
        )
        if os.path.exists(theme_path):
            settings["THEME"] = theme_path
        else:
            raise Exception("Could not find the theme %s" % settings["THEME"])

    # standardize strings to lowercase strings
    for key in ["DEFAULT_LANG"]:
        if key in settings:
            settings[key] = settings[key].lower()

    # set defaults for Jinja environment
    settings = get_jinja_environment(settings)

    # standardize strings to lists
    for key in ["LOCALE"]:
        if key in settings and isinstance(settings[key], str):
            settings[key] = [settings[key]]

    # check settings that must be a particular type
    for key, types in [
        ("OUTPUT_SOURCES_EXTENSION", str),
        ("FILENAME_METADATA", str),
    ]:
        if key in settings and not isinstance(settings[key], types):
            value = settings.pop(key)
            logger.warn(
                "Detected misconfigured %s (%s), " "falling back to the default (%s)",
                key,
                value,
                DEFAULT_CONFIG[key],
            )

    # try to set the different locales, fallback on the default.
    locales = settings.get("LOCALE", DEFAULT_CONFIG["LOCALE"])

    for locale_ in locales:
        try:
            locale.setlocale(locale.LC_ALL, str(locale_))
            break  # break if it is successful
        except locale.Error:
            pass
    else:
        logger.warning(
            "Locale could not be set. Check the LOCALE setting, ensuring it "
            "is valid and available on your system."
        )

    if "SITEURL" in settings:
        # If SITEURL has a trailing slash, remove it and provide a warning
        siteurl = settings["SITEURL"]
        if siteurl.endswith("/"):
            settings["SITEURL"] = siteurl[:-1]
            logger.warning("Removed extraneous trailing slash from SITEURL.")
        # If SITEURL is defined but FEED_DOMAIN isn't,
        # set FEED_DOMAIN to SITEURL
        if "FEED_DOMAIN" not in settings:
            settings["FEED_DOMAIN"] = settings["SITEURL"]

    # check content caching layer and warn of incompatibilities
    if (
        settings.get("CACHE_CONTENT", False)
        and settings.get("CONTENT_CACHING_LAYER", "") == "generator"
        and not settings.get("WITH_FUTURE_DATES", True)
    ):
        logger.warning(
            "WITH_FUTURE_DATES conflicts with CONTENT_CACHING_LAYER "
            "set to 'generator', use 'reader' layer instead"
        )

    # Warn if feeds are generated with both SITEURL & FEED_DOMAIN undefined
    feed_keys = [
        "FEED_ATOM",
        "FEED_RSS",
        "FEED_ALL_ATOM",
        "FEED_ALL_RSS",
        "CATEGORY_FEED_ATOM",
        "CATEGORY_FEED_RSS",
        "AUTHOR_FEED_ATOM",
        "AUTHOR_FEED_RSS",
        "TAG_FEED_ATOM",
        "TAG_FEED_RSS",
        "TRANSLATION_FEED_ATOM",
        "TRANSLATION_FEED_RSS",
    ]

    if any(settings.get(k) for k in feed_keys):
        if not settings.get("SITEURL"):
            logger.warning(
                "Feeds generated without SITEURL set properly may" " not be valid"
            )

    if "TIMEZONE" not in settings:
        logger.warning(
            "No timezone information specified in the settings. Assuming"
            " your timezone is UTC for feed generation. Check "
            "https://docs.getpelican.com/en/latest/settings.html#TIMEZONE "
            "for more information"
        )

    # fix up pagination rules
    from pelican.paginator import PaginationRule

    pagination_rules = [
        PaginationRule(*r)
        for r in settings.get(
            "PAGINATION_PATTERNS",
            DEFAULT_CONFIG["PAGINATION_PATTERNS"],
        )
    ]
    settings["PAGINATION_PATTERNS"] = sorted(
        pagination_rules,
        key=lambda r: r[0],
    )

    # Save people from accidentally setting a string rather than a list
    path_keys = (
        "ARTICLE_EXCLUDES",
        "DEFAULT_METADATA",
        "DIRECT_TEMPLATES",
        "THEME_TEMPLATES_OVERRIDES",
        "FILES_TO_COPY",
        "IGNORE_FILES",
        "PAGINATED_DIRECT_TEMPLATES",
        "PLUGINS",
        "STATIC_EXCLUDES",
        "STATIC_PATHS",
        "THEME_STATIC_PATHS",
        "ARTICLE_PATHS",
        "PAGE_PATHS",
    )
    for PATH_KEY in filter(lambda k: k in settings, path_keys):
        if isinstance(settings[PATH_KEY], str):
            logger.warning(
                "Detected misconfiguration with %s setting "
                "(must be a list), falling back to the default",
                PATH_KEY,
            )
            settings[PATH_KEY] = DEFAULT_CONFIG[PATH_KEY]

    # Add {PAGE,ARTICLE}_PATHS to {ARTICLE,PAGE}_EXCLUDES
    mutually_exclusive = ("ARTICLE", "PAGE")
    for type_1, type_2 in [mutually_exclusive, mutually_exclusive[::-1]]:
        try:
            includes = settings[type_1 + "_PATHS"]
            excludes = settings[type_2 + "_EXCLUDES"]
            for path in includes:
                if path not in excludes:
                    excludes.append(path)
        except KeyError:
            continue  # setting not specified, nothing to do

    return settings
