Blame pynslcd/expr.py

Packit 6bd9ab
Packit 6bd9ab
# expr.py - expression handling functions
Packit 6bd9ab
#
Packit 6bd9ab
# Copyright (C) 2011, 2012, 2013 Arthur de Jong
Packit 6bd9ab
#
Packit 6bd9ab
# This library is free software; you can redistribute it and/or
Packit 6bd9ab
# modify it under the terms of the GNU Lesser General Public
Packit 6bd9ab
# License as published by the Free Software Foundation; either
Packit 6bd9ab
# version 2.1 of the License, or (at your option) any later version.
Packit 6bd9ab
#
Packit 6bd9ab
# This library is distributed in the hope that it will be useful,
Packit 6bd9ab
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6bd9ab
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6bd9ab
# Lesser General Public License for more details.
Packit 6bd9ab
#
Packit 6bd9ab
# You should have received a copy of the GNU Lesser General Public
Packit 6bd9ab
# License along with this library; if not, write to the Free Software
Packit 6bd9ab
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit 6bd9ab
# 02110-1301 USA
Packit 6bd9ab
Packit 6bd9ab
"""Module for handling expressions used for LDAP searches.
Packit 6bd9ab
Packit 6bd9ab
>>> expr = Expression('foo=$foo')
Packit 6bd9ab
>>> expr.value(dict(foo='XX'))
Packit 6bd9ab
'foo=XX'
Packit 6bd9ab
>>> expr = Expression('foo=${foo:-$bar}')
Packit 6bd9ab
>>> expr.value(dict(foo='', bar='YY'))
Packit 6bd9ab
'foo=YY'
Packit 6bd9ab
>>> expr.value(dict(bar=['YY', 'ZZ']))
Packit 6bd9ab
'foo=YY'
Packit 6bd9ab
>>> Expression(r'${passwd#{crypt\}}').value(dict(passwd='{crypt}HASH'))
Packit 6bd9ab
'HASH'
Packit 6bd9ab
>>> Expression('${var#trim}').value(dict(var='notrimme'))
Packit 6bd9ab
'notrimme'
Packit 6bd9ab
>>> Expression('${var#?trim}').value(dict(var='xtrimme'))
Packit 6bd9ab
'me'
Packit 6bd9ab
>>> Expression('${var#*trim}').value(dict(var='xxxtrimme'))
Packit 6bd9ab
'me'
Packit 6bd9ab
>>> Expression('${var%.txt}').value(dict(var='foo.txt'))
Packit 6bd9ab
'foo'
Packit 6bd9ab
>>> Expression('${x#$y}').value(dict(x='a/b', y='a'))
Packit 6bd9ab
'/b'
Packit 6bd9ab
>>> Expression('${var#t*is}').value(dict(var='this is a test'))
Packit 6bd9ab
' is a test'
Packit 6bd9ab
>>> Expression('${var##t*is}').value(dict(var='this is a test'))
Packit 6bd9ab
' a test'
Packit 6bd9ab
>>> Expression('${var%t*st}').value(dict(var='this is a test'))
Packit 6bd9ab
'this is a '
Packit 6bd9ab
>>> Expression('${var%%t*st}').value(dict(var='this is a test'))
Packit 6bd9ab
''
Packit 6bd9ab
Packit 6bd9ab
>>> Expression('${test1:0:6}').value(dict(test1='foobar'))
Packit 6bd9ab
'foobar'
Packit 6bd9ab
>>> Expression('${test1:0:10}').value(dict(test1='foobar'))
Packit 6bd9ab
'foobar'
Packit 6bd9ab
>>> Expression('${test1:0:3}').value(dict(test1='foobar'))
Packit 6bd9ab
'foo'
Packit 6bd9ab
>>> Expression('${test1:3:0}').value(dict(test1='foobar'))
Packit 6bd9ab
''
Packit 6bd9ab
>>> Expression('${test1:3:6}').value(dict(test1='foobar'))
Packit 6bd9ab
'bar'
Packit 6bd9ab
>>> Expression('${test1:7:0}').value(dict(test1='foobar'))
Packit 6bd9ab
''
Packit 6bd9ab
>>> Expression('${test1:7:3}').value(dict(test1='foobar'))
Packit 6bd9ab
''
Packit 6bd9ab
Packit 6bd9ab
"""
Packit 6bd9ab
Packit 6bd9ab
import fnmatch
Packit 6bd9ab
import re
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
# exported names
Packit 6bd9ab
__all__ = ('Expression', )
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
# TODO: do more expression validity checking
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
class MyIter(object):
Packit 6bd9ab
    """Custom iterator-like class with a back() method."""
