voussoirkit/voussoirkit/fusker.py
2020-01-31 20:53:29 -08:00

140 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)