''' Fusking is the act of generating many strings by using a template with a range of integers or a spinner of alternate strings. Ranges: x[1-10]y -> x1y, x2y, x3y, x4y, x5y, x6y, x7y, x8y, x9y, x10y x[01-10]y -> x01y, x02y, x03y, x04y, x05y, x06y, x07y, x08y, x09y, x10y Spinners: x{alpha|beta|charlie}y -> xalphay, xbetay, xcharliey fusker.fusker('https://subdomain-{a|b|c}.website.com/image[01-99].jpg') -> ( 'https://subdomain-a.website.com/image01.jpg', 'https://subdomain-a.website.com/image02.jpg', 'https://subdomain-a.website.com/image03.jpg', ... 'https://subdomain-a.website.com/image99.jpg', 'https://subdomain-b.website.com/image01.jpg', 'https://subdomain-b.website.com/image02.jpg', ) ''' import collections import itertools import string import sys from voussoirkit import basenumber class Landmark: def __init__(self, opener, closer, parser): self.opener = opener self.closer = closer self.parser = parser def barsplit(chars): wordlist = [] wordbuff = [] def flush(): if not wordbuff: return word = fusk_join(wordbuff) wordlist.append(word) wordbuff.clear() for item in chars: if item == '|': flush() else: wordbuff.append(item) flush() return wordlist def fusk_join(items): form = '' fusks = [] result = [] for item in items: if isinstance(item, str): form += item else: form += '{}' fusks.append(item) product = itertools.product(*fusks) for group in product: f = form.format(*group) result.append(f) return result def fusk_spinner(items): for item in items: if isinstance(item, str): yield item else: yield from item def parse_spinner(characters): words = barsplit(characters) spinner = fusk_spinner(words) return spinner def fusk_range(lo, hi, padto=0, base=10, lower=False): for x in range(lo, hi+1): x = basenumber.to_base(x, base) x = x.rjust(padto, '0') if lower: x = x.lower() yield x def parse_range(characters): r = ''.join(characters) (lo, hi) = r.split('-') lo = lo.strip() hi = hi.strip() lowers = string.digits + string.ascii_lowercase uppers = string.digits + string.ascii_uppercase lohi = lo + hi lower = False if all(c in string.digits for c in lohi): base = 10 elif all(c in lowers for c in lohi): lower = True base = 36 elif all(c in uppers for c in lohi): base = 36 else: base = 62 if (not lo) or (not hi): raise ValueError('Invalid range', r) if len(lo) > 1 and lo.startswith('0'): padto = len(lo) if len(hi) != padto: raise ValueError('Inconsistent padding', lo, hi) else: padto = 0 lo = basenumber.from_base(lo, base) hi = basenumber.from_base(hi, base) frange = fusk_range(lo, hi, padto=padto, base=base, lower=lower) return frange landmarks = { '{': Landmark('{', '}', parse_spinner), '[': Landmark('[', ']', parse_range), } def fusker(fstring, landmark=None, depth=0): escaped = False result = [] buff = [] if isinstance(fstring, str): fstring = collections.deque(fstring) while fstring: character = fstring.popleft() if escaped: buff.append('\\' + character) escaped = False elif character == '\\': escaped = True elif landmark and character == landmark.closer: buff = [landmark.parser(buff)] break elif character in landmarks: subtotal = fusker(fstring, landmark=landmarks[character]) buff.extend(subtotal) else: buff.append(character) if not landmark: buff = parse_spinner(buff) return buff return result if __name__ == '__main__': pattern = sys.argv[1] fusk = fusker(pattern) for result in fusk: print(result)