# perllib module for pythonizer, generated by makelib.py on Fri Nov 25 00:19:38 2022
#
# WARNING: Do not edit this file - to change the functions or add new ones, edit them in the pyf directory,
#          then re-run makelib.py
#
__author__ = """Joe Cool"""
___email__ = 'snoopyjc@gmail.com'
__version__ = '1.014'

import sys,os,re,fileinput,subprocess,collections.abc,warnings,inspect,itertools,signal,traceback,io,tempfile,calendar,types,random,dataclasses,builtins,codecs,struct,pprint,functools,argparse
import time as tm_py
import stat as st_py
_str = lambda s: "" if s is None else str(s)
try:
    import fcntl as fc_py
except Exception:
    pass

class Die(Exception):
    def __init__(self, *args,suppress_traceback=None):
        super().__init__(*args)
        if TRACEBACK and not suppress_traceback:
            cluck()

OUTPUT_FIELD_SEPARATOR = ''
INPUT_LAYERS = ''
INPUT_RECORD_SEPARATOR = "\n"
OUTPUT_AUTOFLUSH = 0
AUTODIE = 0
WARNING = 0
TRACEBACK = 0
LIST_SEPARATOR = ' '
OS_ERROR = ''
CHILD_ERROR = 0
_OPEN_MODE_MAP = {'<': 'r', '>': 'w', '+<': 'r+', '+>': 'w+', '>>': 'a', '+>>': 'a+', '|': '|-'}
_DUP_MAP = dict(STDIN=0, STDOUT=1, STDERR=2)
TRACE_RUN = 0
INPUT_LINE_NUMBER = 0
EVAL_ERROR = ''
OUTPUT_LAYERS = ''
BASETIME = tm_py.time()
OUTPUT_RECORD_SEPARATOR = ''


def init_package(name, is_class=False, isa=(), autovivification=True):
    """Initialize a package by creating a namespace for it"""
    pieces = name.split('.')
    parent = builtins
    parent_name = ''
    package_name = ''
    for i, piece in enumerate(pieces):
        if hasattr(parent, piece):
            namespace = getattr(parent, piece)
        else:
            if is_class and i == len(pieces)-1:
                class_parents = []
                if autovivification:
                    class_parents.append(_ArrayHashClass)
                for p in isa:
                    py = p.replace("'", '.').replace('::', '.')
                    if hasattr(builtins, f"{py}_"):
                        py = f"{py}_"
                    if hasattr(builtins, py):
                        class_parents.append(getattr(builtins, py))
                if autovivification:
                    namespace = type(name, tuple(class_parents), Hash())
                else:
                    namespace = type(name, tuple(class_parents), dict())
            else:
                namespace = types.SimpleNamespace()
            if parent_name:
                package_name = parent_name + '.' + piece
            else:
                package_name = piece
            namespace.__PARENT__ = parent_name
            namespace.__PACKAGE__ = package_name
            setattr(parent, piece, namespace)
            if parent != builtins:
                setattr(builtins, package_name, namespace)
        parent = namespace
        parent_name = package_name
    return namespace

def Array(init=None):
    """Array with autovivification"""
    return ArrayHash(init, isHash=False)


class _ArrayHash(collections.defaultdict, collections.abc.Sequence):
    """Implements autovivification of array elements and hash keys"""
    def __init__(self, fcn, isHash=None):
       self.isHash = isHash   # Can be None (not determined yet), False (is an Array), or True (is a Hash)
       super().__init__(fcn)

    def append(self, value):
        if self.isHash is None:
            self.isHash = False
        elif self.isHash:
            raise TypeError('Not an ARRAY reference')
        self[len(self)] = value

    def copy(self):
        if self.isHash:
            return Hash(self)
        elif self.isHash is None:
            return ArrayHash(self)
        else:
            return Array(self)

    def extend(self, lst):
        if self.isHash is None:
            self.isHash = False
        elif self.isHash:
            raise TypeError('Not an ARRAY reference')
        ln = len(self)
        for item in lst:
            self[ln] = item
            ln += 1

    def update(self, values):
        if self.isHash is None:
            self.isHash = True
        elif not self.isHash:
            raise TypeError('Not a HASH reference')
        for k, v in values.items():
            self[k] = v

    def pop(self, key=-1, default=None):
        if self.isHash:
            if key in self:
                value = self[key]
                del self[key]
                return value
            return default
        else:
            ls = len(self)
            if not ls:
                return None
            if key < 0:
                key += ls
            if key < ls:
                value = self[key]
                for i in range(key, ls-1):
                    self[i] = self[i+1]
                del self[ls-1]
                return value
            return None

    def remove(self, x):
        if self.isHash:
            raise TypeError('Not an ARRAY reference')
        for ndx in range(len(self)):
            if self[ndx] == x:
                return self.pop(ndx)
        raise ValueError(f"remove({x}): not found in Array")

    def __getitem__(self, index):
        if self.isHash:
            try:
                return super().__getitem__(index)
            except (TypeError, KeyError):
                return super().__getitem__(str(index))
        elif self.isHash is None:
            if isinstance(index, int) or isinstance(index, slice):
                self.isHash = False
            else:
                self.isHash = True
                try:
                    return super().__getitem__(index)
                except TypeError:
                    return super().__getitem__(str(index))
        if isinstance(index, int):
            if index < 0:
                index += len(self)
            return super().__getitem__(index)
        elif isinstance(index, slice):
            return Array([self[i] for i in range(*index.indices(len(self)))])
        else:
            raise TypeError('Not a HASH reference')

    def __setitem__(self, index, value):
        if self.isHash:
            try:
                super().__setitem__(index, value)
            except TypeError:
                super().__setitem__(str(index), value)
            return
        elif self.isHash is None:
            if isinstance(index, int) or isinstance(index, slice):
                self.isHash = False
            else:
                self.isHash = True
                try:
                    super().__setitem__(index, value)
                except TypeError:
                    super().__setitem__(str(index), value)
        if isinstance(index, int):
            if index < 0:
                index += len(self)
            for i in range(len(self), index):
                super().__setitem__(i, None)
            super().__setitem__(index, value)
            return
        elif isinstance(index, slice):
            for i in range(len(self), index.start):
                super().__setitem__(i, None)
            value = iter(value)
            ndx = index.start
            j = None
            for i in range(*index.indices(len(self))):
                try:
                    super().__setitem__(i, next(value))
                except StopIteration:
                    if j is None:
                        j = i
                    self.pop(j)
                ndx += 1
            rest = list(value)
            lr = len(rest)
            if lr:
                for i in range(len(self)-1,ndx-1,-1):  # Move everything else up
                    super().__setitem__(i+lr, super().__getitem__(i))
            for i in range(lr):
                super().__setitem__(i+ndx, rest[i])

    def __delitem__(self, index):
        if self.isHash:
            try:
                super().__delitem__(index)
            except (TypeError, KeyError):
                super().__delitem__(str(index))
        elif isinstance(index, int):
            if self.isHash:
                raise TypeError('Not an ARRAY reference')
            ls = len(self)
            if not ls:
                return
            if index < 0:
                index += len(self)
            super().__delitem__(index)
        elif isinstance(index, slice):
            if self.isHash:
                raise TypeError('Not an ARRAY reference')
            for i in range(*index.indices(len(self))):
                super().__delitem__(i)

    def __iter__(self):
        if self.isHash:
            for i in self.keys():
                yield i
        else:
            for i in range(len(self)):
                yield self[i]

    def __str__(self):
        if self.isHash:
            return str(dict(self))
        elif self.isHash is None:
            return ''
        return str(list(self))

    def __repr__(self):
        if self.isHash:
            return "Hash(" + self.__str__() + ")"
        elif self.isHash is None:
            return "ArrayHash(" + self.__str__() + ")"
        return "Array(" + self.__str__() + ")"

#    def __getattribute__(self, name):
#        if name in ('keys', 'values', 'items') and not self.isHash:
#            #raise AttributeError
#            def inner():
#                return []
#            return inner
#        return super().__getattribute__(name)

    def __add__(self, other):
        result = ArrayHash(self)
        if self.isHash or (isinstance(other, dict) and not isinstance(other, _ArrayHash)) or (hasattr(other, 'isHash') and other.isHash):
            result.update(other)
        elif self.isHash is None and isinstance(other, (int, float, str)):
            return other
        else:
            result.extend(other)
        return result

    def __iadd__(self, other):
        if self.isHash or (isinstance(other, dict) and not isinstance(other, _ArrayHash)) or (hasattr(other, 'isHash') and other.isHash):
            self.update(other)
        elif self.isHash is None and isinstance(other, (int, float, str)):
            return other
        else:
            self.extend(other)
        return self

    def __radd__(self, other):
        result = ArrayHash()
        if self.isHash or (isinstance(other, dict) and not isinstance(other, _ArrayHash)) or (hasattr(other, 'isHash') and other.isHash):
            result.update(other)
            result.update(self)
        elif self.isHash is None and isinstance(other, (int, float, str)):
            return other
        else:
            result.extend(other)
            result.extend(self)
        return result

    def __eq__(self, other):
        if self.isHash is None:
            if hasattr(other, 'isHash') and other.isHash is None:
                return True
            try:
                return '' == other
            except Exception:
                pass
            try:
                return 0 == other
            except Exception:
                pass
            try:
                return 0 == len(other)
            except Exception:
                pass
            if other is None:
                return True
            return False
        try:
            if(len(self) != len(other)):
                return False
            i1 = iter(self)
            i2 = iter(other)
            while True:
                if next(i1) != next(i2):
                    return False
            return True
        except StopIteration:
            return True
        except Exception:
            return False

    def __lt__(self, other):
        if self.isHash is None:
            if hasattr(other, 'isHash') and other.isHash is None:
                return False
            try:
                return '' < other
            except Exception:
                pass
            try:
                return 0 < other
            except Exception:
                pass
            try:
                return 0 < len(other)
            except Exception:
                pass
            if other is None:
                return False
            return False
        try:
            i1 = iter(self)
            i2 = iter(other)
            while True:
                ni1 = next(i1)
                try:
                    ni2 = next(i2)
                except StopIteration:
                    return False
                if ni1 < ni2:
                    return True
                elif ni1 > ni2:
                    return False
        except StopIteration:
            try:
                next(i2)
            except StopIteration:
                return False
            return True
        except Exception:
            return False

    def __ne__(self, other):
        return not self == other
    def __le__(self, other):
        return self < other or self == other
    def __ge__(self, other):
        return not self < other
    def __gt__(self, other):
        return not (self < other or self == other)

def ArrayHash(init=None,isHash=None):
    """Acts like an array or hash with autovivification"""
    result = _ArrayHash(ArrayHash,isHash=isHash)
    if init is not None:
        if isinstance(init, _ArrayHash):
            if init.isHash:
                result.update(init)
            else:
                result.extend(init)
        elif isinstance(init, collections.abc.Mapping):
            result.update(init)
        elif isinstance(init, collections.abc.Iterable) and not isinstance(init, str):
            result.extend(init)
        else:
            result.append(init)
    return result

def _partialclass(cls, *args, **kwds):
    class NewCls(cls):
        __init__ = functools.partialmethod(cls.__init__, *args, **kwds)
    return NewCls
_ArrayHashClass = _partialclass(_ArrayHash, ArrayHash)

def Hash(init=None):
    """Hash with autovivification"""
    return ArrayHash(init, isHash=True)

def abspath(*args):
    """Implementation of perl Cwd::abs_path function"""
    if len(args) == 0:
        return os.path.abspath(os.getcwd())
    return os.path.abspath(args[0])

def add_element(base, index, value):
    """Implementation of += on an array element"""
    try:
        base[index] += value
    except TypeError:
        if isinstance(value, int) or isinstance(value, float):
            base[index] = num(base[index]) + value
        elif value is None:
            base[index] = num(base[index])
        else:
            base[index] = num(base[index]) + num(value)
    return base[index]

def add_path(path_list):
    """Add a path_list to the system path list"""
    sys.path[0:0] = path_list

def and_element(base, index, value):
    base[index] &= value
    return base[index]

def assign_global(packname, varname, value):
    """Assigns a value to a package global variable and returns the value"""
    namespace = getattr(builtins, packname)
    setattr(namespace, varname, value)
    return value

def assign_hash(h, keys, values):
    """Assign a hash with a list of hash keys and a list of values"""
    keys = list(keys)
    values = list(values)
    if len(keys) == len(values):
        for i in range(len(keys)):
            h[_str(keys[i])] = values[i]
    else:
        for i in range(len(keys)):
            h[_str(keys[i])] = values[i] if i < len(values) else None
    return h

def assign_sparse(lst, indexes, values):
    """Assign a list with a sparse list of indexes and a list of values"""
    if len(indexes) == len(values):
        for i in range(len(indexes)):
            lst[int_(indexes[i])] = values[i]
    else:
        for i in range(len(indexes)):
            lst[int_(indexes[i])] = values[i] if i < len(values) else None
    return lst

def autoflush(self, arg=1):
    """Method added to FH to support OO perl"""
    orig = self._autoflush if hasattr(self, '_autoflush') else 0
    self._autoflush = arg
    if arg:
        self._orig_writelines = self.writelines
        def new_writelines(self, lines):
            self._orig_writelines(lines)
            self.flush()
        self.writelines = types.MethodType(new_writelines, self)
        if hasattr(self, 'write'):
            self._orig_write = self.write
            def new_write(self, b):
                result = self._orig_write(b)
                self.flush()
                return result
            self.write = types.MethodType(new_write, self)
    elif hasattr(self, '_orig_writelines'):
        self.writelines = self._orig_writelines
        if hasattr(self, '_orig_write'):
            self.write = self._orig_write
    return orig

def basename(path, *suffixes):
    """Implementation of perl basename function"""
    path = re.sub(r'(.)/*$', r'\1', path, flags=re.S)
    [basename, dirname, suffix] = fileparse(path, *map(re.escape, suffixes))
    if len(suffix) and not len(basename):
        basename = suffix

    if not len(basename):
        basename = dirname

    return basename

