|
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))
|