File add-templated-directory.patch of Package mock

From 950ddc44ccdd04b50f8024cf8e31be5b4135a2b0 Mon Sep 17 00:00:00 2001
From: Miika Alikirri <miika.alikirri@suse.com>
Date: Mon, 11 Sep 2023 10:06:20 +0300
Subject: Add TemplatedDictionary back to the project

TemplatedDictionary is a small project that doesn't
publish tito builds on GitHub and therefore would
require tito to create package for it.

Since TemplatedDictionary is a really small project
and hasn't changed since Nov 18, 2020, it's easier
to add it back to mock than it is to package tito
to openSUSE products.

See the project here: https://github.com/xsuchy/templated-dictionary
---
 mock/py/mockbuild/config.py         |  3 +-
 mock/py/mockbuild/text.py           | 85 +++++++++++++++++++++++++++++
 mock/tests/test_config_templates.py |  2 +-
 mock/tests/test_package_manager.py  |  2 +-
 4 files changed, 88 insertions(+), 4 deletions(-)

Index: mock-6.0/py/mockbuild/config.py
===================================================================
--- mock-6.0.orig/py/mockbuild/config.py
+++ mock-6.0/py/mockbuild/config.py
@@ -19,7 +19,6 @@ import sys
 import uuid
 import warnings
 
-from templated_dictionary import TemplatedDictionary
 from . import exception
 from . import text
 from .constants import MOCKCONFDIR, PKGPYTHONDIR, VERSION
@@ -63,7 +62,7 @@ def setup_default_config_opts():
 
     alt_opts["dnf4_system_command"].append("system_dnf_command")
 
-    config_opts = TemplatedDictionary(alias_spec=alt_opts)
+    config_opts = text.TemplatedDictionary(alias_spec=alt_opts)
 
     config_opts['config_paths'] = []
     config_opts['version'] = VERSION
Index: mock-6.0/py/mockbuild/text.py
===================================================================
--- mock-6.0.orig/py/mockbuild/text.py
+++ mock-6.0/py/mockbuild/text.py
@@ -1,7 +1,9 @@
 # -*- coding: utf-8 -*-
 # vim:expandtab:autoindent:tabstop=4:shiftwidth=4:filetype=python:textwidth=0:
 
+from collections.abc import MutableMapping
 import locale
+import jinja2
 
 from .trace_decorator import getLog
 
@@ -20,6 +22,89 @@ def compat_expand_string(string, conf_di
     return string % conf_dict
 
 
+# pylint: disable=no-member,unsupported-assignment-operation
+class TemplatedDictionary(MutableMapping):
+    """ Dictionary where __getitem__() is run through Jinja2 template """
+    def __init__(self, *args, alias_spec=None, **kwargs):
+        '''
+        Use the object dict.
+
+        Optional parameter 'alias_spec' is dictionary of form:
+        {'aliased_to': ['alias_one', 'alias_two', ...], ...}
+        When specified, and one of the aliases is accessed - the
+        'aliased_to' config option is returned.
+        '''
+        self.__dict__.update(*args, **kwargs)
+
+        self._aliases = {}
+        if alias_spec:
+            for aliased_to, aliases in alias_spec.items():
+                for alias in aliases:
+                    self._aliases[alias] = aliased_to
+
+    # The next five methods are requirements of the ABC.
+    def __setitem__(self, key, value):
+        key = self._aliases.get(key, key)
+        self.__dict__[key] = value
+
+    def __getitem__(self, key):
+        key = self._aliases.get(key, key)
+        if '__jinja_expand' in self.__dict__ and self.__dict__['__jinja_expand']:
+            return self.__render_value(self.__dict__[key])
+        return self.__dict__[key]
+
+    def __delitem__(self, key):
+        del self.__dict__[key]
+
+    def __iter__(self):
+        return iter(self.__dict__)
+
+    def __len__(self):
+        return len(self.__dict__)
+
+    # The final two methods aren't required, but nice to have
+    def __str__(self):
+        '''returns simple dict representation of the mapping'''
+        return str(self.__dict__)
+
+    def __repr__(self):
+        '''echoes class, id, & reproducible representation in the REPL'''
+        return '{}, TemplatedDictionary({})'.format(super(TemplatedDictionary, self).__repr__(),
+                                                    self.__dict__)
+
+    def copy(self):
+        return TemplatedDictionary(self.__dict__)
+
+    def __render_value(self, value):
+        if isinstance(value, str):
+            return self.__render_string(value)
+        elif isinstance(value, list):
+            # we cannot use list comprehension here, as we need to NOT modify the list (pointer to list)
+            # and we need to modifiy only individual values in the list
+            # If we would create new list, we cannot assign to it, which often happens in configs (e.g. plugins)
+            for i in range(len(value)):  # pylint: disable=consider-using-enumerate
+                value[i] = self.__render_value(value[i])
+            return value
+        elif isinstance(value, dict):
+            # we cannot use list comprehension here, same reasoning as for `list` above
+            for k in value.keys():
+                value[k] = self.__render_value(value[k])
+            return value
+        else:
+            return value
+
+    def __render_string(self, value):
+        orig = last = value
+        max_recursion = self.__dict__.get('jinja_max_recursion', 5)
+        for _ in range(max_recursion):
+            template = jinja2.Template(value, keep_trailing_newline=True)
+            value = _to_native(template.render(self.__dict__))
+            if value == last:
+                return value
+            last = value
+        raise ValueError("too deep jinja re-evaluation on '{}'".format(orig))
+
+
 def _to_text(obj, arg_encoding='utf-8', errors='strict', nonstring='strict'):
     if isinstance(obj, str):
         return obj
Index: mock-6.0/tests/test_config_templates.py
===================================================================
--- mock-6.0.orig/tests/test_config_templates.py
+++ mock-6.0/tests/test_config_templates.py
@@ -1,5 +1,5 @@
 import pytest
-from templated_dictionary import TemplatedDictionary
+from mockbuild.text import TemplatedDictionary
 
 
 def test_transitive_expand():
Index: mock-6.0/tests/test_package_manager.py
===================================================================
--- mock-6.0.orig/tests/test_package_manager.py
+++ mock-6.0/tests/test_package_manager.py
@@ -6,7 +6,7 @@ import pytest
 from unittest import mock
 from unittest.mock import MagicMock
 
-from templated_dictionary import TemplatedDictionary
+from mockbuild.text import TemplatedDictionary
 from mockbuild.config import setup_default_config_opts
 from mockbuild.constants import PKGPYTHONDIR
 from mockbuild.buildroot import Buildroot
openSUSE Build Service is sponsored by