Packit 6bd9ab
Packit 6bd9ab
    def __init__(self, value):
Packit 6bd9ab
        self.value = value
Packit 6bd9ab
        self.pos = 0
Packit 6bd9ab
Packit 6bd9ab
    def next(self):
Packit 6bd9ab
        self.pos += 1
Packit 6bd9ab
        try:
Packit 6bd9ab
            return self.value[self.pos - 1]
Packit 6bd9ab
        except IndexError:
Packit 6bd9ab
            return None
Packit 6bd9ab
Packit 6bd9ab
    def startswith(self, value):
Packit 6bd9ab
        return self.value[self.pos].startswith(value)
Packit 6bd9ab
Packit 6bd9ab
    def back(self):
Packit 6bd9ab
        self.pos -= 1
Packit 6bd9ab
Packit 6bd9ab
    def __iter__(self):
Packit 6bd9ab
        return self
Packit 6bd9ab
Packit 6bd9ab
    def get_name(self):
Packit 6bd9ab
        """Read a variable name from the value iterator."""
Packit 6bd9ab
        name = ''
Packit 6bd9ab
        for c in self:
Packit 6bd9ab
            if not c or not c.isalnum():
Packit 6bd9ab
                self.back()
Packit 6bd9ab
                return name
Packit 6bd9ab
            name += c
Packit 6bd9ab
        return name
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
class DollarExpression(object):
Packit 6bd9ab
    """Class for handling a variable $xxx ${xxx}, ${xxx:-yyy} or ${xxx:+yyy}
Packit 6bd9ab
    expression."""
Packit 6bd9ab
Packit 6bd9ab
    def __init__(self, value):
Packit 6bd9ab
        """Parse the expression as the start of a $-expression."""
Packit 6bd9ab
        self.op = None
Packit 6bd9ab
        self.expr = None
Packit 6bd9ab
        c = value.next()
Packit 6bd9ab
        if c == '{':
Packit 6bd9ab
            self.name = value.get_name()
Packit 6bd9ab
            c = value.next()
Packit 6bd9ab
            if c == '}':
Packit 6bd9ab
                return
Packit 6bd9ab
            elif c == ':':
Packit 6bd9ab
                if value.startswith('-') or value.startswith('+'):
Packit 6bd9ab
                    # ${attr:-word} or ${attr:+word}
Packit 6bd9ab
                    self.op = c + value.next()
Packit 6bd9ab
                else:
Packit 6bd9ab
                    # ${attr:offset:length}
Packit 6bd9ab
                    self.op = c
Packit 6bd9ab
            elif c in ('#', '%'):
Packit 6bd9ab
                c2 = value.next()
Packit 6bd9ab
                if c2 in ('#', '%'):
Packit 6bd9ab
                    c += c2
Packit 6bd9ab
                else:
Packit 6bd9ab
                    value.back()
Packit 6bd9ab
                self.op = c
Packit 6bd9ab
            else:
Packit 6bd9ab
                raise ValueError('Expecting operator')
Packit 6bd9ab
            self.expr = Expression(value, endat='}')
Packit 6bd9ab
        elif c == '(':
Packit 6bd9ab
            self.name = None
Packit 6bd9ab
            self.op = value.get_name()
Packit 6bd9ab
            c = value.next()
Packit 6bd9ab
            if c != '(':
Packit 6bd9ab
                raise ValueError("Expecting '('")
Packit 6bd9ab
            self.expr = Expression(value, endat=')')
Packit 6bd9ab
            c = value.next()
Packit 6bd9ab
            if c != ')':
Packit 6bd9ab
                raise ValueError("Expecting ')'")
Packit 6bd9ab
        else:
Packit 6bd9ab
            value.back()
Packit 6bd9ab
            self.name = value.get_name()
Packit 6bd9ab
Packit 6bd9ab
    def value(self, variables):
Packit 6bd9ab
        """Expand the expression using the variables specified."""
Packit 6bd9ab
        # lookup the value
Packit 6bd9ab
        value = variables.get(self.name, '')
Packit 6bd9ab
        if value in (None, [], ()):
Packit 6bd9ab
            value = ''
Packit 6bd9ab
        elif isinstance(value, (list, tuple)):
Packit 6bd9ab
            value = value[0]
Packit 6bd9ab
        # TODO: try to return multiple values, one for each value of the list
Packit 6bd9ab
        if self.op == ':-':
