import sys
import string
"""
This module provides general purpose routines for generating
lists of strings from patterns. Thus:
python Pattern.py 172.16.[72-74,77-82].[101-200]
produces the following sequence of 800 IPs:
172.16.72.101
172.16.72.102
...
172.16.82.199
172.16.82.200
Ranges can be decimal, hexidecimal and alphabetic, e.g.
% python Pattern.py [0-10]
0
1
2
3
4
5
6
7
8
9
10
%
% python Pattern.py foobar_[a-z]
foobar_a
foobar_b
foobar_c
...
foobar_x
foobar_y
foobar_z
%
% python Pattern.py [a-z][A-Z]
aA
aB
aC
...
zX
zY
zZ
%
% python Pattern.py [0x0-0xf]
0
1
2
...
d
e
f
%
For hexadecimal output the leading 0x is left out because it's trivial to add:
% python Pattern.py 0x[0xa-0xf]
0xa
0xb
0xc
0xd
0xe
0xf
%
To help formatting, zero padding on the start of the range
is added to all members of the range, e.g.
% python Pattern.py an-sm[1-8]-g[001-100]
an-sm1-g001
an-sm1-g002
an-sm1-g003
...
an-sm8-g098
an-sm8-g099
an-sm8-g100
%
In addition, Pattern extends IPPatterns notation by adding
the concept of a zip using juxtaposition. Thus the sequences
produced by successive arguments are computing in step and
are displayed together. Thus to produce a simple table of
the hex ascii codes for lowercase letters is simply:
% python Pattern.py [A-Z] 0x[0x41-0x5a]
A 0x41
B 0x42
C 0x43
...
X 0x58
Y 0x59
Z 0x5a
%
Similarly to produce a deck of 52 cards is simply:
% python Pattern.py [2-9,T,J,Q,K,A][C,D,H,S]
2C
2D
2H
2S
3C
3D
3H
3S
...
KC
KD
KH
KS
AC
AD
AH
AS
%
"""
def aFill( str, n ):
"""leftpad str with 'a' so it is at least n chars long"""
return ('a'*(n-len(str))) + str
def zFill( str, n ):
"""leftpad str with '0' so it is at least n chars long"""
return str.zfill( n )
def computeIntRange( start, finish, toInt=int, fromInt=str, fill=zFill, padLen=len ):
"""Computes a range list from a start value, finish value and optional
int-to-string, string-to-int, pad functions and pad length."""
n = padLen( start )
return [ fill( fromInt(i), n ) for i in range(toInt(start),toInt(finish)+1) ]
def fromHex( h ):
"""Convert a hex string into a int"""
return int(h,16)
def toHex( i ):
"""Convert an int into a hex string (without the leading 0x)"""
return hex( i )[2:]
def isHexadecimalRange( start, finish ):
"""Tests for hexadecimal range"""
return start.startswith( '0x' ) and finish.startswith( '0x' )
def isNumericRange( start, finish ):
"""Tests for decimal range"""
return allNumeric( start ) and allNumeric( finish )
def allIn( as, members ):
"Tests that all elements of as are in members"""
for a in as:
if a not in members:
return False
return True
def allLower( as ):
"""Tests that all strings in as are lowercase"""
return allIn( as, string.lowercase )
def allUpper( as ):
"""Tests that all strings in as are uppercase"""
return allIn( as, string.uppercase )
def allNumeric( as ):
return allIn( as, string.digits )
def sameLength( as, bs ):
"""Tests that as and bs are the same length"""
return len( as ) == len( bs )
def lettersToInt( str ):
"""turn a string of letters into a base 26 number"""
return reduce( lambda x, y: 26*x + y, map( string.lowercase.index, str ))
def intToLetters( i, str='' ):
"""convert a number into a string of lowercase letters"""
if i == 0:
return str
else:
return intToLetters( i/26, string.lowercase[i%26] + str )
def isUpperLetterRange( start, finish ):
"""Tests start and finish are both uppercase letter ranges"""
return allUpper( start ) and allUpper( finish )
def isLowerLetterRange( start, finish ):
"""Tests start and finish are both lowercase letter ranges"""
return allLower( start ) and allLower( finish )
def computeRange( start, finish ):
if isHexadecimalRange( start, finish ):
return computeIntRange( start, finish, fromHex, toHex, zFill, lambda x: len( x )-2 )
if isLowerLetterRange( start, finish ):
return computeIntRange( start, finish, lettersToInt, intToLetters, aFill )
if isUpperLetterRange( start, finish ):
return [s.upper() for s in computeRange(start.lower(),finish.lower())]
if isNumericRange( start, finish ):
return computeIntRange( start, finish )
else:
raise SyntaxError, "invalid range syntax"
def splitAt( s, i, gap=0 ):
"""split s into two strings at index i with an optional gap"""
return s[:i], s[i+gap:]
def find( s, target ):
"""version of find that returns len( s ) when target is not found"""
result = s.find( target )
if result == -1: result = len( s )
return result
class BadOpException( Exception ):
pass
def doOp( op, a, b ):
if op == '++':
return setUnion( a, b )
elif op == '--':
return setDifference( a, b )
elif op == '^^':
return setIntersection( a, b )
else:
raise BadOpException
"""Implementation of Sets based on lists
We don't use the python built-in sets because
a) they were added in a later version (2.3?)
b) we wanted an implemetation that presevered
the ordering of the leftmost argument to any set
operation even if it's slower.
"""
def setEmpty():
"""return the empty set"""
return []
def setCopy( set ):
"""(shallow) copy a set"""
return set[:]
def member( x, set ):
"""test for set membership"""
try:
set.index( x )
return True
except ValueError:
return False
def setToList( set ):
"""takes a set and returns a list"""
return set
def setAdd( set, m ):
if not member( m, set ):
set.append( m )
return set
def setFromList( list ):
"""takes a list and returns a set by ignoring duplicates"""
set = setEmpty()
for a in list:
setAdd( set, a )
return set
def setSubtract( set, m ):
"""in place set removal"""
if member( m, set ): set.remove( m )
def setUnion( as, bs ):
"""returns a new set that is the union of as and bs"""
set = setCopy( as )
for b in bs:
setAdd( set, b )
return set
def setDifference( as, bs ):
"""returns a new set that is the difference of as and bs"""
set = setEmpty()
for a in as:
if not member( a, bs ):
set.append( a )
return set
def setIntersection( as, bs ):
"""returns a new set that is the intersection of as and bs"""
set = setEmpty()
for a in as:
if member( a, bs ):
set.append( a )
return set
def find( s, target, i=0 ):
"""Version fo find which returns len( s ) if target is not found"""
result = s.find( target, i )
if result == -1: result = len( s )
return result
def multifind( s, targets, i=0 ):
"""Find the earliest index in s which matches one of targets starting at i"""
return min( [find( s, target, i ) for target in targets] )
def fileExpr( expr, fileStr='@' ):
"""If expression contains @file@ read in contents of file and return as a list"""
if expr.startswith( fileStr ) and expr.endswith( fileStr ):
return [ line.rstrip() for line in file( expr[1:-1] ).readlines() ]
else:
return [ expr ]
def computeList( expr, openStr='[', closeStr=']', rangeStr='-', sepStr=',', fileStr='@' ):
"""Parse and compute range in expr"""
if expr[0] == openStr:
result = []
while expr[0] != closeStr:
expr = expr[1:]
i = multifind( expr, [closeStr,rangeStr,sepStr] )
if expr[i] == sepStr:
item, expr = splitAt( expr, i )
result = result + fileExpr( item )
elif expr[i] == rangeStr:
start, expr = splitAt( expr, i, 1 )
finish, expr = splitAt( expr, multifind( expr, [closeStr,sepStr] ) )
result = result + computeRange( start, finish )
else:
if i > 0: result = result + fileExpr( expr[:-1] )
break
return result
elif expr[0] == fileStr:
return fileExpr( expr[1:] )
else:
return [expr]
def splitOnBrackets( expr, openStr='[', closeStr=']' ):
"""Splits expr in a sequence of alternating non-bracketed and bracketed expressions"""
n = len( expr )
components = []
while len( expr ) > 0:
n = len( expr )
target = openStr
if expr[0] == target: target = closeStr
i = multifind( expr, [target] )
if target == closeStr: i += 1
components.append( expr[:i] )
expr = expr[i:]
return components
def product( fields, i=0, result='' ):
"""Takes a list of list of fields and produces a generator
that permutes through every possible combination of fields in order."""
if i == len( fields ):
yield result
else:
for field in fields[i]:
for x in product( fields, i+1, result + field ):
yield x
def pattern( p ):
"""Splits pattern p into it's constituents and produces
a iterator than generates all possible permutation"""
return product( map( computeList, splitOnBrackets( p ) ) )
def setExpression( expr ):
"""Handles IP expressions of the form:
<exp> = <exp> [-- <exp> | ++ <exp> | ^^ <exp>]*"""
subexp, expr = splitAt( expr, multifind( expr, ['--', '++', '^^'] ) )
accum = setFromList( pattern( subexp ) )
while expr != '':
op, expr = splitAt( expr, 2 )
subexp, expr = splitAt( expr, multifind( expr, ['--', '++', '^^'] ) )
accum = doOp( op, accum, setFromList( pattern( subexp ) ) )
return iter( setToList( accum ) )
def expression( ps, joinStr=' ' ):
return zipGenerators( map( setExpression, ps ), joinStr )
def zipGenerators( ps, joinStr ):
"""Takes a list of string iterators and produces an iterator of strings joined by joinStr"""
while True:
yield joinStr.join( [p.next() for p in ps] )
def Pattern( ps ):
return expression( ps )
if __name__ == '__main__':
for result in expression( sys.argv[1:] ):
print "%s" % result