# dokumentasi Django 2.2.9.dev20191202104323

# Kode sumber untuk django.template.utils

    import functools
    from collections import Counter, OrderedDict
    from pathlib import Path

    from django.apps import apps
    from django.conf import settings
    from django.core.exceptions import ImproperlyConfigured
    from django.utils.functional import cached_property
    from django.utils.module_loading import import_string

    class InvalidTemplateEngineError(ImproperlyConfigured):
        pass

    class EngineHandler:
        def __init__(self, templates=None):
            """
            templates is an optional list of template engine definitions
            (structured like settings.TEMPLATES).
            """
            self._templates = templates
            self._engines = {}

        @cached_property
        def templates(self):
            if self._templates is None:
                self._templates = settings.TEMPLATES

            templates = OrderedDict()
            backend_names = []
            for tpl in self._templates:
                try:
                    # This will raise an exception if 'BACKEND' doesn't exist or
                    # isn't a string containing at least one dot.
                    default_name = tpl['BACKEND'].rsplit('.', 2)[-2]
                except Exception:
                    invalid_backend = tpl.get('BACKEND', '<not defined>')
                    raise ImproperlyConfigured(
                        "Invalid BACKEND for a template engine: {}. Check "
                        "your TEMPLATES setting.".format(invalid_backend))

                tpl = {
                    'NAME': default_name,
                    'DIRS': [],
                    'APP_DIRS': False,
                    'OPTIONS': {},
                    **tpl,
                }

                templates[tpl['NAME']] = tpl
                backend_names.append(tpl['NAME'])

            counts = Counter(backend_names)
            duplicates = [alias for alias, count in counts.most_common() if count > 1]
            if duplicates:
                raise ImproperlyConfigured(
                    "Template engine aliases aren't unique, duplicates: {}. "
                    "Set a unique NAME for each engine in settings.TEMPLATES."
                    .format(", ".join(duplicates)))

            return templates

        def __getitem__(self, alias):
            try:
                return self._engines[alias]
            except KeyError:
                try:
                    params = self.templates[alias]
                except KeyError:
                    raise InvalidTemplateEngineError(
                        "Could not find config for '{}' "
                        "in settings.TEMPLATES".format(alias))

                # If importing or initializing the backend raises an exception,
                # self._engines[alias] isn't set and this code may get executed
                # again, so we must preserve the original params. See #24265.
                params = params.copy()
                backend = params.pop('BACKEND')
                engine_cls = import_string(backend)
                engine = engine_cls(params)

                self._engines[alias] = engine
                return engine

        def __iter__(self):
            return iter(self.templates)

        def all(self):
            return [self[alias] for alias in self]

    @functools.lru_cache()
    def get_app_template_dirs(dirname):
        """
        Return an iterable of paths of directories to load app templates from.

        dirname is the name of the subdirectory containing templates inside
        installed applications.
        """
        template_dirs = [
            str(Path(app_config.path) / dirname)
            for app_config in apps.get_app_configs()
            if app_config.path and (Path(app_config.path) / dirname).is_dir()
        ]
        # Immutable return value because it will be cached and shared by callers.
        return tuple(template_dirs)