Packit 6bd9ab
            return value if value else self.expr.value(variables)
Packit 6bd9ab
        elif self.op == ':+':
Packit 6bd9ab
            return self.expr.value(variables) if value else ''
Packit 6bd9ab
        elif self.op == ':':
Packit 6bd9ab
            offset, length = self.expr.value(variables).split(':')
Packit 6bd9ab
            offset, length = int(offset), int(length)
Packit 6bd9ab
            return value[offset:offset + length]
Packit 6bd9ab
        elif self.op in ('#', '##', '%', '%%'):
Packit 6bd9ab
            match = fnmatch.translate(self.expr.value(variables))
Packit 6bd9ab
            if self.op == '#':
Packit 6bd9ab
                match = match.replace('*', '*?').replace(r'\Z', r'(?P<replace>.*)\Z')
Packit 6bd9ab
            elif self.op == '##':
Packit 6bd9ab
                match = match.replace(r'\Z', r'(?P<replace>.*?)\Z')
Packit 6bd9ab
            elif self.op == '%':
Packit 6bd9ab
                match = r'(?P<replace>.*)' + match.replace('*', '*?')
Packit 6bd9ab
            elif self.op == '%%':
Packit 6bd9ab
                match = r'(?P<replace>.*?)' + match
Packit 6bd9ab
            match = re.match(match, value)
Packit 6bd9ab
            return match.group('replace') if match else value
Packit 6bd9ab
        elif self.op == 'lower':
Packit 6bd9ab
            return self.expr.value(variables).lower()
Packit 6bd9ab
        elif self.op == 'upper':
Packit 6bd9ab
            return self.expr.value(variables).upper()
Packit 6bd9ab
        return value
Packit 6bd9ab
Packit 6bd9ab
    def variables(self, results):
Packit 6bd9ab
        """Add the variables used in the expression to results."""
Packit 6bd9ab
        if self.name:
Packit 6bd9ab
            results.add(self.name)
Packit 6bd9ab
        if self.expr:
Packit 6bd9ab
            self.expr.variables(results)
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
class Expression(object):
Packit 6bd9ab
    """Class for parsing and expanding an expression."""
Packit 6bd9ab
Packit 6bd9ab
    def __init__(self, value, endat=None):
Packit 6bd9ab
        """Parse the expression as a string."""
Packit 6bd9ab
        if not isinstance(value, MyIter):
Packit 6bd9ab
            self.expression = value
Packit 6bd9ab
            value = MyIter(value)
Packit 6bd9ab
        expr = []
Packit 6bd9ab
        literal = ''
Packit 6bd9ab
        c = value.next()
Packit 6bd9ab
        while c != endat:
Packit 6bd9ab
            if c == '$':
Packit 6bd9ab
                if literal:
Packit 6bd9ab
                    expr.append(literal)
Packit 6bd9ab
                expr.append(DollarExpression(value))
Packit 6bd9ab
                literal = ''
Packit 6bd9ab
            elif c == '\\':
Packit 6bd9ab
                literal += value.next()
Packit 6bd9ab
            else:
Packit 6bd9ab
                literal += c
Packit 6bd9ab
            c = value.next()
Packit 6bd9ab
        if literal:
Packit 6bd9ab
            expr.append(literal)
Packit 6bd9ab
        self.expr = expr
Packit 6bd9ab
Packit 6bd9ab
    def value(self, variables):
Packit 6bd9ab
        """Expand the expression using the variables specified."""
Packit 6bd9ab
        res = ''
Packit 6bd9ab
        for x in self.expr:
Packit 6bd9ab
            if hasattr(x, 'value'):
Packit 6bd9ab
                res += x.value(variables)
Packit 6bd9ab
            else:
Packit 6bd9ab
                res += x
Packit 6bd9ab
        return res
Packit 6bd9ab
Packit 6bd9ab
    def variables(self, results=None):
Packit 6bd9ab
        """Return the variables defined in the expression."""
Packit 6bd9ab
        if not results:
Packit 6bd9ab
            results = set()
Packit 6bd9ab
        for x in self.expr:
Packit 6bd9ab
            if hasattr(x, 'variables'):
Packit 6bd9ab
                x.variables(results)
Packit 6bd9ab
        return results
Packit 6bd9ab
Packit 6bd9ab
    def __str__(self):
Packit 6bd9ab
        return self.expression
Packit 6bd9ab
Packit 6bd9ab
    def __repr__(self):
Packit 6bd9ab
        return repr(str(self))