# THIS IS AN AUTOMATICALLY GENERATED FILE. DO NOT MODIFY
# THIS FILE IS GENERATED BY 'scripts/combine.py'

# ================ shims.py ================
# Shim since MP has no abc module
def abstractmethod(func):
    return func

# ================ utils.py ================
import re

## From https://github.com/python/cpython/blob/0f9d0fb437fd206e281b84309f171f5dfe3ef0c2/Lib/re/__init__.py#L302

# SPECIAL_CHARS
# closing ')', '}' and ']'
# '-' (a range in character set)
# '&', '~', (extended character set operations)
# '#' (comment) and WHITESPACE (ignored) in verbose mode
_special_chars_map = {i: '\\' + chr(i) for i in b'()[]{}?*+-|^$\\.&~# \t\n\r\v\f'}

def escape(pattern):
    """
    Escape special characters in a string.
    """
    if isinstance(pattern, str):
        return translate(pattern, _special_chars_map) #MOD: Use alternate implementation of translate
    else:
        pattern = str(pattern, 'latin1')
        return translate(pattern, _special_chars_map).encode('latin1') #MOD: Use alternate implementation of translate
    
   
# Adaptation of str.translate, VERY ROUGHLY
def translate(s, table):
    if s is None: raise ValueError("String provided to translate must not be none")
    if len(s) == 0: return s

    ret = []
    
    for char in s:
        translation = _charmaptranslate_output(char, table)
        if translation is not None: # If value is none, char will be deleted
            ret.extend(translation)

    return ''.join(ret)

def _charmaptranslate_output(char, table):
    # Map Characters from Table
    try:
        val = table[ord(char)]
        return val
    except LookupError:
        return char
    
# Adaptation of re.finditer, based on available methods
def finditer(pattern, string, pos = 0, endpos = -1):
    string = string[pos: endpos]
    while string:
        mo = re.match(pattern, string)
        if (mo):
            yield mo
            string = string[len(mo.group(0)):]
        else:
            string = string[1:]

# ================ mapping.py ================
# From https://github.com/python/cpython/blob/0f9d0fb437fd206e281b84309f171f5dfe3ef0c2/Lib/_collections_abc.py#L787
# Modifictions noted with MOD:


# MOD Removed inheritance from Collection, at least for now. This does break some subclass hooks
# and instance checks, and could be restored with more work
class Mapping(): 
    """A Mapping is a generic container for associating key/value
    pairs.

    This class provides concrete generic implementations of all
    methods except for __getitem__, __iter__, and __len__.
    """

    __slots__ = ()

    # Tell ABCMeta.__new__ that this class should have TPFLAGS_MAPPING set.
    __abc_tpflags__ = 1 << 6 # Py_TPFLAGS_MAPPING

    @abstractmethod
    def __getitem__(self, key):
        raise KeyError

    def get(self, key, default=None):
        'D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None.'
        try:
            return self[key]
        except KeyError:
            return default

    def __contains__(self, key):
        try:
            self[key]
        except KeyError:
            return False
        else:
            return True

    def keys(self):
        "D.keys() -> a set-like object providing a view on D's keys"
        return KeysView(self)

    def items(self):
        "D.items() -> a set-like object providing a view on D's items"
        return ItemsView(self)

    def values(self):
        "D.values() -> an object providing a view on D's values"
        return ValuesView(self)

    def __eq__(self, other):
        if not isinstance(other, Mapping):
            return NotImplemented
        return dict(self.items()) == dict(other.items())

    __reversed__ = None

# MOD: #Mapping.register(mappingproxy)

# ================ mutablemapping.py ================
# From https://github.com/python/cpython/blob/0f9d0fb437fd206e281b84309f171f5dfe3ef0c2/Lib/_collections_abc.py#L919
# Modifications noted with MOD:


