112 lines
3.4 KiB
Python
112 lines
3.4 KiB
Python
|
|
import sys
|
||
|
|
import re
|
||
|
|
|
||
|
|
syntax_description = """
|
||
|
|
|
||
|
|
A "span" is an integer (the "index") followed by an optional description of one
|
||
|
|
or more sequence subsets. A subset is defined by a series of subset
|
||
|
|
descriptions, separated by a comma. A subset description is either an integer,
|
||
|
|
two integers separated by a hypen to indicate a range, or an integer followed
|
||
|
|
only by a hyphen, which will include all the following elements of the span to
|
||
|
|
the end. No spaces are allowed in a span. All indices are zero-origin.
|
||
|
|
|
||
|
|
Span examples for an index of "3":
|
||
|
|
3
|
||
|
|
3(1)
|
||
|
|
3(0-4)
|
||
|
|
3(1-4,6-9)
|
||
|
|
3(5-)
|
||
|
|
|
||
|
|
Note that some sequence subsets must include two numbers, for example, border
|
||
|
|
lines in a table.
|
||
|
|
|
||
|
|
""".strip()
|
||
|
|
|
||
|
|
|
||
|
|
class Span:
|
||
|
|
def __init__(self, count, spec):
|
||
|
|
def parse_range(match):
|
||
|
|
start, hyphen, end = match.groups()
|
||
|
|
if hyphen is None and end is None:
|
||
|
|
end = start
|
||
|
|
elif end is None:
|
||
|
|
end = count - 1
|
||
|
|
return [int(start), int(end)]
|
||
|
|
self.all = True
|
||
|
|
subset_pat = "[-\\d,]+"
|
||
|
|
span_rgx = re.compile(f"(\d+)(\({subset_pat}\))*")
|
||
|
|
match = span_rgx.match(spec)
|
||
|
|
self.spec = spec
|
||
|
|
if match and match.group(0) == spec:
|
||
|
|
range_rgx = re.compile("(\d+)(-)?(\d+)?")
|
||
|
|
self.index = int(match.group(1))
|
||
|
|
if (match.group(2)):
|
||
|
|
self.subsets = match.group(2).strip("()").split(",")
|
||
|
|
matches = [range_rgx.match(e) for e in self.subsets]
|
||
|
|
self.ranges = [parse_range(e) if e else None for e in matches]
|
||
|
|
self.all = False
|
||
|
|
else:
|
||
|
|
self.ranges = [[0, count-1]]
|
||
|
|
else:
|
||
|
|
print(f'The span specification "{spec}" is incorrect.\n\n{syntax_description}\n')
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
def __str__(self):
|
||
|
|
subsets = "all" if self.all else ",".join([f"{e[0]}-{e[1]}" for e in self.ranges])
|
||
|
|
return f"{self.index}[{subsets}]"
|
||
|
|
|
||
|
|
def __repr__(self):
|
||
|
|
return self.__str__()
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
class Spanset:
|
||
|
|
def __init__(self, count, span_specs):
|
||
|
|
self.count = count
|
||
|
|
self.spans = {}
|
||
|
|
for specs in span_specs.split():
|
||
|
|
for spec in self.parse_spec(specs):
|
||
|
|
print("Spanset spec:", spec)
|
||
|
|
self.spans[spec.index] = spec
|
||
|
|
|
||
|
|
|
||
|
|
def parse_spec(self, spec):
|
||
|
|
named_spec = {"top" : ["0"],
|
||
|
|
"bottom" : [str(self.count-1)],
|
||
|
|
"head" : ["1"],
|
||
|
|
"outer" : ["0", str(self.count-1)],
|
||
|
|
"inner" : [str(e) for e in range(1,self.count-1)],
|
||
|
|
"all" : [str(e) for e in range(0,self.count+1)]
|
||
|
|
}.get(spec)
|
||
|
|
if named_spec is None:
|
||
|
|
return [Span(self.count, spec)]
|
||
|
|
else:
|
||
|
|
return [Span(self.count, e) for e in named_spec]
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
import pprint
|
||
|
|
for s in [
|
||
|
|
Span(10, "0"),
|
||
|
|
Span(10, "1"),
|
||
|
|
Span(10, "2(2)"),
|
||
|
|
Span(10, "3(2-)"),
|
||
|
|
Span(10, "4(2-4)"),
|
||
|
|
Span(10, "5(2-4)"),
|
||
|
|
Span(10, "6(2-4,6)"),
|
||
|
|
Span(10, "7(2,6-8)"),
|
||
|
|
Span(10, "8(2,7-8,9-11,13-14)")]:
|
||
|
|
print(s.spec, "->", s)
|
||
|
|
|
||
|
|
print("Spanset")
|
||
|
|
S = Spanset(10, "1")
|
||
|
|
for name in "top bottom head outer inner all 1(2-3)".split():
|
||
|
|
print(name, "->", S.parse_spec(name))
|
||
|
|
|
||
|
|
print("Instantiate:")
|
||
|
|
s = Spanset(10, "3(1-2,4-5)")
|
||
|
|
print(s.tex_hline(3))
|
||
|
|
|
||
|
|
|
||
|
|
|