mirror of
				https://github.com/arcan1s/ahriman.git
				synced 2025-11-03 23:33:41 +00:00 
			
		
		
		
	expand bash
This commit is contained in:
		@ -28,6 +28,14 @@ ahriman.core.configuration.shell\_interpolator module
 | 
				
			|||||||
   :no-undoc-members:
 | 
					   :no-undoc-members:
 | 
				
			||||||
   :show-inheritance:
 | 
					   :show-inheritance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ahriman.core.configuration.shell\_template module
 | 
				
			||||||
 | 
					-------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. automodule:: ahriman.core.configuration.shell_template
 | 
				
			||||||
 | 
					   :members:
 | 
				
			||||||
 | 
					   :no-undoc-members:
 | 
				
			||||||
 | 
					   :show-inheritance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ahriman.core.configuration.validator module
 | 
					ahriman.core.configuration.validator module
 | 
				
			||||||
-------------------------------------------
 | 
					-------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -270,9 +270,9 @@ class PkgbuildParser(shlex.shlex):
 | 
				
			|||||||
            PkgbuildPatch: extracted a PKGBUILD node
 | 
					            PkgbuildPatch: extracted a PKGBUILD node
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        # simple assignment rule
 | 
					        # simple assignment rule
 | 
				
			||||||
        if (match := self._STRING_ASSIGNMENT.match(token)) is not None:
 | 
					        if m := self._STRING_ASSIGNMENT.match(token):
 | 
				
			||||||
            key = match.group("key")
 | 
					            key = m.group("key")
 | 
				
			||||||
            value = match.group("value")
 | 
					            value = m.group("value")
 | 
				
			||||||
            yield PkgbuildPatch(key, value)
 | 
					            yield PkgbuildPatch(key, value)
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -282,8 +282,8 @@ class PkgbuildParser(shlex.shlex):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        match self.get_token():
 | 
					        match self.get_token():
 | 
				
			||||||
            # array processing. Arrays will be sent as "key=", "(", values, ")"
 | 
					            # array processing. Arrays will be sent as "key=", "(", values, ")"
 | 
				
			||||||
            case PkgbuildToken.ArrayStarts if (match := self._ARRAY_ASSIGNMENT.match(token)) is not None:
 | 
					            case PkgbuildToken.ArrayStarts if m := self._ARRAY_ASSIGNMENT.match(token):
 | 
				
			||||||
                key = match.group("key")
 | 
					                key = m.group("key")
 | 
				
			||||||
                value = self._parse_array()
 | 
					                value = self._parse_array()
 | 
				
			||||||
                yield PkgbuildPatch(key, value)
 | 
					                yield PkgbuildPatch(key, value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -24,16 +24,7 @@ import sys
 | 
				
			|||||||
from collections.abc import Generator, Mapping, MutableMapping
 | 
					from collections.abc import Generator, Mapping, MutableMapping
 | 
				
			||||||
from string import Template
 | 
					from string import Template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ahriman.core.configuration.shell_template import ShellTemplate
 | 
				
			||||||
class ExtendedTemplate(Template):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    extension to the default :class:`Template` class, which also enabled braces regex to lookup in sections
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Attributes:
 | 
					 | 
				
			||||||
        braceidpattern(str): regular expression to match a colon inside braces
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    braceidpattern = r"(?a:[_a-z0-9][_a-z0-9:]*)"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ShellInterpolator(configparser.Interpolation):
 | 
					class ShellInterpolator(configparser.Interpolation):
 | 
				
			||||||
@ -60,7 +51,7 @@ class ShellInterpolator(configparser.Interpolation):
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        def identifiers() -> Generator[tuple[str | None, str], None, None]:
 | 
					        def identifiers() -> Generator[tuple[str | None, str], None, None]:
 | 
				
			||||||
            # extract all found identifiers and parse them
 | 
					            # extract all found identifiers and parse them
 | 
				
			||||||
            for identifier in ExtendedTemplate(value).get_identifiers():
 | 
					            for identifier in ShellTemplate(value).get_identifiers():
 | 
				
			||||||
                match identifier.split(":"):
 | 
					                match identifier.split(":"):
 | 
				
			||||||
                    case [lookup_option]:  # single option from the same section
 | 
					                    case [lookup_option]:  # single option from the same section
 | 
				
			||||||
                        yield None, lookup_option
 | 
					                        yield None, lookup_option
 | 
				
			||||||
@ -121,7 +112,7 @@ class ShellInterpolator(configparser.Interpolation):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        # resolve internal references
 | 
					        # resolve internal references
 | 
				
			||||||
        variables = dict(self._extract_variables(parser, value, defaults))
 | 
					        variables = dict(self._extract_variables(parser, value, defaults))
 | 
				
			||||||
        internal = ExtendedTemplate(escaped).safe_substitute(variables)
 | 
					        internal = ShellTemplate(escaped).safe_substitute(variables)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # resolve enriched environment variables by using default Template class
 | 
					        # resolve enriched environment variables by using default Template class
 | 
				
			||||||
        environment = Template(internal).safe_substitute(self.environment())
 | 
					        environment = Template(internal).safe_substitute(self.environment())
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										158
									
								
								src/ahriman/core/configuration/shell_template.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								src/ahriman/core/configuration/shell_template.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,158 @@
 | 
				
			|||||||
 | 
					#
 | 
				
			||||||
 | 
					# Copyright (c) 2021-2024 ahriman team.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# This file is part of ahriman
 | 
				
			||||||
 | 
					# (see https://github.com/arcan1s/ahriman).
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# This program is free software: you can redistribute it and/or modify
 | 
				
			||||||
 | 
					# it under the terms of the GNU General Public License as published by
 | 
				
			||||||
 | 
					# the Free Software Foundation, either version 3 of the License, or
 | 
				
			||||||
 | 
					# (at your option) any later version.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# This program is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
				
			||||||
 | 
					# GNU General Public License for more details.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# You should have received a copy of the GNU General Public License
 | 
				
			||||||
 | 
					# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					import fnmatch
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from collections.abc import Generator, Mapping
 | 
				
			||||||
 | 
					from string import Template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ShellTemplate(Template):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    extension to the default :class:`Template` class, which also adds additional tokens to braced regex and enables
 | 
				
			||||||
 | 
					    bash expansion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Attributes:
 | 
				
			||||||
 | 
					        braceidpattern(str): regular expression to match every character except for closing bracket
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    braceidpattern = r"(?a:[_a-z0-9][^}]*)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _REMOVE_BACK = re.compile(r"^(?P<key>\w+)%(?P<pattern>.+)$")
 | 
				
			||||||
 | 
					    _REMOVE_FRONT = re.compile(r"^(?P<key>\w+)#(?P<pattern>.+)$")
 | 
				
			||||||
 | 
					    _REPLACE = re.compile(r"^(?P<key>\w+)/(?P<pattern>.+)/(?P<replacement>.+)$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def _remove_back(source: str, pattern: str, *, greedy: bool) -> str:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        resolve "${var%(%)pattern}" constructions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            source(str): source string to match the pattern inside
 | 
				
			||||||
 | 
					            pattern(str): shell expression to match
 | 
				
			||||||
 | 
					            greedy(bool): match as much as possible or not
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Returns:
 | 
				
			||||||
 | 
					            str: result after removal ``pattern`` from the end of the string
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        regex = fnmatch.translate(pattern)
 | 
				
			||||||
 | 
					        compiled = re.compile(regex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = source
 | 
				
			||||||
 | 
					        start_pos = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        while m := compiled.search(source, start_pos):
 | 
				
			||||||
 | 
					            result = source[:m.start()]
 | 
				
			||||||
 | 
					            start_pos += m.start() + 1
 | 
				
			||||||
 | 
					            if greedy:
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def _remove_front(source: str, pattern: str, *, greedy: bool) -> str:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        resolve "${var#(#)pattern}" constructions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            source(str): source string to match the pattern inside
 | 
				
			||||||
 | 
					            pattern(str): shell expression to match
 | 
				
			||||||
 | 
					            greedy(bool): match as much as possible or not
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Returns:
 | 
				
			||||||
 | 
					            str: result after removal ``pattern`` from the start of the string
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        regex = fnmatch.translate(pattern)[:-2]  # remove \Z at the end of the regex
 | 
				
			||||||
 | 
					        if not greedy:
 | 
				
			||||||
 | 
					            regex = regex.replace("*", "*?")
 | 
				
			||||||
 | 
					        compiled = re.compile(regex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        m = compiled.match(source)
 | 
				
			||||||
 | 
					        if m is None:
 | 
				
			||||||
 | 
					            return source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return source[m.end():]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def _replace(source: str, pattern: str, replacement: str, *, greedy: bool) -> str:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        resolve "${var/(/)pattern/replacement}" constructions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            source(str): source string to match the pattern inside
 | 
				
			||||||
 | 
					            pattern(str): shell expression to match
 | 
				
			||||||
 | 
					            replacement(str): new substring
 | 
				
			||||||
 | 
					            greedy(bool): replace as much as possible or not
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Returns:
 | 
				
			||||||
 | 
					            str: result after replacing ``pattern`` by ``replacement``
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        match pattern:
 | 
				
			||||||
 | 
					            case from_back if from_back.startswith("%"):
 | 
				
			||||||
 | 
					                removed = ShellTemplate._remove_back(source, from_back[1:], greedy=False)
 | 
				
			||||||
 | 
					                return removed if removed == source else removed + replacement
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case from_front if from_front.startswith("#"):
 | 
				
			||||||
 | 
					                removed = ShellTemplate._remove_front(source, from_front[1:], greedy=False)
 | 
				
			||||||
 | 
					                return removed if removed == source else replacement + removed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case regular:
 | 
				
			||||||
 | 
					                regex = fnmatch.translate(regular)[:-2]  # remove \Z at the end of the regex
 | 
				
			||||||
 | 
					                compiled = re.compile(regex)
 | 
				
			||||||
 | 
					                return compiled.sub(replacement, source, count=not greedy)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def shell_substitute(self, mapping: Mapping[str, str], /, **kwargs: str) -> str:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        this method behaves the same as :func:`safe_substitute`, however also expands bash string operations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            mapping(Mapping[str, str]): key-value dictionary of variables
 | 
				
			||||||
 | 
					            **kwargs(str): key-value dictionary of variables passed as kwargs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Returns:
 | 
				
			||||||
 | 
					            str: string with replaced values
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        substitutions = (
 | 
				
			||||||
 | 
					            (self._REMOVE_BACK, self._remove_back, "%"),
 | 
				
			||||||
 | 
					            (self._REMOVE_FRONT, self._remove_front, "#"),
 | 
				
			||||||
 | 
					            (self._REPLACE, self._replace, "/"),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def generator(variables: dict[str, str]) -> Generator[tuple[str, str], None, None]:
 | 
				
			||||||
 | 
					            for identifier in self.get_identifiers():
 | 
				
			||||||
 | 
					                for regex, function, greediness in substitutions:
 | 
				
			||||||
 | 
					                    if m := regex.match(identifier):
 | 
				
			||||||
 | 
					                        source = variables.get(m.group("key"))
 | 
				
			||||||
 | 
					                        if source is None:
 | 
				
			||||||
 | 
					                            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        # replace pattern with non-greedy
 | 
				
			||||||
 | 
					                        pattern = m.group("pattern").removeprefix(greediness)
 | 
				
			||||||
 | 
					                        greedy = m.group("pattern").startswith(greediness)
 | 
				
			||||||
 | 
					                        # gather all additional args
 | 
				
			||||||
 | 
					                        args = {key: value for key, value in m.groupdict().items() if key not in ("key", "pattern")}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        yield identifier, function(source, pattern, **args, greedy=greedy)
 | 
				
			||||||
 | 
					                        break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        kwargs.update(mapping)
 | 
				
			||||||
 | 
					        substituted = dict(generator(kwargs))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self.safe_substitute(kwargs | substituted)
 | 
				
			||||||
@ -21,9 +21,9 @@ import shlex
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from dataclasses import dataclass, fields
 | 
					from dataclasses import dataclass, fields
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
from string import Template
 | 
					 | 
				
			||||||
from typing import Any, Generator, Self
 | 
					from typing import Any, Generator, Self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ahriman.core.configuration.shell_template import ShellTemplate
 | 
				
			||||||
from ahriman.core.utils import dataclass_view, filter_json
 | 
					from ahriman.core.utils import dataclass_view, filter_json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -180,8 +180,8 @@ class PkgbuildPatch:
 | 
				
			|||||||
            This function doesn't support recursive substitution
 | 
					            This function doesn't support recursive substitution
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if isinstance(self.value, str):
 | 
					        if isinstance(self.value, str):
 | 
				
			||||||
            return Template(self.value).safe_substitute(variables)
 | 
					            return ShellTemplate(self.value).shell_substitute(variables)
 | 
				
			||||||
        return [Template(value).safe_substitute(variables) for value in self.value]
 | 
					        return [ShellTemplate(value).shell_substitute(variables) for value in self.value]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def view(self) -> dict[str, Any]:
 | 
					    def view(self) -> dict[str, Any]:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ahriman.core.configuration import Configuration
 | 
					from ahriman.core.configuration import Configuration
 | 
				
			||||||
from ahriman.core.configuration.shell_interpolator import ExtendedTemplate, ShellInterpolator
 | 
					from ahriman.core.configuration.shell_interpolator import ShellInterpolator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _parser() -> dict[str, dict[str, str]]:
 | 
					def _parser() -> dict[str, dict[str, str]]:
 | 
				
			||||||
@ -27,14 +27,6 @@ def _parser() -> dict[str, dict[str, str]]:
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_extended_template() -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    must match colons in braces
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    assert ExtendedTemplate("$key:value").get_identifiers() == ["key"]
 | 
					 | 
				
			||||||
    assert ExtendedTemplate("${key:value}").get_identifiers() == ["key:value"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def test_extract_variables() -> None:
 | 
					def test_extract_variables() -> None:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    must extract variables list
 | 
					    must extract variables list
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										81
									
								
								tests/ahriman/core/configuration/test_shell_template.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								tests/ahriman/core/configuration/test_shell_template.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					from ahriman.core.configuration.shell_template import ShellTemplate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_shell_template_braceidpattern() -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must match colons in braces
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    assert ShellTemplate("$k:value").get_identifiers() == ["k"]
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k:value}").get_identifiers() == ["k:value"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_remove_back() -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must remove substring from the back
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k%removeme}").shell_substitute({"k": "please removeme"}) == "please "
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k%removeme*}").shell_substitute({"k": "please removeme removeme"}) == "please removeme "
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k%removem?}").shell_substitute({"k": "please removeme removeme"}) == "please removeme "
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k%%removeme}").shell_substitute({"k": "please removeme removeme"}) == "please removeme "
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k%%removeme*}").shell_substitute({"k": "please removeme removeme"}) == "please "
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k%%removem?}").shell_substitute({"k": "please removeme removeme"}) == "please removeme "
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k%removeme}").shell_substitute({}) == "${k%removeme}"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k%%removeme}").shell_substitute({}) == "${k%%removeme}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k%r3m0v3m3}").shell_substitute({"k": "please removeme"}) == "please removeme"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k%%r3m0v3m3}").shell_substitute({"k": "please removeme"}) == "please removeme"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_remove_front() -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must remove substring from the front
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k#removeme}").shell_substitute({"k": "removeme please"}) == " please"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k#*removeme}").shell_substitute({"k": "removeme removeme please"}) == " removeme please"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k#removem?}").shell_substitute({"k": "removeme removeme please"}) == " removeme please"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k##removeme}").shell_substitute({"k": "removeme removeme please"}) == " removeme please"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k##*removeme}").shell_substitute({"k": "removeme removeme please"}) == " please"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k##removem?}").shell_substitute({"k": "removeme removeme please"}) == " removeme please"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k#removeme}").shell_substitute({}) == "${k#removeme}"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k##removeme}").shell_substitute({}) == "${k##removeme}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k#r3m0v3m3}").shell_substitute({"k": "removeme please"}) == "removeme please"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k##r3m0v3m3}").shell_substitute({"k": "removeme please"}) == "removeme please"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_replace() -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must perform regular replacement
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k/in/out}").shell_substitute({"k": "in replace in"}) == "out replace in"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k/in*/out}").shell_substitute({"k": "in replace in"}) == "out"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k/*in/out}").shell_substitute({"k": "in replace in replace"}) == "out replace"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k/i?/out}").shell_substitute({"k": "in replace in"}) == "out replace in"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k//in/out}").shell_substitute({"k": "in replace in"}) == "out replace out"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k//in*/out}").shell_substitute({"k": "in replace in"}) == "out"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k//*in/out}").shell_substitute({"k": "in replace in replace"}) == "out replace"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k//i?/out}").shell_substitute({"k": "in replace in replace"}) == "out replace out replace"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k/in/out}").shell_substitute({}) == "${k/in/out}"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k//in/out}").shell_substitute({}) == "${k//in/out}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_replace_back() -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must replace substring from the back
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k/%in/out}").shell_substitute({"k": "in replace in"}) == "in replace out"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k/%in/out}").shell_substitute({"k": "in replace in "}) == "in replace in "
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_replace_front() -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    must replace substring from the front
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k/#in/out}").shell_substitute({"k": "in replace in"}) == "out replace in"
 | 
				
			||||||
 | 
					    assert ShellTemplate("${k/#in/out}").shell_substitute({"k": " in replace in"}) == " in replace in"
 | 
				
			||||||
		Reference in New Issue
	
	Block a user