class MutableMapping(Mapping):
    """A MutableMapping is a generic container for associating
    key/value pairs.

    This class provides concrete generic implementations of all
    methods except for __getitem__, __setitem__, __delitem__,
    __iter__, and __len__.
    """

    __slots__ = ()

    @abstractmethod
    def __setitem__(self, key, value):
        raise KeyError

    @abstractmethod
    def __delitem__(self, key):
        raise KeyError

    __marker = object()

    def pop(self, key, default=__marker):
        '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
          If key is not found, d is returned if given, otherwise KeyError is raised.
        '''
        try:
            value = self[key]
        except KeyError:
            if default is self.__marker:
                raise
            return default
        else:
            del self[key]
            return value

    def popitem(self):
        '''D.popitem() -> (k, v), remove and return some (key, value) pair
           as a 2-tuple; but raise KeyError if D is empty.
        '''
        try:
            key = next(iter(self))
        except StopIteration:
            raise KeyError from None
        value = self[key]
        del self[key]
        return key, value

    def clear(self):
        'D.clear() -> None.  Remove all items from D.'
        try:
            while True:
                self.popitem()
        except KeyError:
            pass

    def update(self, other=(), **kwds): #MOD no position-only arguments
        ''' D.update([E, ]**F) -> None.  Update D from mapping/iterable E and F.
            If E present and has a .keys() method, does:     for k in E: D[k] = E[k]
            If E present and lacks .keys() method, does:     for (k, v) in E: D[k] = v
            In either case, this is followed by: for k, v in F.items(): D[k] = v
        '''
        if isinstance(other, Mapping):
            for key in other:
                self[key] = other[key]
        elif hasattr(other, "keys"):
            for key in other.keys():
                self[key] = other[key]
        else:
            for key, value in other:
                self[key] = value
        for key, value in kwds.items():
            self[key] = value

    def setdefault(self, key, default=None):
        'D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D'
        try:
            return self[key]
        except KeyError:
            self[key] = default
        return default


# MOD: # MutableMapping.register(dict)

# ================ chainmap.py ================
# From https://github.com/python/cpython/blob/main/Lib/collections/__init__.py#L985
# Modifications noted in comments with MOD:


class ChainMap(MutableMapping):
    ''' A ChainMap groups multiple dicts (or other mappings) together
    to create a single, updateable view.

    The underlying mappings are stored in a list.  That list is public and can
    be accessed or updated using the *maps* attribute.  There is no other
    state.

    Lookups search the underlying mappings successively until a key is found.
    In contrast, writes, updates, and deletions only operate on the first
    mapping.

    '''

    def __init__(self, *maps):
        '''Initialize a ChainMap by setting *maps* to the given mappings.
        If no mappings are provided, a single empty dictionary is used.

        '''
        self.maps = list(maps) or [{}]          # always at least one map

    def __missing__(self, key):
        raise KeyError(key)

    def __getitem__(self, key):
        for mapping in self.maps:
            try:
                return mapping[key]             # can't use 'key in mapping' with defaultdict
            except KeyError:
                pass
        return self.__missing__(key)            # support subclasses that define __missing__

    def get(self, key, default=None):
        return self[key] if key in self else default

    def __len__(self):
        return len(set().union(*self.maps))     # reuses stored hash values if possible

    def __iter__(self):
        d = {}
        for mapping in map(dict.fromkeys, reversed(self.maps)):
            d |= mapping                        # reuses stored hash values if possible
        return iter(d)

    def __contains__(self, key):
        return any(key in m for m in self.maps)

    def __bool__(self):
        return any(self.maps)

    # MOD: #@_recursive_repr()
    def __repr__(self):
        return f'{self.__class__.__name__}({", ".join(map(repr, self.maps))})'

    @classmethod
    def fromkeys(cls, iterable, *args):
        'Create a ChainMap with a single dict created from the iterable.'
        return cls(dict.fromkeys(iterable, *args))

    def copy(self):
        'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]'
        return self.__class__(self.maps[0].copy(), *self.maps[1:])

    __copy__ = copy

    def new_child(self, m=None, **kwargs):      # like Django's Context.push()
        '''New ChainMap with a new map followed by all previous maps.
        If no map is provided, an empty dict is used.
        Keyword arguments update the map or new empty dict.
        '''
        if m is None:
            m = kwargs
        elif kwargs:
            m.update(kwargs)
        return self.__class__(m, *self.maps)

    @property
    def parents(self):                          # like Django's Context.pop()
        'New ChainMap from maps[1:].'
        return self.__class__(*self.maps[1:])

    def __setitem__(self, key, value):
        self.maps[0][key] = value

    def __delitem__(self, key):
        try:
            del self.maps[0][key]
        except KeyError:
            raise KeyError(f'Key not found in the first mapping: {key!r}')

    def popitem(self):
        'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.'
        try:
            return self.maps[0].popitem()
        except KeyError:
            raise KeyError('No keys found in the first mapping.')

    def pop(self, key, *args):
        'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].'
        try:
            return self.maps[0].pop(key, *args)
        except KeyError:
            raise KeyError(f'Key not found in the first mapping: {key!r}')

    def clear(self):
        'Clear maps[0], leaving maps[1:] intact.'
        self.maps[0].clear()

    def __ior__(self, other):
        self.maps[0].update(other)
        return self

    def __or__(self, other):
        if not isinstance(other, _Mapping):
            return NotImplemented
        m = self.copy()
        m.maps[0].update(other)
        return m

    def __ror__(self, other):
        if not isinstance(other, _Mapping):
            return NotImplemented
        m = dict(other)
        for child in reversed(self.maps):
            m.update(child)
        return self.__class__(m)

# ================ template.py ================
# From https://github.com/python/cpython/blob/main/Lib/string.py
# Modifications noted in comments with MOD:

import re

_sentinel_dict = {}

class Template:
    """A string class for supporting $-substitutions."""

    delimiter = '$'
    # r'[a-z]' matches to non-ASCII letters when used with IGNORECASE, but
    # without the ASCII flag.  We can't add re.ASCII to flags because of
    # backward compatibility.  So we use the ?a local flag and [a-z] pattern.
    # See https://bugs.python.org/issue31672
    idpattern = r'[_a-zA-Z][_a-zA-Z0-9]*'
    braceidpattern = None
    #flags = _re.IGNORECASE # MOD: MP RE does not use flags; added uppercase letter to the idpattern above

           

    def __init__(self, template):
        self.template = template

        # MOD: The following logic was previously in __init_subclass__, but moved
        # here due to difference in inheritnace
        delim = escape(self.delimiter)
        id = self.idpattern
        bid = self.braceidpattern or self.idpattern
        # MOD: Raw f strings not permitted in Micropython
        self._pattern = f"{delim}(({delim})|({id})|{{({bid})}}|())"

        # Escape sequence of two delimiters #Escaped
        # delimiter and a Python identifier #Named
        # delimiter and a braced identifier #Braced
        # Other ill-formed delimiter exprs #Invalid

        self.pattern = re.compile(self._pattern)

    # Search for $$, $identifier, ${identifier}, and any bare $'s

    def _invalid(self, mo):
        i = mo.start('invalid')
        lines = self.template[:i].splitlines(keepends=True)
        if not lines:
            colno = 1
            lineno = 1
        else:
            colno = i - len(''.join(lines[:-1]))
            lineno = len(lines)
        raise ValueError('Invalid placeholder in string: line %d, col %d' %
                         (lineno, colno))

    def substitute(self, mapping=_sentinel_dict, **kws): # MOD: No position-only arguments
        if mapping is _sentinel_dict:
            mapping = kws
        elif kws:
            mapping = _ChainMap(kws, mapping)
        # Helper function for .sub()
        def convert(mo):
            # Check the most common path first.
            named = mo.group(3)
            if named is not None:
                return str(mapping[named])
            braced = mo.group(4)
            if braced is not None:
                return str(mapping[braced.strip('{}')])
            if mo.group(1) is not None:
                return self.delimiter
            if mo.group(5) is not None:
                self._invalid(mo)
            raise ValueError('Unrecognized named group in pattern',
                             self.pattern)
        return re.sub(self.pattern, convert, self.template)

    def safe_substitute(self, mapping=_sentinel_dict, **kws): # MOD: No position-only arguments
        if mapping is _sentinel_dict:
            mapping = kws
        elif kws:
            mapping = _ChainMap(kws, mapping)
        # Helper function for .sub()
        def convert(mo):
            # Check the most common path first.
            named = mo.group(3)
            if named is not None:
                try:
                    return str(mapping[named])
                except KeyError:
                    return mo.group(0)
            braced = mo.group(4)
            if braced is not None:
                try:
                    return str(mapping[braced.strip('{}')])
                except KeyError:
                        return mo.group(0)
            if mo.group(1) is not None:
                return self.delimiter
            if mo.group(4) is not None:
                return self.group(0)
            raise ValueError('Unrecognized named group in pattern',
                             self.pattern)
        return re.sub(self.pattern, convert, self.template)

    def is_valid(self):
        for mo in self.pattern.finditer(self.template):
            if mo.group('invalid') is not None:
                return False
            if (mo.group('named') is None
                and mo.group('braced') is None
                and mo.group('escaped') is None):
                # If all the groups are None, there must be
                # another group we're not expecting
                raise ValueError('Unrecognized named group in pattern',
                    self.pattern)
        return True
    
    # An alternate and relatively inefficient implementation
    def get_identifiers(self):
        ids = []
        for mo in finditer(self.pattern, self.template):
            named = mo.group(3) or mo.group(4) #Named or braced
            if named is not None and named not in ids:
                # add a named group only the first time it appears
                ids.append(named)
            elif (named is None
                and mo.group(5) is None
                and mo.group(1) is None):
                # If all the groups are None, there must be
                # another group we're not expecting
                raise ValueError('Unrecognized named group in pattern',
                    self.pattern)
        return ids
        

# Initialize Template.pattern.  __init_subclass__() is automatically called
# only for subclasses, not for the Template class itself.
# MOD: See notes in __init__ above Template.__init_subclass__()

