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