"""
Dekorator funkcji wykonujący sprawdzanie poprawności przedziałów dla przekazanych argumentów do dowolnej funkcji lub metody. Podsumowanie użycia:

    @rangetest(percent=(0.0, 1.0), month=(1, 12))
    def func-or-method(..., percent, ..., month=5, ...):
        ...
    func-or-method(..., value, month=8, ...)

Argumenty dla dekoratora określane są po słowach kluczowych. W samym wywołaniu argumenty mogą być przekazywane po pozycji lub za pomocą słowa kluczowego. Wartości domyślne mogą być pomijane.
Przykładowe przypadki użycia znajdują się w pliku rangetest_test.py.
"""
trace = True

def rangetest(**argchecks):                 # Sprawdzenie poprawności przedziałów dla obu oraz wartości domyślnych
    def onDecorator(func):                  # onCall pamięta func oraz argchecks
        if not __debug__:                   # True, jeśli ustawiono "python –O main.py args..."
            return func                     # Opakowanie przy debugowaniu; inaczej użycie oryginału
        else:
            funcname = func.__name__
            funccode = func.__code__
            funcargs = funccode.co_varnames[:funccode.co_argcount]

            def onCall(*pargs, **kargs):
                # Wszystkie argumenty pozycyjne pargs dopasowują pierwsze N oczekiwanych argumentów po pozycji
                # Reszta musi być w kargs lub jest pomijanymi wartościami domyślnymi

                positionals = funcargs[:len(pargs)]
                errormsg    = lambda *args: '%s Argument "%s" nie mieści się w przedziale %s..%s' % args

                for (argname, (low, high)) in argchecks.items():
                    # Dla wszystkich argumentów, które mają być sprawdzone
                    if argname in kargs:
                        # Przekazane po nazwie
                        if kargs[argname] < low or kargs[argname] > high:
                            raise TypeError(errormsg(funcname, argname, low, high))

                    elif argname in positionals:
                        # Przekazane po pozycji
                        position = positionals.index(argname)
                        if pargs[position] < low or pargs[position] > high:
                            raise TypeError(errormsg(funcname, argname, low, high))

                    else:
                        # Assume not passed: default
                        if trace:
                            print(f'-Argument "{argname}" ma wartość domyślną')

                return func(*pargs, **kargs)    # OK: wykonanie oryginalnego wywołania
            return onCall
    return onDecorator

