139 lines
3.3 KiB
Python
139 lines
3.3 KiB
Python
|
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)
|