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