1. Jonathan Eunice
  2. intensional


intensional / intensional.py

from mementos import MementoMetaclass   # to memoize IntensionalSet
from stuf import orderedstuf            # for ReMatch
import re                               # for Re
import fnmatch                          # for Glob
import inspect                          # for en passant operation in Re

class IntensionalSet(object):
    An intensional set (actually, an intensionally defined set) is a set that
    is defined by a definition, rather than an explicit listing of members
    (which would be an extensional set).
    Like other Python sets, IntensionalSet objects can include explicit
    items--but it also admits the possibility of rule-based membership.

    __metaclass__ = MementoMetaclass
# Any = Union, Every = Intersection
# might want to complete more of the set functions like symmetric difference, discard, etc

class ReMatch(orderedstuf):
    An attributes-exposed regular expression match object. Ideally this would be
    a subclass of the re module's match object, but they are of type ``_sre.SRE_Match``
    and `appear to be unsubclassable
    Thus, ReMatch is a subclass of an attributes-exposed dict type, ``stuf``, into
    which the relevant data has been copied.

    # in future, might want to do a pass-through for methods of _sre.SRE_Match
    # though they are currently available through x._match.method
def show_frames():
    f = inspect.currentframe().f_back # the caller
    while f:
        print f, f.f_lineno
        print "   f_locals:", ' '.join(f.f_locals.keys())
        f = f.f_back
class Re(IntensionalSet):
    def __init__(self, pattern, flags=0):
        self.pattern = pattern
        self.flags   = flags
        self.re = re.compile(pattern, flags)
        self.groups     = self.re.groups
        self.groupindex = self.re.groupindex
    def _regroup(self, m):
        Given an _sre.SRE_Match object, create and return a corresponding
        ReMatch object. Also, set the en passant variable to it.
        if not m:
            return m
        result = ReMatch()
        result['_match'] = m   # access to original match object
        result.update(m.groupdict())  # update named groups
        # update numerical / positional groups
        result[0] = m.group(0)
        for i, g in enumerate(m.groups(), start=1):
            result[i] = g
        # this method -> calling method -> MementosMetaclass.__call__ -> Re.__init__ -> caller
        inspect.currentframe().f_back.f_back.f_back.f_back.f_locals['_'] = result

        return result
    def __contains__(self, item):
        # if not isinstance(item, basestring):
        #     item = str(item)
        return self._regroup(self.re.search(item))
    # methods that return ReMatch objects
    def search(self, *args, **kwargs):
        return self._regroup(self.re.search(*args, **kwargs))

    def match(self, *args, **kwargs):
        return self._regroup(self.re.match(*args, **kwargs))
    # methods that don't need ReMatch objects
    def finditer(self, *args, **kwargs):
        return self.re.finditer(*args, **kwargs)
    def findall(self, *args, **kwargs):
        return self.re.findall(*args, **kwargs)
    def split(self, *args, **kwargs):
        return self.re.split(*args, **kwargs)
    def sub(self, *args, **kwargs):
        return self.re.sub(*args, **kwargs)
    def subn(self, *args, **kwargs):
        return self.re.subn(*args, **kwargs)
    def escape(self, *args, **kwargs):
        return self.re.escape(*args, **kwargs)

class Glob(IntensionalSet):
    def __init__(self, expr):
        self.expr = expr
    def __contains__(self, item):
        return fnmatch.fnmatch(item, self.expr)
class Any(IntensionalSet):
    An item is in an Any if it is or is in any member of the set.
    def __init__(self, *args):
        self.items = set(args)
    def __contains__(self, item):
        if item in self.items:
            return True
        for i in self.items:
            if hasattr(i, '__contains__'):
                if item in i:
                    return True
                if item == i:
                    return True
        return False
class Every(IntensionalSet):
    An item is in an Every if it is or is in every member of the set.
    def __init__(self, *args):
        self.items = set(args)

    def __contains__(self, item):
        for i in self.items:
            if hasattr(i, '__contains__'):
                if item not in i:
                    return False
                if item != i:
                    return False
        return True

class ButNot(IntensionalSet):
    An item is in a ButNot if it's in the primary set and not the exclusion.
    def __init__(self, items, exclusion):
        self.items = items
        self.exclusion = exclusion

    def __contains__(self, item):
        if item == self.items or item in self.items:
            if item != self.exclusion and item not in self.exclusion:
                return True
        return False

class Test(IntensionalSet):
    def __init__(self, expr, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs
        self.expr = expr
        if isinstance(expr, basestring):
            if not expr.startswith('lambda'):
                expr = 'lambda x: ' + expr
            self.func = eval(expr)
        elif hasattr(expr, '__call__'):
            self.func = expr
            raise ValueError('expr needs to be string or callable')
    def __contains__(self, item):
            return self.func(item, *self.args, **self.kwargs)
        except StandardError:
            return False
        # NB failure to run test => fails test
        # might silently hide syntax errors and such
        # do we want a mode or mechanism to make such errors into Warnings?