def binmode(fh,mode='b',encoding=None,errors=None,newline=None):
    """Handle binmode"""
    global OS_ERROR, TRACEBACK, AUTODIE
    try:
        omode = ''
        fno = None
        try:
            fno = fh.fileno()
            fh.flush()      # could be a closed file
            omode = fh.mode # could not have a mode
        except Exception:
            pass
        if mode is None:
            mode = omode.replace('b', '')
        else:
            mode = omode + mode
        if encoding is None and 'b' not in mode:
            encoding = fh.encoding
        if errors is None and 'b' not in mode:
            errors = fh.errors
        if fno is None:
            result = io.TextIOWrapper(io.BufferedIOBase(), encoding=encoding, errors=errors, newline=newline)
        else:
            result = os.fdopen(os.dup(fno), mode, encoding=encoding, errors=errors, newline=newline)
        if hasattr(fh, 'filename') and hasattr(fh, '_name'):   # from tempfile
            result.filename = fh.filename
            result._name = fh._name
        if hasattr(fh, 'say'):        # from IO::File
            return _create_all_fh_methods(fh)
        return result
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(f"binmode failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return None


def binmode_dynamic(fh, mode):
    """Handle binmode where the mode/layers are dynamic"""
    encoding = None
    errors = None
    newline = None
    mmode = mode

    ext = None
    if ':' in mode:
        mode, ext = mode.split(':')
    if mode in _OPEN_MODE_MAP:
        mode = _OPEN_MODE_MAP[mode]
    else:
        mode = 'r'
    if ext:
        if ext == 'raw' or ext == 'bytes':
            mode += 'b'
        elif ext.startswith('encoding('):
            encoding = ext.replace('encoding(','').replace(')','')
            errors = 'replace'
        elif ext == 'utf8':
            encoding = 'UTF-8'
            errors = 'ignore'
    return binmode(fh, mode, encoding=encoding, errors=errors, newline=newline)

def bless(obj, classname, isa=(), is_tie_package=False):
    """Create an object for obj in classname"""
    if not isinstance(classname, str):
        if hasattr(classname, '__name__'):  # They sent us the class object
            classname = classname.__name__
        elif hasattr(classname, '__class__'): # They sent us an instance
            classname = classname.__class__.__name__
    if not hasattr(builtins, classname):
        init_package(classname, is_class=True, isa=isa)
    result_class = getattr(builtins, classname)
    result = result_class()
    self = result
    if is_tie_package:      # avoid infinite recursion calling __setitem__ for a tied class
        self = result.__dict__
    if hasattr(obj, 'isHash'):
        if obj.isHash:
            for key, value in obj.items():
                self[key] = value
        else:
            for i, value in enumerate(obj):
                self[i] = value
    elif isinstance(obj, collections.abc.Mapping):
        for key, value in obj.items():
            self[key] = value
    elif isinstance(obj, collections.abc.Iterable) and not isinstance(obj, str):
        for i, value in enumerate(obj):
            self[i] = value
    elif WARNING:
        carp(f"'bless' {classname} not implemented on {type(obj)} type object")

    return result

def caller(expr=None):
    """ Implementation of caller function in perl"""
    try:
        fr = sys._getframe(2 if expr is None else (max(int(expr),0)+1))
        package = 'main'
        if hasattr(fr.f_builtins, '__PACKAGE__'):
            package = fr.f_builtins.__PACKAGE__
        if expr is None:
            return [package, fr.f_code.co_filename, fr.f_lineno]
        return [package, fr.f_code.co_filename, fr.f_lineno,
                fr.f_code.co_name, fr.f_code.co_argcount, 1,
                '', 0, 0, 0, 0]
    except ValueError:
        return None

def carp(*args,skip=1):
    """Warn with no backtrace"""
    if TRACEBACK:
        print(longmess(*args, skip=skip), end='', file=sys.stderr)
    else:
        print(shortmess(*args, skip=skip), end='', file=sys.stderr)
    return 1

def cgtime(secs=None):
    """Replacement for perl built-in gmtime function in scalar context"""
    return tm_py.asctime(tm_py.gmtime(secs))


def chdir(d):
    """Implementation of perl chdir"""
    global AUTODIE, TRACEBACK, OS_ERROR
    try:
        os.chdir(d)
        return 1
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(OS_ERROR,skip=2)
        if AUTODIE:
            raise
        return ''



def chmod(mode, *argv):
    """Implementation of perl chmod function"""
    result = 0
    for arg in argv:
        try:
            os.chmod(arg, mode)
            result += 1
        except Exception:
            pass
    return result

def chomp_element(base, index, value):
    """Implementation of perl = and chomp on an array element"""
    if value is None:
        value = ''
    if INPUT_RECORD_SEPARATOR is None or isinstance(INPUT_RECORD_SEPARATOR, int):
        base[index] = value
        return 0

    if INPUT_RECORD_SEPARATOR == '':
        chomped_value = value.rstrip("\n")
    else:
        chomped_value = value.rstrip(INPUT_RECORD_SEPARATOR)

    base[index] = chomped_value
    return len(value) - len(base[index])

def chomp_global(packname, varname, value):
    """Assigns a value to a package global variable, does a chomp and returns the number of chars chopped"""
    namespace = getattr(builtins, packname)
    if value is None:
        value = ''
    if INPUT_RECORD_SEPARATOR is None or isinstance(INPUT_RECORD_SEPARATOR, int):
        setattr(namespace, varname, value)
        return 0

    if INPUT_RECORD_SEPARATOR == '':
        chomped_value = value.rstrip("\n")
    else:
        chomped_value = value.rstrip(INPUT_RECORD_SEPARATOR)
    setattr(namespace, varname, chomped_value)
    return len(value) - len(chomped_value)

def chomp_with_result(var):
    """Implementation of chomp where the count of chars removed is needed.  Returns a tuple
    of (result, count)."""
    count = 0
    if var is None:
        var = ''
    if INPUT_RECORD_SEPARATOR is None or isinstance(INPUT_RECORD_SEPARATOR, int):
        return (var, 0)
    if (hasattr(var, 'isHash') and var.isHash) or (not hasattr(var, 'isHash') and isinstance(var, collections.abc.Mapping)):
        for k, v in var.items():
            (var[k], cnt) = chomp_with_result(v)
            count += cnt
        return (var, count)
    if isinstance(var, collections.abc.Iterable) and not isinstance(var, str):
        for i, v in enumerate(var):
            (var[i], cnt) = chomp_with_result(v)
            count += cnt
        return (var, count)

    var = str(var)
    if INPUT_RECORD_SEPARATOR == '':
        result = var.rstrip("\n")
    elif var.endswith(INPUT_RECORD_SEPARATOR):
        result = var[0:-len(INPUT_RECORD_SEPARATOR)]
    else:
        result = var
    return (result, len(var) - len(result))

def chop_element(base, index, value):
    """Implementation of perl = and chop on an array element"""
    if value is None:
        value = ''
    result = value[-1:]
    base[index] = value[0:-1]
    return result

def chop_global(packname, varname, value):
    """Assigns a value to a package global variable, does a chop and returns the value chopped"""
    namespace = getattr(builtins, packname)
    if value is None:
        value = ''
    result = value[-1:]
    setattr(namespace, varname, value[0:-1])
    return result

def chop_without_result(var):
    """Implementation of chop where the last char removed is not needed.  Returns a tuple
    of (result, '')."""
    if var is None:
        var = ''
    if (hasattr(var, 'isHash') and var.isHash) or (not hasattr(var, 'isHash') and isinstance(var, collections.abc.Mapping)):
        for k, v in var.items():
            (var[k], _) = chop_without_result(v)
        return (var, '')
    if isinstance(var, collections.abc.Iterable) and not isinstance(var, str):
        for i, v in enumerate(var):
            (var[i], _) = chop_without_result(v)
        return (var, '')

    var = str(var)
    result = var[0:-1]
    return (result, '')

def chop_with_result(var):
    """Implementation of chop where the last char removed is needed.  Returns a tuple
    of (result, last_c)."""
    if var is None:
        var = ''
    if (hasattr(var, 'isHash') and var.isHash) or (not hasattr(var, 'isHash') and isinstance(var, collections.abc.Mapping)):
        for k, v in var.items():
            (var[k], last_c) = chop_with_result(v)
        return (var, last_c)
    if isinstance(var, collections.abc.Iterable) and not isinstance(var, str):
        for i, v in enumerate(var):
            (var[i], last_c) = chop_with_result(v)
        return (var, last_c)

    var = str(var)
    last_c = var[-1:]
    result = var[0:-1]
    return (result, last_c)

def closedir(DIR):
    """Implementation of perl closedir"""
    DIR[0] = None
    DIR[1] = None

def close_(fh):
    """Implementation of perl close"""
    global AUTODIE, TRACEBACK, OS_ERROR, TRACE_RUN
    try:
        if hasattr(fh, '_sp'):      # issue 72: subprocess
            fh.flush()
            fh._sp.communicate()
            if TRACE_RUN:
                sp = subprocess.CompletedProcess(f"open({fh._file})", fh._sp.returncode)
                carp(f'trace close({fh._file}): {repr(sp)}', skip=2)
            fh.close()
            if fh._sp.returncode:
                raise IOError(f"close({fh._file}): failed with {fh._sp.returncode}")
            return 1
        if fh is None:
            raise TypeError(f"close(None): failed")
        #if WARNING and fh.closed:
            #carp(f"close failed: Filehandle is already closed", skip=2)
        fh.close()
        return 1
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(OS_ERROR,skip=2)
        if AUTODIE:
            raise
        return ''



def cluck(*args,skip=1):
    """Warn with stack backtrace"""
    print(longmess(*args, skip=skip), end='', file=sys.stderr)
    return 1

def cmp(a,b):
    """3-way comparison like the cmp operator in perl"""
    if a is None:
        a = ''
    if b is None:
        b = ''
    return (a > b) - (a < b)

def concat_element(base, index, value):
    """Implementation of perl .= on an array element"""
    try:
        base[index] += value
    except TypeError:
        if value is None:
            if base[index] is None:
                base[index] = ''
            else:
                base[index] = str(base[index])
        else:
            if base[index] is None:
                base[index] = str(value)
            else:
                base[index] = str(base[index]) + str(value)
    return base[index]

def confess(*args,skip=1):
    """Error with stack backtrace"""
    raise Die(longmess(*args, skip=skip),suppress_traceback=True)

def croak(*args,skip=1):
    """Error with no backtrace"""
    if TRACEBACK:
        raise Die(longmess(*args, skip=skip),suppress_traceback=True)
    raise Die(shortmess(*args, skip=skip),suppress_traceback=True)

def curdir():
    """Implementation of File::Spec->curdir"""
    return '.'


def die(*args):
    """For when 'die' is used in a lambda function or expression"""
    m = ''.join(args)
    raise Die(m)

def dirname(fullname):
    """Emulation of File::Basename qw(dirname) for unix"""
    def fileparse(fullname):
        [dirpath,basename] = (_m:=re.search(re.compile(r'^(.*/)?(.*)',re.S),fullname),_m.groups() if _m else [None,None])[1]

        if not (dirpath):
            dirpath = './'
        return (basename, dirpath)

    [basename, dirname] = fileparse(fullname)

    dirname = re.sub(r'(.)/*$', r'\1', dirname, flags=re.S)
    if not len(basename):
        [basename, dirname] = fileparse(dirname)
        dirname = re.sub(r'(.)/*$', r'\1', dirname, flags=re.S)

    return dirname

def divide_element(base, index, value):
    base[index] /= value
    return base[index]

init_package('Data.Dumper')

Data.Dumper.Indent_v = 2
Data.Dumper.Trailingcomma_v = False
Data.Dumper.Purity_v = 0
Data.Dumper.Pad_v = ''
Data.Dumper.Varname_v = "VAR"
Data.Dumper.Useqq_v = 0
Data.Dumper.Terse_v = False
Data.Dumper.Freezer_v = ''
Data.Dumper.Toaster_v = ''
Data.Dumper.Deepcopy_v = 0
Data.Dumper.Quotekeys_v = 1
Data.Dumper.Bless_v = 'bless'
Data.Dumper.Pair_v = ':'
Data.Dumper.Maxdepth_v = 0
Data.Dumper.Maxrecurse_v = 1000
Data.Dumper.Useperl_v = 0
Data.Dumper.Sortkeys_v = 0
Data.Dumper.Deparse_v = False
Data.Dumper.Sparseseen_v = False

def Dumper(*args):
    """Implementation of Data::Dumper"""
    result = []
    pp = pprint.PrettyPrinter(indent=Data.Dumper.Indent_v, 
                       depth=None if Data.Dumper.Maxdepth_v==0 else Data.Dumper.Maxdepth_v,
                       compact=Data.Dumper.Terse_v,
                       sort_dicts=Data.Dumper.Sortkeys_v)
    for i, arg in enumerate(args, start=1):
        if Data.Dumper.Terse_v:
            result.append(f"{Data.Dumper.Pad_v}" + pp.pformat(arg))
        else:
            result.append(f"{Data.Dumper.Pad_v}{Data.Dumper.Varname_v}{i} = " + pp.pformat(arg))
    spacer = " " if Data.Dumper.Indent_v == 0 else "\n"
    return spacer.join(result)

def dup(file,mode,checked=True,equals=False,encoding=None,errors=None):
    """Replacement for perl built-in open function when the mode contains '&'.  Keyword arg
    'checked' means the result will be checked.  Keyword arg 'equals' means that '&=' was specified,
    so skip the os.dup operation."""
    global OS_ERROR, TRACEBACK, AUTODIE
    try:
        if isinstance(file, io.IOBase):     # file handle
            file.flush()
            if encoding is None:
                encoding = file.encoding
            if errors is None:
                errors = file.errors
            if equals:
                return os.fdopen(file.fileno(), mode, encoding=encoding, errors=errors)
            return os.fdopen(os.dup(file.fileno()), mode, encoding=encoding, errors=errors)
        if isinstance(file, int):
            pass
        elif (_m:=re.match(r'=?(\d+)', file)):
            file = int(_m.group(1))
        elif file in _DUP_MAP:
            file = _DUP_MAP[file]
        if equals:
            return _create_fh_methods(os.fdopen(file, mode, encoding=encoding, errors=errors))
        return _create_fh_methods(os.fdopen(os.dup(file), mode, encoding=encoding, errors=errors))
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(f"dup failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        if checked:
            return None
        fh = io.StringIO()
        fh.close()
        return _create_fh_methods(fh)


def each(h_a):
    """See https://perldoc.perl.org/functions/each"""
    key = str(id(h_a))       # Unique memory address of object
    if not hasattr(each, key):
        setattr(each, key, iter(h_a))
    it = getattr(each, key)
    try:
        v = next(it)
    except StopIteration:
        setattr(each, key, iter(h_a))
        return []

    if hasattr(h_a, 'keys'):
        return [v, h_a[v]]
    ndx_key = key + 'i'
    i = 0;
    if hasattr(each, ndx_key):
        i = getattr(each, ndx_key)
    setattr(each, ndx_key, i+1)
    return [i, v]

def eof(fh):
    global AUTODIE, TRACEBACK
    """Implementation of perl eof"""
    try:
        pos = fh.tell()
        return (pos == os.path.getsize(fh))
    except Exception as e:
        if TRACEBACK:
            cluck(f"eof failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return 1



def exc(e):
    """Exception information like perl, e.g. message at issue_42.pl line 21."""
    try:
        m = str(e)
        if m.endswith('\n'):
            return m
        return f"{m} at {os.path.basename(sys.exc_info()[2].tb_frame.f_code.co_filename)} line {sys.exc_info()[2].tb_lineno}.\n"
    except Exception:
        return str(e)


def exponentiate_element(base, index, value):
    base[index] **= value
    return base[index]

def extract_bracketed(text, delimiters='{}()[]<>', prefix_pattern='^\s*'):
    """Implementation of Text::Bracketed::extract_bracketed in list context.  Returns a list with
    (extracted_substring, updated_text, skipped_prefix)"""
    open_to_close=dict()
    close_to_open=dict()
    if '{' in delimiters:
        open_to_close['{'] = '}'
        close_to_open['}'] = '{'
    if '(' in delimiters:
        open_to_close['('] = ')'
        close_to_open[')'] = '('
    if '[' in delimiters:
        open_to_close['['] = ']'
        close_to_open[']'] = '['
    if '<' in delimiters:
        open_to_close['<'] = '>'
        close_to_open['>'] = '<'

    stack = []

    prefix = ''
    orig_text = text
    if (_m := re.match(prefix_pattern, text)):
        prefix = _m.group(0)
        text = text[len(prefix):]

    if not text or text[0] not in open_to_close:
        return [None, orig_text, None]

    for i, c in enumerate(text):
        if c in open_to_close:
            stack.append(c)
        elif c in close_to_open:
            try:
                top = stack.pop()
                if top != close_to_open[c]:
                    return [None, orig_text, None]
                if not stack:
                    return [text[:i+1], text[i+1:], prefix]
            except IndexError:
                return [None, orig_text, None]
    return [None, orig_text, None]

def extract_bracketed_s(text, delimiters='{}()[]<>', prefix_pattern='^\s*'):
    """Implementation of Text::Bracketed::extract_bracketed in scalar context.  Returns a tuple with
    (updated_text, extracted_substring)"""
    open_to_close=dict()
    close_to_open=dict()
    if '{' in delimiters:
        open_to_close['{'] = '}'
        close_to_open['}'] = '{'
    if '(' in delimiters:
        open_to_close['('] = ')'
        close_to_open[')'] = '('
    if '[' in delimiters:
        open_to_close['['] = ']'
        close_to_open[']'] = '['
    if '<' in delimiters:
        open_to_close['<'] = '>'
        close_to_open['>'] = '<'

    stack = []

    if (_m := re.match(prefix_pattern, text)):
        text = text[len(_m.group(0)):]

    if not text or text[0] not in open_to_close:
        return (text, None)

    for i, c in enumerate(text):
        if c in open_to_close:
            stack.append(c)
        elif c in close_to_open:
            try:
                top = stack.pop()
                if top != close_to_open[c]:
                    return (text, None)
                if not stack:
                    return (text[i+1:], text[:i+1])
            except IndexError:
                return (text, None)
    return (text, None)

def fcntl(fh, func, scalar):
    global AUTODIE, TRACEBACK, OS_ERROR
    """Implementation of perl fcntl"""
    try:
        result = fc_py.fcntl(fh, func, scalar)
        if result == 0:
            return "0 but true"
        if result == -1:
            return None
        return result
    except Exception as e:
        OS_ERROR = str(e)
        if TRACEBACK:
            cluck(f"fcntl failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return None

def fdopen(fh, fd, mode):
    """Implementation of $fh->fdopen(fd, mode)"""
    if isinstance(fd, str) and re.match(r'^\d+$', fd):
        fd = int(fd)
    if isinstance(fd, int):
        fd = f'={fd}'
    if fh and not fh.closed:
        fh.close()
    return _create_all_fh_methods(open_dynamic(_open_mode_string(mode) + '&' + fd))

_fileinput_iter = None

def fileinput_next(files=None, inplace=False, backup='',*, mode='r', openhook=None, encoding=None, errors=None):
    """Implementation of fileinput.input() where it can be called multiple times for the same <> operator"""
    global _fileinput_iter

    if _fileinput_iter is None:
        try:
            (mode, encoding, errors, newline) = handle_open_pragma(mode, encoding, errors)
        except NameError:
            pass
        try:
            _fileinput_iter = fileinput.input(files=files, inplace=inplace, backup=backup, mode=mode, openhook=openhook,
                                          encoding=encoding, errors=errors)
        except TypeError:   # pythons older than 3.10 don't have encoding and errors
            _fileinput_iter = fileinput.input(files=files, inplace=inplace, backup=backup, mode=mode, openhook=openhook)
    
    result = next(_fileinput_iter, None)
    if result is None:
        _fileinput_iter = None
    return result

def fileno(fh):
    global OS_ERROR, TRACEBACK, AUTODIE
    try:
        if isinstance(fh, list) and len(fh) == 2 and isinstance(fh[0], list) and isinstance(fh[1], int):    # DIRHANDLE
            raise TypeError("Directories have no associated fileno");
        return fh.fileno()
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(f"fileno failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return None


def fileparse(*args):
    """Split a path into basename, dirpath, and (optional) suffixes.
    Translated from perl File::Basename for unix, plus annotations"""
    fullname = args[0]
    suffixes = args[1:]
    if fullname is None:
        raise Die("fileparse(): need a valid pathname")
    fullname = str(fullname)
    [dirpath,basename] = (_m:=re.search(re.compile(r'^(.*/)?(.*)',re.S),fullname),_m.groups() if _m else [None,None])[1]
    if not (dirpath):
        dirpath = './'

    tail=''
    suffix=''
    if suffixes:
        for suffix in suffixes:
            if(isinstance(suffix, re.Pattern)): # in case they use qr
                suffix = suffix.pattern
            pat=f"({suffix})$"
            def sub(_m):
                nonlocal tail
                tail = _m.group(1) + tail
                return ''
            basename = re.sub(re.compile(pat,re.S),sub,basename,count=1)

    return (basename, dirpath, tail)

def file_exists(path):        # -e
    if not path:
        return ''   # False
    if hasattr(path, 'cando'):
        return 1    # True
    return 1 if os.path.exists(path) else ''

def file_size(path):        # -s
    if not path:
        return None
    if hasattr(path, '_size'):
        return path._size
    return os.path.getsize(path)

def filter_map(f, i):
    """Given a function f that returns a tuple of (new_val, include) and
    an iterable i, return an iterable of new_vals where include is True"""
    for v in i:
        (new_val, include) = f(v)
        if include:
            yield new_val


_finditer_pattern = None
_finditer_string = None
_finditer_iter = None

def finditer_next(pattern, string, flags=0):
    """Implementation of re.finditer() where it can be called multiple times for the same pattern and string"""
    global _finditer_iter, _finditer_pattern, _finditer_string

    if _finditer_pattern != pattern or _finditer_string != string:
        _finditer_iter = None
    if _finditer_iter is None:
        _finditer_iter = re.finditer(pattern, string, flags)
        _finditer_pattern = pattern
        _finditer_string = string

    result = next(_finditer_iter, None)
    if result is None:
        _finditer_iter = None
    return result

def flatten(lst):
    """Flatten a list down to 1 level"""
    result = []
    if (not isinstance(lst, collections.abc.Iterable)) or isinstance(lst, str):
        return [lst]
    for elem in lst:
        if hasattr(elem, 'isHash'):   # Array or Hash
            result = Array(result)
            if elem.isHash:
                for e in itertools.chain.from_iterable(elem.items()):
                    result.extend(flatten(e))
            else:
                for e in elem:
                    result.extend(flatten(e))
        elif isinstance(elem, collections.abc.Mapping):
            for e in itertools.chain.from_iterable(elem.items()):
                result.extend(flatten(e))
        elif isinstance(elem, collections.abc.Iterable) and not isinstance(elem, str):
            for e in elem:
                result.extend(flatten(e))
        else:
            result.append(elem)
    return result


def flock(fd, operation):
    """ Replacement for perl Fcntl flock function"""
    global OS_ERROR, TRACEBACK, AUTODIE
    try:
        # To avoid the possibility of miscoordination, Perl now flushes FILEHANDLE before locking or unlocking it.
        fd.flush()
        fc_py.flock(fd, operation)
        return 1
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(f"flock failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return ''
    

def flt(expr):
    """Convert expr to a float number
       Ref: https://squareperl.com/en/how-perl-convert-string-to-number"""
    if not expr:
        return 0
    try:
        return +expr    # Unary plus: The fastest way to test for numeric
    except Exception:
        pass
    for _ in range(2):
        try:
            f = float(expr)
            return f
        except Exception:
            pass
        if isinstance(expr, str):
            if not (m:=re.match(r'^\s*([+-]?(?:\d+(?:[.]\d*)?(?:[eE][+-]?\d+)?|[.]\d+(?:[eE][+-]?\d+)?))', expr)):
                break
            expr = m.group(1);
        elif isinstance(expr, bytes):
            if not (m:=re.match(br'^\s*([+-]?(?:\d+(?:[.]\d*)?(?:[eE][+-]?\d+)?|[.]\d+(?:[eE][+-]?\d+)?))', expr)):
                break
            expr = m.group(1);
        else:
            return expr
    if WARNING:
        caller = inspect.getframeinfo(inspect.stack()[1][0])
        warnings.warn(f"Argument \"{expr}\" isn't numeric in numeric context at {caller.filename}:{caller.lineno}")
    return 0

def format_(fmt, args=None):
    """Like % formatter in python, but auto-converts the args to the proper types"""
    fmt = str(fmt)
    if args is None:
        args = []
    if isinstance(args, collections.abc.Iterable) and not isinstance(args, str):
        args = list(args)
    else:
        args = [args]
    fmt_regex = re.compile(r'%(?:[#0+ -])*(\*|\d+)?([.](?:\*|\d+))?[hlL]?([diouxXeEfFgGcrsa])')
    num_fmts = set('diouxXeEfFgG')
    i = 0
    for m in re.finditer(fmt_regex, fmt.replace('%%', '')):
        if i >= len(args):
            args.append('')
        if m.group(1) == '*':
            args[i] = int_(args[i])
            i += 1
        if m.group(2) == '.*':
            args[i] = int_(args[i])
            i += 1
        if m.group(3) in num_fmts:
            args[i] = num(args[i])
        i += 1
    return fmt % tuple(args)


def format_write(fh):
    """Implementation of perl format_write"""
    raise NotImplementedError

def getc(fh):
    """Implementation of perl getc"""
    fh._last_pos = fh.tell()    # for ungetc
    return fh.read(1)

def getpos(fh):
    """Implementation of perl $fh->getpos"""
    return fh.tell()
    

def getsignal(signum):
    """Handle references to %SIG not on the LHS of expression"""
    result = signal.getsignal(signum)
    if result == signal.SIG_IGN:
        return 'IGNORE'
    elif result == signal.SIG_DFL:
        return 'DEFAULT'
    return result


def get_access_age_days(path):        # -A
    """Implementation of perl -A"""
    global OS_ERROR, TRACEBACK, AUTODIE
    if not path:
        return None
    if hasattr(path, '_atime'):
        t = path._atime
    else:
        try:
            if hasattr(path, 'fileno') and os.stat in os.supports_fd:
                path = path.fileno()
            elif hasattr(path, 'name'):
                path = path.name
            t = os.path.getatime(path)
        except Exception as _e:
            OS_ERROR = str(_e)
            if TRACEBACK:
                cluck(f"-A {path} failed: {OS_ERROR}",skip=2)
            if AUTODIE:
                raise
            return 0
    return (BASETIME - t) / 86400.0
    

def get_creation_age_days(path):       # -C
    """Implementation of perl -C"""
    global OS_ERROR, TRACEBACK, AUTODIE
    if not path:
        return None
    if hasattr(path, '_ctime'):
        t = path._ctime
    else:
        try:
            if hasattr(path, 'fileno') and os.stat in os.supports_fd:
                path = path.fileno()
            elif hasattr(path, 'name'):
                path = path.name
            t = os.path.getctime(path)
        except Exception as _e:
            OS_ERROR = str(_e)
            if TRACEBACK:
                cluck(f"-C {path} failed: {OS_ERROR}",skip=2)
            if AUTODIE:
                raise
            return 0
    return (BASETIME - t) / 86400.0
    

def get_element(base, index):
    """Safe element getter from a list, tuple, or Array - returns None if the element doesn't exist"""
    if index < 0:
        index += len(base)
    if index >= 0 and index < len(base):
        return base[index]
    return None

def get_mod_age_days(path):        # -M
    """Implementation of perl -M"""
    global OS_ERROR, TRACEBACK, AUTODIE
    if not path:
        return None
    if hasattr(path, '_mtime'):
        t = path._mtime
    else:
        try:
            if hasattr(path, 'fileno') and os.stat in os.supports_fd:
                path = path.fileno()
            elif hasattr(path, 'name'):
                path = path.name
            t = os.path.getmtime(path)
        except Exception as _e:
            OS_ERROR = str(_e)
            if TRACEBACK:
                cluck(f"-M {path} failed: {OS_ERROR}",skip=2)
            if AUTODIE:
                raise
            return 0
    return (BASETIME - t) / 86400.0
    

def gmtime(secs=None):
    """Replacement for perl built-in gmtime function"""
    try:
        gmt = tm_py.gmtime(secs)
    except Exception:
        try:
            import datetime
            dt = datetime.datetime.utcfromtimestamp(0) + datetime.timedelta(seconds=secs)
            gmt = dt.timetuple()
        except Exception:
            return (9, 9, 9, 9, 9, 99999, 0, 9, 0)
    return (gmt.tm_sec, gmt.tm_min, gmt.tm_hour, gmt.tm_mday, 
            gmt.tm_mon-1, gmt.tm_year-1900, (gmt.tm_wday+1)%7, 
            gmt.tm_yday-1, 0) 


def handle_open_pragma(mode, encoding, errors, newline="\n"):
    """Handle any "use open" pragma that may be in effect"""
    if encoding is not None:
        return (mode, encoding, errors, newline)
    layers = None
    if ('r' in mode or mode == '-|') and INPUT_LAYERS:
        layers = INPUT_LAYERS
    elif OUTPUT_LAYERS:
        layers = OUTPUT_LAYERS
    else:
        return (mode, encoding, errors, newline)

    layers = layers.replace(':', '')
    if layers == 'raw' or layers == 'bytes':
        if 'b' not in mode:
            mode += 'b'
        newline = None
    elif layers.startswith('encoding('):
        encoding = layers.replace('encoding(','').replace(')','')
        errors = 'replace'
    elif layers == 'utf8':
        encoding = 'UTF-8'
        errors = 'ignore'
    elif layers == 'crlf':
        newline = None

    return (mode, encoding, errors, newline)

def has_setgid(path):        # -g
    if not path:
        return ''   # False
    if hasattr(path, '_mode'):
        return 1 if (path._mode & st_py.S_ISGID) != 0 else ''
    return 1 if (os.stat(path).st_mode & st_py.S_ISGID) != 0 else ''

def has_setuid(path):        # -u
    if not path:
        return ''       # False
    if hasattr(path, '_mode'):
        return 1 if (path._mode & st_py.S_ISUID) != 0 else ''
    return 1 if (os.stat(path).st_mode & st_py.S_ISUID) != 0 else ''

def has_sticky(path):        # -k
    if not path:
        return ''       # False
    if hasattr(path, '_mode'):
        return 1 if (path._mode & st_py.S_ISVTX) != 0 else ''
    return 1 if (os.stat(path).st_mode & st_py.S_ISVTX) != 0 else ''

def import_(globals, path, module=None, fromlist=None, version=None):
    """Handle use/require statement from perl"""
    if module is None:
        [path, module] = os.path.split(os.path.splitext(os.path.abspath(path))[0])
    if module in sys.modules and \
      hasattr((mod:=sys.modules[module]), '__file__') and \
      os.path.join(path, module) + '.py' == mod.__file__:
       pass
    else:
        try:
            sys.path.insert(0, path)
            mod = __import__(module, globals=globals, fromlist=['*'])
            sys.modules[module] = mod
        finally:
            sys.path.pop(0)

    if hasattr(mod, 'VERSION') and version is not None:
        if isinstance(version, str) and version[0] == 'v':
            version = version[1:]
        try:
            version = float(version)
        except Exception:
            version = 0.0
        mod_version = None
        try:
            mod_version = float(mod.VERSION)
        except Exception:
            pass
        if mod_version is not None and version > mod_version:
            raise ValueError(f"For import {module}, desired version {version} > actual version {mod_version} at {path}")

    # globals[module] = mod

    if fromlist is None:
        return 1                 # use X ();

    if not isinstance(fromlist, (list, tuple)):
        fromlist = [fromlist]

    actual_imports = set()
    export = mod.EXPORT if hasattr(mod, 'EXPORT') else ()
    export_ok = mod.EXPORT_OK if hasattr(mod, 'EXPORT_OK') else ()
    export_tags = mod.EXPORT_TAGS if hasattr(mod, 'EXPORT_TAGS') else ()

    if (fromlist[0] == '*' or fromlist[0] == ':all') and hasattr(mod, '__all__'):
        actual_imports = set(mod.__all__)
    #elif fromlist[0] == '*' and not hasattr(mod, 'EXPORT'):
        #for key in mod.__dict__.keys():
            #if key[0] != '_':
                #actual_imports.add(key)
    else:
        for desired in fromlist:
            if (ch:=desired[0]) == '!':
                if desired == fromlist[0]:
                    actual_imports = set(export)
                ch2 = desired[1]
                if ch2 == ':':
                    tag = desired[2:]
                    if tag in export_tags:
                        for e in export_tags[tag][0]:
                            actual_imports.discard(e)
                elif ch2 == '/':
                    pat = re.compile(desired[2:-1])
                    for e in (export + export_ok):
                        if re.search(pat, e):
                            actual_imports.discard(e)
                else:
                    actual_imports.discard(desired[1:])
            elif ch == ':':
                tag = desired[1:]
                if tag in export_tags:
                    for e in export_tags[tag][0]:
                        actual_imports.add(e)
                elif tag == 'DEFAULT':
                    actual_imports.update(set(export))
            elif ch == '/':
                pat = re.compile(desired[1:-1])
                for e in (export + export_ok):
                    if re.search(pat, e):
                        actual_imports.add(e)
            elif desired == '*':
                actual_imports.update(set(export))

        actual_imports = list(actual_imports)
        sig_map = {'$': '_v', '@': '_a', '%': '_h'}
        for i in range(len(actual_imports)):
            perl_name = actual_imports[i]
            sig = perl_name[0]
            if sig == '&':
                perl_name = perl_name[1:]
                if hasattr(mod, perl_name+'_'):
                    actual_imports[i] = perl_name+'_'
            elif sig in ('$', '@', '%'):
                perl_name = perl_name[1:]
                sm = sig_map[sig]
                if hasattr(mod, perl_name+sm):
                    actual_imports[i] = perl_name+sm
                elif hasattr(mod, perl_name+'_'):
                    actual_imports[i] = perl_name+'_'
            elif hasattr(mod, perl_name+'_'):
                actual_imports[i] = perl_name+'_'

    for imp in actual_imports:
        if hasattr(mod, imp):
            globals[imp] = getattr(mod, imp)
            if hasattr(builtins, '__PACKAGE__'):
                pkg = builtins.__PACKAGE__
                if hasattr(builtins, pkg):
                    namespace = builtins[pkg]
                    setattr(namespace, imp, getattr(mod, imp))

    return 1

def init_global(packname, varname, value):
    """Return the proper value to initialize a package global variable only once"""
    namespace = getattr(builtins, packname)
    if hasattr(namespace, varname):
        return getattr(namespace, varname)
    setattr(namespace, varname, value)
    return value

def input_line_number(fh, value=None):
    """Implementation of perl input_line_number"""
    global INPUT_LINE_NUMBER
    if value is None:
        try:
            return fileinput.lineno()
        except RuntimeError:
            return INPUT_LINE_NUMBER
    else:
        prev = input_line_number(fh)
        INPUT_LINE_NUMBER = value
        return prev
    
    

def int_(expr):
    """Convert expr to an integer"""
    if not expr:
        return 0
    if isinstance(expr, int):
        return +expr            # The Unary plus will convert True to 1
    try:
        return int(expr)
    except Exception:
        pass
    if not isinstance(expr, (str, bytes)):
        if isinstance(expr, complex):
            return int_(expr.real)
        return expr
    if (m:=re.match(r'^\s*([+-]?(?:\d+))', expr)):
        return int(m.group(1))
    if WARNING:
        caller = inspect.getframeinfo(inspect.stack()[1][0])
        warnings.warn(f"Argument \"{expr}\" isn't numeric in integer context at {caller.filename}:{caller.lineno}")
    return 0

def ioctl(fh, func, scalar):
    global AUTODIE, TRACEBACK, OS_ERROR
    """Implementation of perl ioctl"""
    try:
        result = fc_py.ioctl(fh, func, scalar)
        if result == 0:
            return "0 but true"
        if result == -1:
            return None
        return result
    except Exception as e:
        OS_ERROR = str(e)
        if TRACEBACK:
            cluck(f"ioctl failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return None

def _create_all_fh_methods(fh):
    """Create all special methods for OO filehandles"""
    methods=dict(autoflush=autoflush,binmode=binmode, close_=close_, eof=eof, 
                 fcntl=fcntl, format_write=format_write, getc=getc,
                 getpos=getpos, ioctl=ioctl, input_line_number=input_line_number, 
                 open=IOFile_open, print_=print_, printf=printf, say=say, setpos=setpos,
                 # READ is handled specially because of the output scalar: read=read, 
                 stat=stat, 
                 # SYSREAD needs to be handled like READ sysread=sysread, 
                 sysseek=sysseek, syswrite=syswrite, 
                 truncate=truncate, ungetc=ungetc, write_=write_,
                 )
    for method, func in methods.items():
        setattr(fh, method, types.MethodType(func, fh))

    fh.getline = fh.readline
    fh.getlines = fh.readlines

    return fh

def IOFile(path=None, mode=None, perms=None):
    """Implementation of IO::File->new()"""
    global TRACEBACK, AUTODIE
    try:
        if path is None:
            fh = io.TextIOWrapper(io.BufferedIOBase())
            fh.close()
            return _create_all_fh_methods(fh)
        if perms is None:
            perms = 0o777
        #fh = os.fdopen(os.open(path, mode, perms))
        fh = IOFile_open(path, mode, perms)
        return _create_all_fh_methods(fh)
    except Exception as e:
        if TRACEBACK:
            if perms is None:
                if mode is None:
                    cluck(f"IO::File->new({path}) failed: {OS_ERROR}",skip=2)
                else:
                    cluck(f"IO::File->new({path}, {mode}) failed: {OS_ERROR}",skip=2)
            else:
                cluck(f"IO::File->new({path}, {mode}, {perms}) failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        fh = io.TextIOWrapper(io.BufferedIOBase())
        fh.close()
        return _create_all_fh_methods(fh)

def IOFile_from_fd(fd, mode):
    """Implementation of IO::File::new_from_fd()"""
    global TRACEBACK, AUTODIE
    try:
        return fdopen(None, fd, mode)
    except Exception as e:
        if TRACEBACK:
            cluck(f"IO::File::new_from_fd({fd}, {mode}) failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return None

def _open_mode_string(mode):
    if not ((_m:=re.search(r'^\+?(<|>>?)$',mode))):
        if not (mode:=re.sub(r'^r(\+?)$',r'\g<1><',mode, count=1)):
            if not (mode:=re.sub(r'^w(\+?)$',r'\g<1>>',mode, count=1)):
                if not (mode:=re.sub(r'^a(\+?)$',r'\g<1>>>',mode, count=1)):
                    croak(f"IO::Handle: bad open mode: {mode}")
    return mode

def IOFile_open(fh, filename, mode=None, perms=None):
    """Implementation of perl $fh->open method"""
    if mode is not None:
        if isinstance(mode, str) and re.match(r'^\d+$', mode):
            mode = int(mode)
            if perms is None:
                perms = 0o666
            result = os.fdopen(os.open(filename, mode, perms))
        elif ':' in mode:
            result = open_dynamic(filename, mode, checked=False)
        else:
            result = open_dynamic(filename, _open_mode_string(mode), checked=False)
    else:
        encoding = errors = None
        if hasattr(fh, 'encoding'):
            encoding = fh.encoding
            errors = fh.errors
        result = open_dynamic(filename,encoding=encoding,errors=errors)
    if not fh.closed:
        fh.close()
    return _create_all_fh_methods(result)

def IOFile_tmpfile():
    """Implementation of IO::File->new_tmpfile"""
    fh = tempfile.NamedTemporaryFile()
    return _create_all_fh_methods(fh)

def isa(self, classname):
    """Implementation of UNIVERSAL::isa and $obj->isa"""
    _ref_map = {"<class 'int'>": 'SCALAR', "<class 'str'>": 'SCALAR',
                "<class 'float'>": 'SCALAR', "<class 'NoneType'>": 'SCALAR',
                "<class 'list'>": 'ARRAY', "<class 'tuple'>": 'ARRAY',
                "<class 'dict'>": 'HASH'}
    t = str(type(self))
    if t in _ref_map:
        return 1 if _ref_map[t] == classname else ''
    elif '_ArrayHash' in t:
        if r.isHash:
            return 1 if 'HASH' == classname else ''
        return 1 if 'ARRAY' == classname else ''
    elif classname == 'IO::Handle':
        return 1 if isinstance(self, io.IOBase) else ''
    classname = classname.replace("'", '.').replace('::', '.')
    if hasattr(builtins, classname):
        the_class = getattr(builtins, classname)
        return 1 if isinstance(self, the_class) else ''
    return ''       # False

def is_block_special(path):        # -b
    if not path:
        return ''       # False
    if hasattr(path, '_mode'):
        return 1 if st_py.S_ISBLK(path._mode) else ''
    if hasattr(path, 'fileno') and os.stat in os.supports_fd:
        path = path.fileno()
    elif hasattr(path, 'name'):
        path = path.name
    return 1 if st_py.S_ISBLK(os.stat(path).st_mode) else ''

def is_char_special(path):        # -c
    if not path:
        return ''       # False
    if hasattr(path, '_mode'):
        return 1 if st_py.S_ISCHR(path._mode) else ''
    if hasattr(path, 'fileno') and os.stat in os.supports_fd:
        path = path.fileno()
    elif hasattr(path, 'name'):
        path = path.name
    return 1 if st_py.S_ISCHR(os.stat(path).st_mode) else ''

def is_dir(path):        # -d
    if not path:
        return ''       # False
    if hasattr(path, '_mode'):
        return 1 if st_py.S_ISDIR(path._mode) else ''
    if hasattr(path, 'fileno') and os.stat in os.supports_fd:
        path = path.fileno()
    elif hasattr(path, 'name'):
        path = path.name
    return 1 if os.path.isdir(path) else ''

def is_empty_file(path):        # -z
    if not path:
        return None
    if hasattr(path, '_size'):
        return 1 if path._size == 0 else ''
    if hasattr(path, 'fileno') and os.stat in os.supports_fd:
        path = path.fileno()
    elif hasattr(path, 'name'):
        path = path.name
    return 1 if not os.path.getsize(path) else ''

def is_executable(path):       # -x
    if not path:
        return ''       # False
    if hasattr(path, 'cando'):
        return 1 if path.cando(st_py.S_IXUSR, 1) else ''
    if hasattr(path, 'fileno') and os.access in os.supports_fd:
        path = path.fileno()
    elif hasattr(path, 'name'):
        path = path.name
    return 1 if os.access(path, os.X_OK, effective_ids=(os.access in os.supports_effective_ids)) else ''

def is_file(path):        # -f
    if not path:
        return ''       # False
    if hasattr(path, '_mode'):
        return 1 if st_py.S_ISREG(path._mode) else ''
    if hasattr(path, 'fileno') and os.stat in os.supports_fd:
        path = path.fileno()
    elif hasattr(path, 'name'):
        path = path.name
    return 1 if os.path.isfile(path) else ''

def is_link(path):        # -l
    if not path:
        return ''       # False
    if hasattr(path, '_mode'):
        return 1 if st_py.S_ISLNK(path._mode) else ''
    if hasattr(path, 'fileno') and os.lstat in os.supports_fd:
        path = path.fileno()
    elif hasattr(path, 'name'):
        path = path.name
    return 1 if os.path.islink(path) else ''

def is_owned(path):        # -o
    if not path:
        return ''       # False
    if hasattr(path, '_uid'):
        return 1 if path._uid == os.geteuid() else ''
    if hasattr(path, 'fileno') and os.stat in os.supports_fd:
        path = path.fileno()
    elif hasattr(path, 'name'):
        path = path.name
    return 1 if os.stat(path).st_uid == os.geteuid() else ''

def is_pipe(path):        # -p
    if not path:
        return ''       # False
    if hasattr(path, '_mode'):
        return 1 if st_py.S_ISFIFO(path._mode) else ''
    if hasattr(path, 'fileno') and os.stat in os.supports_fd:
        path = path.fileno()
    elif hasattr(path, 'name'):
        path = path.name
    return 1 if st_py.S_ISFIFO(os.stat(path).st_mode) else ''

def is_readable(path):     # -r
    if not path:
        return ''       # False
    if hasattr(path, 'cando'):
        return 1 if path.cando(st_py.S_IRUSR, 1) else ''
    if hasattr(path, 'fileno') and os.access in os.supports_fd:
        path = path.fileno()
    elif hasattr(path, 'name'):
        path = path.name
    return 1 if os.access(path, os.R_OK, effective_ids=(os.access in os.supports_effective_ids)) else ''

def is_real_executable(path):      # -X
    if not path:
        return ''       # False
    if hasattr(path, 'cando'):
        return 1 if path.cando(st_py.S_IXUSR, 0) else ''
    if hasattr(path, 'fileno') and os.access in os.supports_fd:
        path = path.fileno()
    elif hasattr(path, 'name'):
        path = path.name
    return 1 if os.access(path, os.X_OK) else ''

def is_real_owned(path):   # -O
    if not path:
        return ''       # False
    if hasattr(path, '_uid'):
        return 1 if path._uid == os.getuid() else ''
    if hasattr(path, 'fileno') and os.stat in os.supports_fd:
        path = path.fileno()
    elif hasattr(path, 'name'):
        path = path.name
    return 1 if os.stat(path).st_uid == os.getuid() else ''

def is_real_readable(path):        # -R
    if not path:
        return ''       # False
    if hasattr(path, 'cando'):
        return 1 if path.cando(st_py.S_IRUSR, 0) else ''
    if hasattr(path, 'fileno') and os.access in os.supports_fd:
        path = path.fileno()
    elif hasattr(path, 'name'):
        path = path.name
    return 1 if os.access(path, os.R_OK) else ''

def is_real_writable(path):
    if hasattr(path, 'cando'):
        return 1 if path.cando(st_py.S_IRUSR, 0) else ''
    if hasattr(path, 'fileno') and os.access in os.supports_fd:
        path = path.fileno()
    elif hasattr(path, 'name'):
        path = path.name
    return 1 if os.access(path, os.W_OK) else ''

def is_socket(path):        # -S
    if not path:
        return ''       # False
    if hasattr(path, '_mode'):
        return 1 if st_py.S_ISSOCK(path._mode) else ''
    if hasattr(path, 'fileno') and os.stat in os.supports_fd:
        path = path.fileno()
    elif hasattr(path, 'name'):
        path = path.name
    return 1 if st_py.S_ISSOCK(os.stat(path).st_mode) else ''

def is_tty(path):        # -t
    if not path:
        return ''       # False
    if hasattr(path, 'isatty'):
        return 1 if path.isatty() else ''
    if isinstance(path, tuple):
        raise ValueError('-t not supported on File_stat')
    if hasattr(path, 'name'):
        path = path.name
    try:
        with open(path, 'r') as t:
            return 1 if t.isatty() else ''
    except Exception:
        return ''       # False

def is_writable(path):     # -w
    if not path:
        return ''       # False
    if hasattr(path, 'cando'):
        return 1 if path.cando(st_py.S_IWUSR, 1) else ''
    if hasattr(path, 'fileno') and os.access in os.supports_fd:
        path = path.fileno()
    elif hasattr(path, 'name'):
        path = path.name
    return 1 if os.access(path, os.W_OK, effective_ids=(os.access in os.supports_effective_ids)) else ''

def kill(sig, *args):
    """Implementation of perl kill function"""
    global AUTODIE, TRACEBACK, OS_ERROR
    if isinstance(sig, str):
        neg = 1
        if sig.startswith('-'):
            neg = -1
        if not sig.startswith('SIG'):
            sig = f"SIG{sig}"
        if not sig in signal.Signals:
            carp(f'Unrecognized signal name "{sig}"')
            return 0
        sig = signal.Signals[sig] * neg
    result = 0
    for pid in args:
        try:
            os.kill(pid, sig)
            result += 1
        except Exception as _e:
            OS_ERROR = str(_e)
            if TRACEBACK:
                cluck(f"kill({sig}, {pid}) failed: {OS_ERROR}", skip=2)
            if AUTODIE:
                raise
    return result


def lcfirst(string):
    """Implementation of lcfirst and \l in interpolated strings: lowercase the first char of the given string"""
    return string[0:1].lower() + string[1:]

def list_of_n(lst, n):
    """For assignment to (list, ...) - make this list the right size"""
    if lst is None or (hasattr(lst, 'isHash') and lst.isHash) or not (isinstance(lst, collections.abc.Sequence) and not isinstance(lst, str)):
        lst = [lst]
    la = len(lst)
    if la == n:
        return lst
    if la > n:
        return lst[:n]
    return list(lst) + [None for _ in range(n-la)]


def list_to_hash(lst):
    """Convert a flat list of key value pairs to a hash"""
    return {lst[i]: lst[i+1] for i in range(0, len(lst), 2)};

def localtime(secs=None):
    """Replacement for perl built-in localtime function"""
    try:
        lct = tm_py.localtime(secs)
    except Exception:
        try:
            import datetime
            dt = datetime.datetime.fromtimestamp(0) + datetime.timedelta(seconds=secs)
            lct = dt.timetuple()
        except Exception:
            return (9, 9, 9, 9, 9, 99999, 0, 9, 0)
    return (lct.tm_sec, lct.tm_min, lct.tm_hour, lct.tm_mday, 
            lct.tm_mon-1, lct.tm_year-1900, (lct.tm_wday+1)%7, 
            lct.tm_yday-1, lct.tm_isdst) 


def longmess(*args, skip=0):
    """Message with stack backtrace"""
    def ff(fn):
        fn = os.path.relpath(fn)
        if fn.startswith('./'):
            return fn[2:]
        return fn
    def fa(a):
       result = re.sub(r'^\(\*_args=(.*)\)$', r'\1',a).replace(',)', ')')
       if result == '[]':
           return '()'
       return result
    stack = inspect.stack()
    stack = stack[skip:]
    m = ''.join(map(str, args))
    m += ' at ' + ff(stack[1].filename) + ' line ' + str(stack[1].lineno) + ".\n"
    for i in range(1, len(stack)-1):
       s = stack[i]
       s2 = stack[i+1]
       m += '        ' + s.function+fa(inspect.formatargvalues(*inspect.getargvalues(s.frame))) + ' called at ' + ff(s2.filename) + ' line ' + str(s2.lineno) + "\n"
    return m

def looks_like_binary(path):        # -B
    """Implementation of perl -B"""
    if isinstance(path, tuple):
        return ValueError('-B not supported on File_stat')
    return 1 if not looks_like_text(path) else ''

def looks_like_text(path):        # -T
    """Implementation of perl -T"""
    global TRACE_RUN
    if not isinstance(path, str):
        return ValueError('-T is only supported on paths')
    rtn = subprocess.run(f'file "{path}"',capture_output=True,text=True,shell=(os.name!='nt'))
    if TRACE_RUN:
        carp(f'trace -T {path}: {repr(rtn)}', skip=2)
    if rtn.returncode:
        return None
    rtn = rtn.stdout
    return 1 if 'text' in rtn else ''

def lstat(path):
    """Handle lstat call with or without "use File::stat;" """
    if isinstance(path, File_stat):
        return path     # for '_' special variable
    try:
        if hasattr(path, 'fileno') and os.lstat in os.supports_fd:
            path = path.fileno()
        elif hasattr(path, 'name'):
            path = path.name
        s = os.lstat(path)
    except Exception:
        return ()

    result = File_stat(_dev=s.st_dev, _ino=s.st_ino, _mode=s.st_mode,
            _nlink=s.st_nlink, _uid=s.st_uid, _gid=s.st_gid, 
            _rdev=s.st_rdev if hasattr(s, 'st_rdev') else 0,
            _size=s.st_size, _atime=s.st_atime, _mtime=s.st_mtime, _ctime=s.st_ctime,
            _blksize=s.st_blksize if hasattr(s, 'st_blksize') else 512,
            _blocks=s.st_blocks if hasattr(s, 'st_blocks') else s.st_size // 512)
    return result

def maketrans_c(arg1, arg2, delete=False):
    """Make a complement tr table for the 'c' flag.  If the 'd' flag is passed, then delete=True.  Ranges are expanded in arg1 and arg2 but arg2 is not otherwise normalized"""
    t = str.maketrans(arg1, arg1)
    d = dict()
    for i in range(257):
        if i not in t:
            if not arg2:
                if delete:
                    d[i] = None
                else:
                    d[i] = i
            elif i < len(arg2):
                d[i] = arg2[i]
            elif delete:
                d[i] = None
            else:
                d[i] = arg2[-1]

    return str.maketrans(d)


def make_list(*args):
    """For push/unshift @arr, expr;  We use extend/[0:0] so make sure expr is iterable"""
    if len(args) == 1 and isinstance(args[0], collections.abc.Iterable) and not isinstance(args[0], str) and (
            not hasattr(args[0], 'isHash') or not args[0].isHash):
        return args[0]
    return args


def map_int(*args):
    """Convert each element to an int"""
    return list(map(int_, flatten(args)))

def map_num(*args):
    """Convert each element to a num"""
    return list(map(num, flatten(args)))

def map_str(*args):
    """Convert each element to a str"""
    return list(map(_str, flatten(args)))

def mkdir(path, mode=0o777):
    global TRACEBACK, AUTODIE, OS_ERROR
    """Implementation of perl mkdir function"""
    try:
        os.mkdir(path, mode)
        return 1
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            if mode == 0o777:
                cluck(f"mkdir({path}) failed: {OS_ERROR}",skip=2)
            else:
                cluck(f"mkdir({path}, 0o{mode:o}) failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return ''

def mkdtemp(template):
    """Implementation of File::Temp::mkdtemp()"""
    template = template.replace('X', '')
    (base, dirn, tail) = fileparse(template)
    return tempfile.mkdtemp(prefix=base, dir=dirn)

def mkstemp(template):
    """Implementation of File::Temp::mkstemp()"""
    template = template.replace('X', '')
    (base, dirn, tail) = fileparse(template)
    fh = tempfile.NamedTemporaryFile(prefix=base, dir=dirn, delete=False)
    return (fh, fh.name)

def mkstemps(template, suffix):
    """Implementation of File::Temp::mkstemps()"""
    template = template.replace('X', '')
    (base, dirn, tail) = fileparse(template)
    fh = tempfile.NamedTemporaryFile(prefix=base, dir=dirn, suffix=suffix, delete=False)
    return(fh, fh.name)

def mktemp(template):
    """Implementation of File::Temp::mktemp()"""
    template = template.replace('X', '')
    (base, dirn, tail) = fileparse(template)
    ntf = tempfile.NamedTemporaryFile(prefix=base, dir=dirn, delete=False)
    result = ntf.name
    ntf.close()
    return result

def mod_element(base, index, value):
    base[index] %= value
    return base[index]

def multiply_element(base, index, value):
    base[index] *= value
    return base[index]

def need_sh(cmd):
    """Does this command need a shell to run it?"""
    if os.name == 'nt':     # windows
        if isinstance(cmd, (tuple, list)):
            for e in cmd:
                if need_sh(e):
                    return True
            return False
        if re.search(r'[<>|&*]', cmd) or re.match(r'(?:copy|echo|dir|type|cd) ', cmd):
            return True
        return False
    return True

def nr():
    """Get the current INPUT_LINE_NUMBER"""
    global INPUT_LINE_NUMBER
    try:
        return fileinput.lineno()
    except RuntimeError:
        return INPUT_LINE_NUMBER

def num(expr):
    """Convert expr to a number
       Ref: https://squareperl.com/en/how-perl-convert-string-to-number"""
    if expr is None:
        return 0
    try:
        return +expr    # Unary plus: The fastest way to test for numeric
    except Exception:
        pass
    #if isinstance(expr, (int, float)):
        #return expr
    try:
        return int(expr)
    except Exception:
        pass
    for _ in range(2):
        try:
            f = float(expr)
            if f.is_integer():
                return int(f)
            return f
        except Exception:
            pass
        if isinstance(expr, str):
            if not (m:=re.match(r'^\s*([+-]?(?:\d+(?:[.]\d*)?(?:[eE][+-]?\d+)?|[.]\d+(?:[eE][+-]?\d+)?))', expr)):
                break
            expr = m.group(1);
        elif isinstance(expr, bytes):
            if not (m:=re.match(br'^\s*([+-]?(?:\d+(?:[.]\d*)?(?:[eE][+-]?\d+)?|[.]\d+(?:[eE][+-]?\d+)?))', expr)):
                break
            expr = m.group(1);
        elif hasattr(expr, 'isHash') and expr.isHash is None:
            return 0
        else:
            return expr
    if WARNING:
        caller = inspect.getframeinfo(inspect.stack()[1][0])
        warnings.warn(f"Argument \"{expr}\" isn't numeric in numeric context at {caller.filename}:{caller.lineno}")
    return 0

def _create_fh_methods(fh):
    """Create special methods for filehandles"""
    try:
        fh.autoflush = types.MethodType(autoflush, fh)
    except NameError:  # _autoflush is only brought in if we reference it
        pass
    return fh

def open_(file,mode,encoding=None,errors=None,checked=True,newline="\n"):
    """Replacement for perl built-in open function when the mode is known."""
    global OS_ERROR, TRACEBACK, AUTODIE
    try:
        (mode, encoding, errors, newline) = handle_open_pragma(mode, encoding, errors, newline)
    except NameError:
        pass
    if 'b' not in mode and encoding is None:
        encoding = 'latin1'
        if errors is None:
            errors = 'ignore'
    file = str(file)                # handle numeric filename
    try:
        if mode == '|-' or mode == '|-b':    # pipe to
            text = True if mode == '|-' else False
            sp = subprocess.Popen(file, stdin=subprocess.PIPE, shell=need_sh(file), text=text, encoding=encoding, errors=errors)
            if sp.returncode:
                raise Die(f"open(|{file}): failed with {sp.returncode}")
            sp.stdin._sp = sp           # issue 72
            sp.stdin._file = f"|{file}" # issue 72
            return sp.stdin
        elif mode == '-|' or mode == '-|b':  # pipe from
            text = True if mode == '-|' else False
            sp = subprocess.Popen(file, stdout=subprocess.PIPE, shell=need_sh(file), text=text, encoding=encoding, errors=errors)
            if sp.returncode:
                raise Die(f"open({file}|): failed with {sp.returncode}")
            sp.stdout._sp = sp          # issue 72
            sp.stdout._file = f"|{file}" # issue 72
            return sp.stdout
        if file is None:
            return tempfile.TemporaryFile(mode=mode, encoding=encoding)
        if os.name == 'nt' and file.startswith('/tmp/'):
            file = tempfile.gettempdir() + file[4:]
        if 'b' in mode:
            newline = None
        file = file.rstrip("\n\r")
        return _create_fh_methods(open(file,mode,encoding=encoding,errors=errors,newline=newline))
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(f"open({file}, {mode}) failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        if checked:     # e.g. used in if(...)
            return None
        fh = io.TextIOWrapper(io.BufferedIOBase())
        fh.close()
        return _create_fh_methods(fh)

def opendir(DIR):
    """Implementation of perl opendir"""
    global OS_ERROR, TRACEBACK, AUTODIE
    class DirHandle(list):
        pass
    try:
        result = DirHandle([list(os.listdir(DIR)), 0])
        result.name = DIR   # for stat and friends
        return result
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(f"opendir({DIR}) failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return None

def open_dynamic(file,mode=None,encoding=None,errors=None,checked=True):
    """Replacement for perl built-in open function when the mode is unknown."""
    dup_it = None
    pipe = None
    if mode is None:
        m = re.match(r'^\s*([<>+|-]*)([&]?=?)\s*(.*?)\s*([|]?)\s*$', file)
        mode = m.group(1)
        dup_it = m.group(2)
        file = m.group(3)
        pipe = m.group(4)
    elif '&' in mode:           # dup
        dup_it = '&'
        mode = mode.replace('&', '')
        if '=' in mode:
            dup_it = '&='
            mode = mode.replace('=', '')

    if mode == '<-' or mode == '-' or mode == '-<':
        return sys.stdin
    if mode == '>-' or mode == '->':
        return sys.stdout
    ext = None
    if ':' in mode:
        mode, ext = mode.split(':')
    if mode in _OPEN_MODE_MAP:
        mode = _OPEN_MODE_MAP[mode]
        if ext:
            if ext == 'raw' or ext == 'bytes':
                mode += 'b'
            elif ext.startswith('encoding('):
                encoding = ext.replace('encoding(','').replace(')','')
                errors = 'replace'
            elif ext == 'utf8':
                encoding = 'UTF-8'
                errors = 'ignore'
        if dup_it:
            if '=' in dup_it:
                return dup(file, mode,encoding=encoding,errors=errors,checked=checked,equals=True)
            return dup(file, mode,encoding=encoding,errors=errors,checked=checked)
        return open_(file, mode,encoding=encoding,errors=errors,checked=checked)
    if pipe:
        return open_(file, '-|',encoding=encoding,errors=errors,checked=checked)
    return open_(file, 'r',encoding=encoding,errors=errors,checked=checked)


def or_element(base, index, value):
    base[index] |= value
    return base[index]

def os_name():
    """Implementation of $OSNAME / $^O"""
    result = sys.platform
    return 'MSWin32' if result == 'win32' else result

_PACK_TO_STRUCT = dict(a='s', c='b', C='B', s='h', S='H', l='l', L='L', q='q', Q='Q',
                       i='i', I='I', n='!H', N='!L', v='<H', V='<L', j='i', J='I', f='f',
                       d='d', F='d', x='x')
_TEMPLATE_LENGTH = dict(a=1, c=1, C=1, s=2, S=2, l=4, L=4, q=8, Q=8, i=4, I=4, n=2, N=4, v=2, V=4, j=4, J=4, f=4, d=8, F=8, x=1)
# Create simple bytes <-> str identity conversions
_decoding_map = codecs.make_identity_dict(range(256))
_encoding_map = codecs.make_encoding_map(_decoding_map)
def _str_to_bytes(by):
    return codecs.charmap_encode(by, 'ignore', _encoding_map)[0]
def _bytes_to_str(by):
    return codecs.charmap_decode(by, 'ignore', _decoding_map)[0]

def _get_pack_unpack_format_and_counts(template, args, is_unpack=False):
    # FIXME: Handle more cases using a custom translator.
    format_and_counts = []
    prefix = ''
    format = ''
    i = 0
    ndx = 0
    typ = 'unpack' if is_unpack else 'pack'
    len_so_far = 0
    prev = 0
    while i < len(template):
        if template[i].isspace():
            i += 1
            continue
        if template[i] in _PACK_TO_STRUCT:
            fmt = _PACK_TO_STRUCT[template[i]]
        else:
            raise Die(f'{typ} format {template[i]} is not currently supported')

        mod = ''
        cnt = 1
        if (_m:=re.match(r'^([!<>]?)((?:(?:\[?(?:(?:\d+)|[*]))\]?)|(?:\[[A-Za-z]\]))?', template[i+1:])):
            i += _m.end()
            mod = _m.group(1)
            if mod is None:
                mod = ''
            cnt = _m.group(2)
            if not cnt:
                cnt = '1'
            elif cnt[0] == '[':
                cnt = cnt[1:-1]
            if cnt.isdigit():
                cnt = int(cnt)
            elif cnt in _TEMPLATE_LENGTH:
                cnt = _TEMPLATE_LENGTH[cnt]
            else:
                raise Die(f'{typ} cannot get length of {cnt} template')
            if mod == '!':
                if len(fmt) != 1:
                    fmt = fmt.lower()
                    mod = fmt[0]
                    fmt = fmt[1]
                else:
                    mod = '@'   # Native
            elif len(fmt) != 1:
                mod = fmt[0]
                fmt = fmt[1]

            if cnt == '*':
                if is_unpack and hasattr(args[0], '__len__'):
                    cnt = len(args[0]) - len_so_far - (struct.calcsize(format) if format else 0)
                if ndx < len(args) and hasattr(args[ndx], '__len__'):
                    cnt = len(args[ndx])
                else:
                    cnt = 1
            fmt = f"{cnt}{fmt}"
        elif len(fmt) != 1:
            mod = fmt[0]
            fmt = fmt[1]


        fmt_code = fmt[-1]
        if mod == prefix:
            format += fmt
            mod = ''
            fmt = ''
        elif mod and not prefix:
            prefix = mod
            format += fmt
            mod = ''
            fmt = ''
        else:
            format = f'{prefix}{format}'
            len_so_far += struct.calcsize(format)
            format_and_counts.append((format, prev, ndx))
            prefix = ''
            format = ''
            prev = ndx

        if fmt_code == 's':
            if not is_unpack and isinstance(args[ndx], str):
                args[ndx] = _str_to_bytes(args[ndx])
            ndx += 1
        else:
            ndx += cnt

        i += 1

    format = prefix + mod + format + fmt
    if format:
        format_and_counts.append((format, prev, ndx))

    return format_and_counts

def pack(template, *args):
    """pack items into a str via a given format template"""
    # Look here to handle many cases: https://docs.python.org/3/library/struct.html
    args = list(args)
    result = ''
    format_and_counts = _get_pack_unpack_format_and_counts(template, args)

    for format, start, end in format_and_counts:
        result += _bytes_to_str(struct.pack(format, *args[start:end]))

    return result

def package_call(package, function, *args, **kwargs):
    """Call a function in a different package"""
    cur_package = builtins.__PACKAGE__
    try:
        builtins.__PACKAGE__ = package.__PACKAGE__
        return function(*args, **kwargs)
    finally:
        builtins.__PACKAGE = cur_package

def perl_print(*args, **kwargs):
    """Replacement for perl built-in print/say/warn functions.
    Note that by default this acts like 'say' in that it appends a newline.
    To prevent the newline, pass the end='' keyword argument.  To write
    to a different file, pass the file=... keyword argument.  To flush the output
    after writing, pass flush=True.  To replace the OUTPUT_FIELD_SEPARATOR, pass sep='...'.
    It returns 1 if successful"""
    global OS_ERROR, TRACEBACK, AUTODIE
    try:
        file = sys.stdout
        if 'file' in kwargs:
            file = kwargs['file']
            if file is None:
                raise Die('print() on unopened filehandle')
        if 'sep' not in kwargs:
            kwargs['sep'] = OUTPUT_FIELD_SEPARATOR
        if 'end' in kwargs:
            kwargs['end'] += OUTPUT_RECORD_SEPARATOR
        else:
            kwargs['end'] = "\n" + OUTPUT_RECORD_SEPARATOR
        if 'flush' not in kwargs and hasattr(file, '_autoflush'):
            kwargs['flush'] = file._autoflush
        print(*args, **kwargs)
        return 1        # True
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(f"print failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return ''       # False


def postprocess_arguments(parser, parser_rem):
    """After argument parsing, see if we have any leftover arguments and
    flag those as errors"""
    errors = ''
    for arg in parser_rem:
        if len(arg) != 0 and arg[0] == '-':
            errors += f"Unknown option: {re.sub(r'^-*', '', arg)}\n"

    if errors:
        if parser.exit_on_error:
            print(errors, file=sys.stderr, end='')
            sys.exit(1)
        raise argparse.ArgumentError(None, errors)


init_package('Getopt.Long')

def preprocess_arguments():
    """Pre-process the command line arguments, changing -option to --option"""
    for i in range(1, len(sys.argv)):
        if len(sys.argv[i]) > 2 and sys.argv[i][0] == '-' and sys.argv[i][1] != '-':
            sys.argv[i] = '-' + sys.argv[i]

def print_(fh, *args):
    """Implementation of perl $fh->print method"""
    global OS_ERROR, TRACEBACK, AUTODIE
    try:
        print(*args, end='', file=fh)
        return 1        # True
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(f"print failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return ''       # False


def printf(fh, fmt, *args):
    """Implementation of perl $fh->printf method"""
    global OS_ERROR, TRACEBACK, AUTODIE
    try:
        print(format_(fmt, *args), end='', file=fh)
        return 1        # True
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            if isinstance(fmt, str):
                fmt = fmt.replace("\n", '\\n')
            cluck(f"printf({fmt},...) failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return ''       # False


def quotemeta(string):
    """Implementation of perl quotemeta - all chars not matching /[A-Za-z_0-9]/ will be preceded by a backslash"""
    return re.sub(r'([^A-Za-z_0-9])', r'\\\g<1>', string, count=0)

def raise_(exception):
    """To raise an exception in a lambda function or expression"""
    raise exception

def rand(expr=0):
    """Implementation of perl rand function"""
    if expr == 0:
        expr = 1
    return random.random() * expr


def range_(var, pat1, flags1, pat2, flags2, key):
    """The line-range operator.  See https://perldoc.perl.org/perlop#Range-Operators"""
    if not hasattr(range_, key):
        setattr(range_, key, 0)
    seq = getattr(range_, key)
    if isinstance(seq, str):        # e.g. nnE0
        setattr(range_, key, 0)
        return ''       # False

    if seq == 0:                    # Waiting for left to become True
        if isinstance(pat1, str):
            val = re.search(pat1, var, flags=flags1)
        else:
            val = bool(pat1)
        if not val:
            return ''       # False

    seq += 1                        # once left becomes True, then the seq starts counting, and we check right
    setattr(range_, key, seq)
    if isinstance(pat2, str):
        val = re.search(pat2, var, flags=flags2)
    else:
        val = bool(pat2)
    if val:
        seq = str(seq)+'E0'         # end marker
        setattr(range_, key, seq)
    return seq

def read(fh, var, length, offset=0, need_len=False):
    """Read length bytes from the fh, and return the result to store in var
       if need_len is False, else return a tuple with the result
       and the length read"""
    global OS_ERROR, TRACEBACK, AUTODIE
    if var is None:
        var = ''
    try:
        s = fh.read(length)
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(f"read of {length} byte(s) failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        if need_len:
            return (var, None)
        return var

    ls = len(s)
    lv = len(var)
    if offset < 0:
        offset += lv
    if offset:
        if isinstance(s, str):
            if isinstance(var, bytes):
                var = var.decode()
            if need_len:
                return (var[:offset] + ('\0' * (offset-lv)) + s, ls)
            else:
                return var[:offset] + ('\0' * (offset-lv)) + s
        else:
            if isinstance(var, str):
                var = var.encode()
            if need_len:
                return (var[:offset] + (b'\0' * (offset-lv)) + s, ls)
            else:
                return var[:offset] + (b'\0' * (offset-lv)) + s
    if need_len:
        return (s, ls)
    return s

def readdir(DIR):
    """Implementation of perl readdir in scalar context"""
    try:
        result = (DIR[0])[DIR[1]]
        DIR[1] += 1
        return result
    except IndexError:
        return None

def readdirs(DIR):
    """Implementation of perl readdir in list context"""
    result = (DIR[0])[DIR[1]:]
    DIR[1] = len(DIR[0])
    return result

def readline(fh):
    """Reads a line from a file.
    (instead use _readline_full if you need support for perl $/ or $.)"""
    result = fh.readline()
    if not result:
        return None
    return result


def readline_full(fh):
    """Reads a line from a file, handles perl $/ and sets $. """
    global INPUT_RECORD_SEPARATOR, INPUT_LINE_NUMBER
    if INPUT_RECORD_SEPARATOR == "\n":
        result = fh.readline()
        if not result:
            return None
    elif INPUT_RECORD_SEPARATOR is None:
        result = fh.read()
    else:
        if not hasattr(fh, '_data'):
            fh._data = fh.read()
            fh._pos = 0
        irs = INPUT_RECORD_SEPARATOR
        if irs == '':       # paragraph mode
            pos = fh._pos
            while(fh._data[pos] == "\n"):
                pos += 1
            fh._pos = pos
            irs = "\n\n"
        pos = fh._pos
        ndx = fh._data.index(irs, pos)
        if ndx < 0:
            fh._pos = len(fh._data)
        fh._pos = ndx + len(irs)
        result = fh._data[pos:fh._pos]

    if not result:
        if hasattr(fh, '_data'):
            del fh._data
        if hasattr(fh, '_at_eof') and fh._at_eof:
            return None
        else:
            fh._at_eof = True
    else:
        fh._at_eof = False
    if not hasattr(fh, '_lno'):
        INPUT_LINE_NUMBER = fh._lno = 1
    else:
        fh._lno += 1
        INPUT_LINE_NUMBER = fh._lno
    return result


def readlink(path):
    """Returns the value of a symbolic link.  If there is a system error, returns the undefined value and sets OS_ERROR (errno)."""
    global OS_ERROR, TRACEBACK, AUTODIE
    try:
        return os.readlink(path)
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(f"readlink({path}) failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return None


def ref(r):
    """ref function in perl - called when NOT followed by a backslash"""
    _ref_map = {"<class 'int'>": 'SCALAR', "<class 'str'>": 'SCALAR',
                "<class 'float'>": 'SCALAR', "<class 'NoneType'>": 'SCALAR',
                "<class 'list'>": 'ARRAY', "<class 'tuple'>": 'ARRAY',
                "<class 'dict'>": 'HASH'}
    tr = type(r)
    t = str(tr)
    if t in _ref_map:
        return ''
    elif '_ArrayHash' in t:
        return ''
    if hasattr(tr, '__name__'):
        return tr.__name__
    return t.replace("<class '", '').replace("'>", '')

def refs(r):
    """ref function in perl - called when followed by a backslash"""
    _ref_map = {"<class 'int'>": 'SCALAR', "<class 'str'>": 'SCALAR',
                "<class 'float'>": 'SCALAR', "<class 'NoneType'>": 'SCALAR',
                "<class 'list'>": 'ARRAY', "<class 'tuple'>": 'ARRAY',
                "<class 'dict'>": 'HASH'}
    t = str(type(r))
    if t in _ref_map:
        return _ref_map[t]
    elif '_ArrayHash' in t:
        if r.isHash:
            return 'HASH'
        return 'ARRAY'
    elif hasattr(r, 'TIEARRAY'):
        return 'ARRAY'
    elif hasattr(r, 'TIEHASH'):
        return 'HASH'
    return ''

def reverse_scalar(expr):
    """reverse function implementation in scalar context"""
    if expr is None:
        return ''
    if hasattr(expr, 'isHash'):
        if expr.isHash:
            expr = [_item for _k in expr for _item in (_k, expr[_k])]
        else:
            return ''.join(expr)[::-1]
    elif isinstance(expr, collections.abc.Mapping):  # flatten hash (dict)
        expr = [_item for _k in expr for _item in (_k, expr[_k])]
    if isinstance(expr, collections.abc.Iterable) and not isinstance(expr, str):
        return ''.join(expr)[::-1]
    return expr[::-1]


def rewinddir(DIR):
    DIR[1] = 0

def rmdir(d):
    """Implementation of perl rmdir"""
    global AUTODIE, TRACEBACK, OS_ERROR
    try:
        os.rmdir(d)
        return 1
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(OS_ERROR,skip=2)
        if AUTODIE:
            raise
        return ''



def run(*args):
    """Execute a command and return the stdout in list context"""
    global CHILD_ERROR, AUTODIE, TRACEBACK, INPUT_RECORD_SEPARATOR, TRACE_RUN
    if len(args) == 1:
        args = args[0]
    try:
        sp = subprocess.run(args,stdin=sys.stdin,capture_output=True,text=True,shell=need_sh(args))
    except FileNotFoundError:   # can happen on windows if shell=False
        sp = subprocess.CompletedProcess(args, 127)
    except OSError:             # check if we're trying to run a perl or python script on Windows
        if isinstance(args, str):
            args = [args]
        arg_split = args[0].split()[0]
        if arg_split.endswith('.py'):
            args = [sys.executable] + args
        elif arg_split.endswith('.pl'):
            args = ['perl'] + args
        else:
            raise
        sp = subprocess.run(args,stdin=sys.stdin,capture_output=True,text=True,shell=need_sh(args))
    if TRACE_RUN:
        carp(f'trace run({args}): {repr(sp)}', skip=2)
    CHILD_ERROR = sp.returncode
    if CHILD_ERROR:
        if AUTODIE:
            raise Die(f'run({args}): failed with rc {CHILD_ERROR}')
        if TRACEBACK:
            cluck(f'run({args}): failed with rc {CHILD_ERROR}',skip=2)
    if INPUT_RECORD_SEPARATOR is None:
        return sp.stdout
    irs = INPUT_RECORD_SEPARATOR
    pos = 0
    if irs == '':   # paragraph mode
        while(sp.stdout[pos] == "\n"):
            pos += 1;
        irs = "\n\n"
    arr = sp.stdout[pos:].split(irs)
    if arr[-1] == '':
        arr = arr[:-1]
    return [line + irs for line in arr]

def run_s(*args):
    """Execute a command and return the stdout in scalar context"""
    global CHILD_ERROR, AUTODIE, TRACEBACK, TRACE_RUN
    if len(args) == 1:
        args = args[0]
    try:
        sp = subprocess.run(args,stdin=sys.stdin,capture_output=True,text=True,shell=need_sh(args))
    except FileNotFoundError:   # can happen on windows if shell=False
        sp = subprocess.CompletedProcess(args, 127)
    except OSError:             # check if we're trying to run a perl or python script on Windows
        if isinstance(args, str):
            args = [args]
        arg_split = args[0].split()[0]
        if arg_split.endswith('.py'):
            args = [sys.executable] + args
        elif arg_split.endswith('.pl'):
            args = ['perl'] + args
        else:
            raise
        sp = subprocess.run(args,stdin=sys.stdin,capture_output=True,text=True,shell=need_sh(args))
    if TRACE_RUN:
        carp(f'trace run({args}): {repr(sp)}', skip=2)
    CHILD_ERROR = sp.returncode
    if CHILD_ERROR:
        if AUTODIE:
            raise Die(f'run({args}): failed with rc {CHILD_ERROR}')
        if TRACEBACK:
            cluck(f'run({args}): failed with rc {CHILD_ERROR}',skip=2)
    return sp.stdout

def say(fh, *args):
    """Implementation of perl $fh->say method"""
    global OS_ERROR, TRACEBACK, AUTODIE
    try:
        print(*args, file=fh)
        return 1        # True
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(f"say failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return ''       # False


def seek(fh, pos, whence):
    """Implementation of perl seek"""
    global OS_ERROR, TRACEBACK, AUTODIE
    try:
        return fh.seek(pos, whence)
        return 1        # True
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(f"seek({pos},{whence}) failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return None


def seekdir(DIR, pos):
    DIR[1] = pos

def select(fh):
    """Implementation of perl select function"""
    result = sys.stdout
    sys.stdout = fh
    return result

def setpos(fh, off):
    """Implementation of perl $fh->setpos method"""
    global OS_ERROR, TRACEBACK, AUTODIE
    try:
        fh.seek(off, os.SEEK_SET)
        return 1        # True
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(f"setpos({off}) failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return ''       # False


def set_breakpoint():
    """Sets a debugger breakpoint, but only if pdb is active, mimicking $DB::single"""
    if 'pdb' in sys.modules:
        import pdb
        pdb.set_trace()


def set_element(base, index, value):
    """Implementation of perl = on an array element or hash key"""
    base[index] = value
    return value

def set_last_ndx(arr, ndx):
    """Implementation of assignment to perl array last index $#array"""
    del arr[ndx+1:]
    for _ in range((ndx+1)-len(arr)):
        arr.append(None)


def shift_left_element(base, index, value):
    base[index] <<= value
    return base[index]

def shift_right_element(base, index, value):
    base[index] >>= value
    return base[index]

def shortmess(*args, skip=0):
    """Message with no backtrace"""
    def ff(fn):
        fn = os.path.relpath(fn)
        if fn.startswith('./'):
            return fn[2:]
        return fn
    stack = inspect.stack()
    stack = stack[skip:]
    m = ''.join(map(str, args))
    m += ' at ' + ff(stack[1].filename) + ' line ' + str(stack[1].lineno) + ".\n"
    return m

def spaceship(a,b):
    """3-way comparison like the <=> operator in perl"""
    return (a > b) - (a < b)


def splice(array, *args):
    """Implementation of splice function"""
    offset = 0;
    if len(args) >= 1:
        offset = args[0]
    length = len(array)
    if len(args) >= 2:
        length = args[1]
    if offset < 0:
        offset += len(array)
    total = offset + length
    if length < 0:
        total = length
    removed = array[offset:total]
    array[offset:total] = args[2:]
    return removed

def splice_s(array, *args):
    """Implementation of splice function in scalar context"""
    offset = 0;
    if len(args) >= 1:
        offset = args[0]
    length = len(array)
    if len(args) >= 2:
        length = args[1]
    if offset < 0:
        offset += len(array)
    total = offset + length
    if length < 0:
        total = length
    removed = array[offset:total]
    array[offset:total] = args[2:]
    if not removed:
        return None
    return removed[-1]

def split(pattern, string, maxsplit=0, flags=0):
    """Split function in perl is similar to re.split but not quite
       the same - this function makes it the same"""
    result = re.split(pattern, string, max(0, maxsplit), flags)
    if len(result) >= 1 and result[0] == '' and (m:=re.match(pattern, string, flags)) and len(m.group(0)) == 0:
        result = result[1:]   # A zero-width match at the beginning of EXPR never produces an empty field
    if maxsplit >= -1:  # We subtracted one from what the user specifies
        limit = len(result)
        # Empty results at the end are eliminated
        for i in range(limit-1, -1, -1):
            if result[i] == '':
                limit -= 1
            else:
                break
        return result[:limit]
    return result

def splitdir(*_args):
    """Implementation of File::Spec->splitdir"""
    return split(r"/", _str(_args[0]), -1 - 1)  # Preserve trailing fields


def splitpath(*_args):
    """Implementation of File::Spec->splitpath"""
    [path, nofile] = list_of_n(_args, 2)

    [volume, directory, file] = ("", "", "")

    if nofile:
        directory = path
    else:
        _m = re.search(
            re.compile(r"^ ( (?: .* / (?: \.\.?\Z )? )? ) ([^/]*) ", re.X | re.S), _str(path)
        )
        directory = _m.group(1)
        file = _m.group(2)

    return [volume, directory, file]


def split_s(pattern, string, maxsplit=0, flags=0):
    """Split function in perl is similar to re.split but not quite
       the same - this is the version used in scalar context"""
    result = re.split(pattern, string, max(0, maxsplit), flags)
    if len(result) >= 1 and result[0] == '' and (m:=re.match(pattern, string, flags)) and len(m.group(0)) == 0:
        result = result[1:]   # A zero-width match at the beginning of EXPR never produces an empty field
    if maxsplit >= -1:  # We subtracted one from what the user specifies
        limit = len(result)
        # Empty results at the end are eliminated
        for i in range(limit-1, -1, -1):
            if result[i] == '':
                limit -= 1
            else:
                break
        return limit
    return len(result)

def stat_cando(self, mode, eff):
    """Implementation of File::Stat::stat_cando.  This takes an arrayref containing the return values of stat or lstat as its first argument, and interprets it for you"""
    if os.name == 'nt':
        if (self._mode & mode):
            return 1    # True
        return ''       # False
    uid = os.geteuid() if eff else os.getuid()
    def _ingroup(gid, eff):
        [egid, *supp] = os.getgrouplist(os.geteuid(), os.getegid())
        rgid = os.getgid()
        if gid == (egid if eff else rgid):
            return 1    # True
        if gid in supp:
            return 1    # True
        return ''       # False
    if uid == 0 or (sys.platform == 'cygwin' and _ingroup(544, eff)):    # Root
        if not (mode & 0o111):
            return 1    # Not testing for executable: all file tests are true
        if (self._mode & 0o111) or st_py.S_ISDIR(self._mode):
            return 1    # True
        return ''       # False
    if self._uid == uid:
        if (self._mode & mode):
            return 1    # True
    elif _ingroup(self._gid, eff):
        if (self._mode & (mode >> 3)):
            return 1    # True
    else:
        if (self._mode & (mode >> 6)):
            return 1    # True
    return ''           # False

@dataclasses.dataclass
class File_stat(collections.abc.Sequence):
    _dev: int
    _ino: int
    _mode: int
    _nlink: int
    _uid: int
    _gid: int
    _rdev: int
    _size: int
    _atime: int
    _mtime: int
    _ctime: int
    _blksize: int
    _blocks: int
    _item_map = {0:'_dev', 1:'_ino', 2:'_mode', 3:'_nlink', 4:'_uid', 5:'_gid',
            6:'_rdev', 7:'_size', 8:'_atime', 9:'_mtime', 10:'_ctime', 11:'_blksize', 12:'_blocks'}
    def dev(self):
        return self._dev
    def ino(self):
        return self._ino
    def mode(self):
        return self._mode
    def nlink(self):
        return self._nlink
    def uid(self):
        return self._uid
    def gid(self):
        return self._gid
    def rdev(self):
        return self._rdev
    def size(self):
        return self._size
    def atime(self):
        return self._atime
    def mtime(self):
        return self._mtime
    def ctime(self):
        return self._ctime
    def blksize(self):
        return self._blksize
    def blocks(self):
        return self._blocks
    def __len__(self):
        return len(self._item_map)
    def __getitem__(self, index):
        if isinstance(index, slice):
            return [self[i] for i in range(*index.indices(len(self)))]
        if index < 0:
            index += len(self)
        try:
            return getattr(self, self._item_map[index])
        except KeyError:
            raise IndexError('File_stat index out of range')
    def __contains__(self, item):
        return item in self._item_map
    def cando(self, mode, eff):
        return stat_cando(self, mode, eff)

def stat(path):
    """Handle stat call with or without "use File::stat;" """
    if isinstance(path, File_stat):
        return path     # for '_' special variable
    try:
        if hasattr(path, 'fileno') and os.stat in os.supports_fd:
            path = path.fileno()
        elif hasattr(path, 'name'):
            path = path.name
        s = os.stat(path)
    except Exception:
        return ()
    result = File_stat(_dev=s.st_dev, _ino=s.st_ino, _mode=s.st_mode,
            _nlink=s.st_nlink, _uid=s.st_uid, _gid=s.st_gid, 
            _rdev=s.st_rdev if hasattr(s, 'st_rdev') else 0,
            _size=s.st_size, _atime=s.st_atime, _mtime=s.st_mtime, _ctime=s.st_ctime,
            _blksize=s.st_blksize if hasattr(s, 'st_blksize') else 512,
            _blocks=s.st_blocks if hasattr(s, 'st_blocks') else s.st_size // 512)
    return result

def strftime(fmt, sec, min=None, hour=None, mday=None, mon=None, year=None, wday=0, yday=0, isdst=0):
    """Implementation of perl strftime"""
    if min is None:
        min = sec[1]
        hour = sec[2]
        mday = sec[3]
        mon = sec[4]
        year = sec[5]
        sec = sec[0]
    tl = timelocal(sec, min, hour, mday, mon, year)
    return tm_py.strftime(fmt, tm_py.localtime(tl))

def sub():
    """Implementation of __SUB__ in perl"""
    try:
        frame = sys._getframe(1)
        name = frame.f_code.co_name
        return frame.f_globals[name]
    except Exception:
        return None

def substitute_and_count(this, that, var, replace=True, count=0):
    """Perform a re substitute, but also count the # of matches"""
    (result, ctr) = re.subn(this, that, var, count=count)
    if not replace:
        return (var, result)
    return (result, ctr)

def substitute_element(base, index, this, that, count=0, replace=True):
    """Perform a re substitution on an array element or hash value, and also count the # of matches"""
    (result, ctr) = re.subn(this, that, _str(base[index]), count=count)
    if replace:
        base[index] = result
        return ctr
    return result

def substitute_global(packname, varname, this, that, replace=True, count=0):
    """Perform a re substitute on a global, and also count the # of matches"""
    namespace = getattr(builtins, packname)
    var = _str(getattr(namespace, varname))
    (result, ctr) = re.subn(this, that, var, count=count)
    if replace:
        setattr(namespace, varname, result)
        return ctr
    return result

def substr(this, start, length, replacement):
    """Handle substr with replacement - returns a tuple
       with (new_this, chars_removed)"""
    chars_removed = this[start:start+length]
    new_this = this[:start] + replacement + this[start+length:]
    return (new_this, chars_removed)

def subtract_element(base, index, value):
    """Implementation of -= on an array element"""
    try:
        base[index] -= value
    except TypeError:
        if isinstance(value, int) or isinstance(value, float):
            base[index] = num(base[index]) - value
        elif value is not None:
            raise
    return base[index]

def switch(s_val):
    """Implementation of switch/given statement in perl.  This
    returns a function that is called for each case."""
    def iter_to_bool(i):
        try:
            v = next(i)
            return True
        except StopIteration:
            return False

    if callable(s_val):
        def f_switch(c_val):
            if callable(c_val):
                return s_val == c_val
            if isinstance(c_val, collections.abc.Iterable) and not isinstance(c_val, str):
                return s_val(*c_val)
            return s_val(c_val)
        return f_switch
    elif isinstance(s_val, int) or isinstance(s_val, float):
        def n_switch(c_val):
            if isinstance(c_val, int) or isinstance(c_val, float):
                return s_val == c_val
            if callable(c_val):
                return c_val(s_val)
            if isinstance(c_val, collections.abc.Iterable) and not isinstance(c_val, str):
                if hasattr(c_val, 'isHash') and c_val.isHash:
                    return str(s_val) in c_val
                return s_val in c_val
            if isinstance(c_val, re.Pattern):
                return re.search(c_val, str(s_val))
            if isinstance(c_val, dict):
                return str(s_val) in c_val
            return str(s_val) == str(c_val)
        return n_switch
    elif isinstance(s_val, str):
        def s_switch(c_val):
            if (isinstance(c_val, collections.abc.Iterable) and not isinstance(c_val, str)) or isinstance(c_val, dict):
                return s_val in c_val       # list, Array, or Hash
            if callable(c_val):
                return c_val(s_val)
            if isinstance(c_val, re.Pattern):
                return re.search(c_val, s_val)
            return s_val == str(c_val)
        return s_switch
    elif isinstance(s_val, dict) and (not hasattr(s_val, 'isHash') or s_val.isHash):
        def h_switch(c_val):
            if isinstance(c_val, dict) and (not hasattr(c_val, 'isHash') or c_val.isHash):
                return s_val == c_val
            if isinstance(c_val, collections.abc.Iterable) and not isinstance(c_val, str):
                return iter_to_bool(filter(lambda _d: _d in s_val and s_val[_d], c_val))
            if callable(c_val):
                return c_val(s_val)
            if isinstance(c_val, re.Pattern):
                return iter_to_bool(filter(lambda _d: re.search(c_val, _d) and _d in s_val and s_val[_d], s_val.keys()))
            return str(c_val) in s_val and s_val[str(c_val)]
        return h_switch
    elif isinstance(s_val, collections.abc.Iterable) and (not hasattr(s_val, 'isHash') or not s_val.isHash):
        def a_switch(c_val):
            if isinstance(c_val, dict) and (not hasattr(c_val, 'isHash') or c_val.isHash):
                return iter_to_bool(filter(lambda _d: str(_d) in c_val and c_val[str(_d)], s_val))
            if isinstance(c_val, collections.abc.Iterable) and not isinstance(c_val, str):
                for item in s_val:
                    if item not in c_val:
                        return False
                return True
            if callable(c_val):
                return c_val(*(map(str, s_val)))
            if isinstance(c_val, re.Pattern):
                return iter_to_bool(filter(lambda _d: re.search(c_val, _d), map(str, s_val)))
            return c_val in s_val
        return a_switch
    elif isinstance(s_val, re.Pattern):
        def r_switch(c_val):
            if isinstance(c_val, dict) and (not hasattr(c_val, 'isHash') or c_val.isHash):
                return iter_to_bool(filter(lambda _d: re.search(s_val, _d) and c_val[_d], c_val.keys()))
            if isinstance(c_val, collections.abc.Iterable) and not isinstance(c_val, str):
                return iter_to_bool(filter(lambda _d: re.search(s_val, _d), map(str, c_val)))
            if callable(c_val):
                return c_val(s_val)
            if isinstance(c_val, re.Pattern):
                return s_val.pattern == c_val.pattern
            return re.search(s_val, str(c_val))
        return r_switch
    else:
        def n_switch(c_val):
            return False
        return n_switch

def sysread(fh, var, length, offset=0, need_len=False):
    """Read length bytes from the fh, and return the result to store in var
       if need_len is False, else return a tuple with the result
       and the length read"""
    global OS_ERROR, TRACEBACK, AUTODIE
    if var is None:
        var = ''
    try:
        s = os.read(fh.fileno(), length)
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(f"sysread of {length} bytes failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        if need_len:
            return (var, None)
        return var

    ls = len(s)
    lv = len(var)
    if offset < 0:
        offset += lv
    if offset:
        if isinstance(var, str):
            var = var.encode()
        if need_len:
            return (var[:offset] + (b'\0' * (offset-lv)) + s, ls)
        else:
            return var[:offset] + (b'\0' * (offset-lv)) + s
    if need_len:
        return (s, ls)
    return s

def sysseek(fh, pos, how=os.SEEK_SET):
    """Implementation of perl sysseek"""
    return os.lseek(fh.fileno(), pos, how)

def system(*args):
    """Execute a command and return the return code"""
    global CHILD_ERROR, AUTODIE, TRACEBACK, TRACE_RUN
    if len(args) == 1:
        args = args[0]
    try:
        sp = subprocess.run(args,text=True,stdin=sys.stdin,stdout=sys.stdout,stderr=sys.stderr,shell=need_sh(args))
    except FileNotFoundError:   # can happen on windows if shell=False
        sp = subprocess.CompletedProcess(args, 127)
    except OSError:             # check if we're trying to run a perl or python script on Windows
        if isinstance(args, str):
            args = [args]
        arg_split = args[0].split()[0]
        if arg_split.endswith('.py'):
            args = [sys.executable] + args
        elif arg_split.endswith('.pl'):
            args = ['perl'] + args
        else:
            raise
        sp = subprocess.run(args,text=True,stdin=sys.stdin,stdout=sys.stdout,stderr=sys.stderr,shell=need_sh(args))
    if TRACE_RUN:
        carp(f'trace system({args}): {repr(sp)}', skip=2)
    CHILD_ERROR = sp.returncode
    if CHILD_ERROR:
        if AUTODIE:
            raise Die(f'system({args}): failed with rc {CHILD_ERROR}')
        if TRACEBACK:
            cluck(f'system({args}): failed with rc {CHILD_ERROR}',skip=2)
    return CHILD_ERROR

def syswrite(fh, scalar, length=None, offset=0):
    """Implementation of perl syswrite"""
    if length is None and hasattr(scalar, 'len'):
        length = len(scalar)-offset
    if isinstance(scalar, str):
        return os.write(fh.fileno(), scalar[offset:length+offset].encode())
    elif isinstance(scalar, bytes):
        return os.write(fh.fileno(), scalar[offset:length+offset])
    else:
        return os.write(fh.fileno(), str(scalar).encode())

def tell(fh):
    """Implementation of perl tell"""
    global OS_ERROR, TRACEBACK, AUTODIE
    try:
        return fh.tell()
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            cluck(f"tell failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return -1


def telldir(DIR):
    return DIR[1]

def tempdir(*args):
    """Implementation of File::Temp::tempdir()"""
    template=None
    options={}
    start = 0
    if len(args) % 2 == 1:
        template = args[0]
        start = 1
    for i in range(start, len(args), 2):
        options[args[i]] = args[i+1]

    dirn=None
    base=None
    if template:
        template = template.replace('X', '')
        (base, dirn, tail) = fileparse(template)
    if 'DIR' in options:
        dirn = options['DIR']
    return tempfile.mkdtemp(prefix=base, dir=dirn)

def tempfile_(*args):
    """Implementation of File::Temp::tempfile() in list context"""
    template=None
    options={}
    start = 0
    if len(args) % 2 == 1:
        template = args[0]
        start = 1
    for i in range(start, len(args), 2):
        options[args[i]] = args[i+1]

    dirn=None
    base=None
    suffix=None
    unlink=True
    if 'TEMPLATE' in options:
        template = options['TEMPLATE']
    if template:
        template = template.replace('X', '')
        (base, dirn, tail) = fileparse(template)
    if 'SUFFIX' in options:
        suffix = options['SUFFIX']
    if 'DIR' in options:
        dirn = options['DIR']
    if 'UNLINK' in options:
        unlink = options['UNLINK']
    fh = tempfile.NamedTemporaryFile(prefix=base, dir=dirn, suffix=suffix, delete=unlink)
    def filename(fh):
        return fh._name
    fh._name = fh.name
    fh.filename = types.MethodType(filename, fh)
    return (fh, fh.name)

def tempfile_s(*args):
    """Implementation of File::Temp::tempfile() in scalar context"""
    (fh, _) = tempfile_(*args)
    return fh

def tempnam(template, suffix):
    """Implementation of File::Temp::tempnam()"""
    template = template.replace('X', '')
    (base, dirn, tail) = fileparse(template)
    (fh, name) = tempfile.mkstemp(prefix=base, dir=dirn, suffix=suffix)
    fh.close()
    return name

def time():
    """ Replacement for perl built-in time function"""
    return (tm_py.time_ns() // 1000000000)

def timegm(sec, min, hour, mday, mon, year, wday=0, yday=0, isdst=0):
    """Replacement for perl built-in timegm function"""
    if year < 1900:
        year += 1900
    return calendar.timegm((year, mon+1, mday, hour, min, sec, 0, 1, -1))


def timelocal(sec, min, hour, mday, mon, year, wday=0, yday=0, isdst=0):
    """Replacement for perl built-in timelocal function"""
    if year < 1900:
        year += 1900
    try:
        return tm_py.mktime((year, mon+1, mday, hour, min, sec, 0, 1, -1))
    except Exception:
        try:
            import datetime
            diff = datetime.datetime(year, mon+1, mday, hour, min, sec) - datetime.datetime.fromtimestamp(0)
            return diff.total_seconds()
        except Exception:
            return 999999999999



def tmpfile():
    """Implementation of File::Temp tmpfile()"""
    return tempfile.TemporaryFile()

def tmpnam():
    """Implementation of POSIX tmpnam() in list context"""
    ntf = tempfile.NamedTemporaryFile(delete=False)
    return (ntf, ntf.name)

def tmpnam_s():
    """Implementation of POSIX tmpnam() in scalar context"""
    ntf = tempfile.NamedTemporaryFile(delete=False)
    result = ntf.name
    ntf.close()
    return result

def translate(table, var, replace=True, complement=False, delete=False, squash=False):
    """Perform a tr translate operation"""
    result = []
    pv = None
    for ch in var:
        if ord(ch) > 256 and complement:
            ch = chr(256)
        try:
            v = table[ord(ch)]
        except LookupError:
            v = ch
            pv = None
        if v is not None:
            if isinstance(v, int):
                v = chr(v)
            if pv != v or not squash:
                result.append(v)
            pv = v
    return ''.join(result)

def translate_and_count(table, var, replace=True, complement=False, delete=False, squash=False):
    """Perform a tr translate, but also count the # of matches"""
    result = []
    ctr = 0;
    pv = None
    for ch in var:
        if ord(ch) > 256 and complement:
            ch = chr(256)
        try:
            v = table[ord(ch)]
            ctr += 1
        except LookupError:
            v = ch
            pv = None
        if v is not None:
            if isinstance(v, int):
                v = chr(v)
            if pv != v or not squash:
                result.append(v)
            pv = v
    if not replace:
        return (var, ''.join(result))
    return (''.join(result), ctr)

def translate_element(base, index, table, replace=True, complement=False, delete=False, squash=False):
    """Perform a tr translate on a global, and also count the # of matches"""
    result = []
    ctr = 0;
    var = _str(base[index])
    pv = None
    for ch in var:
        if ord(ch) > 256 and complement:
            ch = chr(256)
        try:
            v = table[ord(ch)]
            ctr += 1
        except LookupError:
            v = ch
            pv = None
        if v is not None:
            if isinstance(v, int):
                v = chr(v)
            if pv != v or not squash:
                result.append(v)
            pv = v
    if replace:
        base[index] = ''.join(result)
        return ctr
    return ''.join(result)

def translate_global(packname, varname, table, replace=True, complement=False, delete=False, squash=False):
    """Perform a tr translate on a global, and also count the # of matches"""
    result = []
    ctr = 0;
    pv = None
    namespace = getattr(builtins, packname)
    var = _str(getattr(namespace, varname))
    for ch in var:
        if ord(ch) > 256 and complement:
            ch = chr(256)
        try:
            v = table[ord(ch)]
            ctr += 1
        except LookupError:
            v = ch
            pv = None
        if v is not None:
            if isinstance(v, int):
                v = chr(v)
            if pv != v or not squash:
                result.append(v)
            pv = v
    if replace:
        setattr(namespace, varname, ''.join(result))
        return ctr
    return ''.join(result)

def truncate(fh, length):
    """Implementation of perl $fh->truncate method"""
    global OS_ERROR, TRACEBACK, AUTODIE
    try:
        if hasattr(fh, 'flush'):
            fh.flush()
        if hasattr(fh, 'truncate'):
            fh.truncate(length)
        else:
            if hasattr(fh, 'fileno'):
                fh = fh.fileno()
            os.truncate(fh, length)
        return 1    # True
    except Exception as _e:
        OS_ERROR = str(_e)
        if TRACEBACK:
            if isinstance(fh, str):
                cluck(f"truncate({fh}, {length}) failed: {OS_ERROR}",skip=2)
            else:
                cluck(f"truncate to {length} failed: {OS_ERROR}",skip=2)
        if AUTODIE:
            raise
        return None


def ucfirst(string):
    """Implementation of ucfirst and \ u in interpolated strings: uppercase the first char of the given string"""
    return string[0:1].upper() + string[1:]

def ungetc(fh, ordinal):
    """Implementation of perl $fh->ungetc method"""
    # We only support putting back what was there after a getc
    if hasattr(fh, "_last_pos"):    # Set by _getc
        fh.seek(fh._last_pos, 0)
        ch = fh.read(1)
        if ch == chr(ordinal):
            fh.seek(fh._last_pos, 0)
            delattr(fh, "_last_pos")
            return
        else:
            fh.seek(0, 2)

    raise NotImplementedError

def unlink(*args):
    """Implementation of perl unlink"""
    global OS_ERROR

    cnt = 0
    for f in args:
        try:
            os.unlink(f)
            cnt += 1
        except Exception as e:
            OS_ERROR = str(e)

    return cnt

def unpack(template, bytestr):
    """Unpack bytestr using the template and return a list of values"""
    if isinstance(bytestr, str):
        bytestr = _str_to_bytes(bytestr)
    result = []
    format_and_counts = _get_pack_unpack_format_and_counts(template, (bytestr,), is_unpack=True)
    start = 0
    for format, _, _ in format_and_counts:
        size = struct.calcsize(format)
        result.extend(struct.unpack(format, bytestr[start:start+size]))
        start += size

    for i, r in enumerate(result):
        if isinstance(r, bytes):
            result[i] = _bytes_to_str(r)

    return result

def updir():
    """Implementation of File::Spec->updir"""
    return '..'

def utime(atime, mtime, *args):
    """Implementation of perl utime function"""
    global TRACEBACK, AUTODIE, OS_ERROR
    result = 0
    OS_ERROR = ''
    times = None
    if atime is None and mtime is None:
        pass
    elif atime is None:
        atime = 0
    elif mtime is None:
        mtime = 0
    times = (atime, mtime)
    for fd in args:
        try:
            if hasattr(fd, 'fileno') and os.utime in os.supports_fd:
                fd = fd.fileno()
            elif hasattr(fd, 'name'):
                fd = fd.name
            os.utime(fd, times)
            result += 1
        except Exception as _e:
            OS_ERROR = str(_e)
            if TRACEBACK:
                cluck(f"utime({atime}, {mtime}, {fd}) failed: {OS_ERROR}",skip=2)
            if AUTODIE:
                raise
    return result



def wait():
    """Replacement for perl wait() call"""
    global CHILD_ERROR
    try:
        (pid, stat) = os.wait()
        CHILD_ERROR = stat
        return pid
    except Exception:
        return -1


def waitpid(pid, flags):
    """Replacement for perl waitpid() call"""
    global CHILD_ERROR
    try:
        (rpid, stat) = os.waitpid(pid, options)
        CHILD_ERROR = stat
        return rpid
    except Exception:
        return -1


def write_(fh, scalar, length=None, offset=0):
    """Implementation of perl $fh->write"""
    if length is None and hasattr(scalar, 'len'):
        length = len(scalar)-offset
    if 'b' in fh.mode:
        if isinstance(scalar, str):
            return fh.write(scalar[offset:length+offset].encode())
        elif isinstance(scalar, bytes):
            return fh.write(scalar[offset:length+offset])
        return fh.write(str(scalar).encode())
    else:
        if isinstance(scalar, str):
            return fh.write(scalar[offset:length+offset])
        elif isinstance(scalar, bytes):
            return fh.write(scalar[offset:length+offset].decode())
        return fh.write(str(scalar))


def xor_element(base, index, value):
    base[index] ^= value
    return base[index]
