Offer-motivated features: :hline defaults, ranged :hpos, @date :days, :bottom none
- @table: a writer's :hline/:vline replaces the default lines; new
boundary name 'none' removes all lines
- @table: ranged :hpos argument overrides :cell_hpos per cell
(\multicolumn{1} in tex; positions a colspan anchor's merged cell)
- @date/@datetime: :days offset argument (sks/date/date.py)
- @document: ":bottom none" suppresses the footer (\pagestyle{empty})
Also carries the escape-system generator/renderer fixes, per-cell-range
:format, and uppercase .TTF/.OTF font recognition from klammertext-dev.
This commit is contained in:
@@ -25,10 +25,16 @@ index range "2-" (to the last index), or a name defined by the argument
|
||||
commas; each is an index "4", a closed range "1-4", or an open range "6-"
|
||||
(to the end). All indices are zero-origin.
|
||||
|
||||
A negative index counts from the end, as in Python: -1 is the last index,
|
||||
-2 the second to last, and so on. Ranges are inclusive, so "1--2" is index
|
||||
1 through the second-to-last index (the last index excluded).
|
||||
|
||||
Examples:
|
||||
3 index 3, full extent
|
||||
2-5(0-2) indices 2 through 5, each restricted to 0 through 2
|
||||
3(1-4,6-9) index 3, restricted to 1-4 and 6-9
|
||||
-1 the last index
|
||||
1--2 index 1 through the second-to-last index
|
||||
head(1-) with table hline names: boundary 1, from column 1 on
|
||||
""".strip()
|
||||
|
||||
@@ -38,8 +44,13 @@ class Range_error(Exception):
|
||||
super().__init__(f"{message}\n\n{syntax_description}")
|
||||
|
||||
|
||||
item_rgx = re.compile(r"(?:(\d+)(-)?(\d*)|([A-Za-z]+))(?:\(([\d,\-]+)\))?$")
|
||||
subset_rgx = re.compile(r"(\d+)(-)?(\d*)$")
|
||||
# A numeric selector is a signed integer, optionally followed by a range
|
||||
# part: a separating hyphen and an optional signed end index (empty end =
|
||||
# open range). The leading sign lets an index count from the end (-1 is
|
||||
# the last), matching Python list indexing. The separating hyphen never
|
||||
# collides with a minus sign because \d+ never consumes it.
|
||||
item_rgx = re.compile(r"(?:(-?\d+)(-(-?\d+)?)?|([A-Za-z]+))(?:\(([-\d,]+)\))?$")
|
||||
subset_rgx = re.compile(r"(-?\d+)(-(-?\d+)?)?$")
|
||||
|
||||
|
||||
def hline_names(count):
|
||||
@@ -49,7 +60,8 @@ def hline_names(count):
|
||||
"head": [1],
|
||||
"bottom": [last],
|
||||
"inner": list(range(1, last)),
|
||||
"all": list(range(count))}
|
||||
"all": list(range(count)),
|
||||
"none": []}
|
||||
|
||||
|
||||
def vline_names(count):
|
||||
@@ -57,7 +69,8 @@ def vline_names(count):
|
||||
last = count - 1
|
||||
return {"outer": [0, last],
|
||||
"inner": list(range(1, last)),
|
||||
"all": list(range(count))}
|
||||
"all": list(range(count)),
|
||||
"none": []}
|
||||
|
||||
|
||||
class Indexed_range:
|
||||
@@ -126,31 +139,36 @@ class Indexed_ranges:
|
||||
argument = f"{self.argument} argument: " if self.argument else ""
|
||||
raise Range_error(f"{argument}{message}")
|
||||
|
||||
def normalize(self, raw, spec, count):
|
||||
"""Resolve a possibly-negative index to 0..count-1 (Python-style):
|
||||
a negative index counts from the end (-1 is the last)."""
|
||||
i = raw + count if raw < 0 else raw
|
||||
if not 0 <= i < count:
|
||||
self.error(f'In "{spec}", index {raw} is out of range '
|
||||
f"(0 through {count - 1}, or -1 through -{count}).")
|
||||
return i
|
||||
|
||||
def parse(self, spec):
|
||||
match = item_rgx.match(spec)
|
||||
if not match:
|
||||
self.error(f'"{spec}" is not a valid indexed_range.')
|
||||
number, hyphen, end, name, subsets = match.groups()
|
||||
number, range_part, end, name, subsets = match.groups()
|
||||
if name is not None:
|
||||
if name not in self.names:
|
||||
known = " ".join(self.names) or "none"
|
||||
self.error(f'"{name}" is not a valid name here '
|
||||
f"(valid names: {known}).")
|
||||
indices = self.names[name]
|
||||
elif range_part is None:
|
||||
indices = [self.normalize(int(number), spec, self.count)]
|
||||
else:
|
||||
start = int(number)
|
||||
if not hyphen:
|
||||
indices = [start]
|
||||
else:
|
||||
last = int(end) if end else self.count - 1
|
||||
if start > last:
|
||||
self.error(f'In "{spec}", the index range start {start} '
|
||||
f"is greater than its end {last}.")
|
||||
indices = list(range(start, last + 1))
|
||||
for i in indices:
|
||||
if i >= self.count:
|
||||
self.error(f'In "{spec}", index {i} is out of range '
|
||||
f"(0 through {self.count - 1}).")
|
||||
first = self.normalize(int(number), spec, self.count)
|
||||
last = (self.normalize(int(end), spec, self.count)
|
||||
if end else self.count - 1)
|
||||
if first > last:
|
||||
self.error(f'In "{spec}", the index range start {first} '
|
||||
f"is greater than its end {last}.")
|
||||
indices = list(range(first, last + 1))
|
||||
ranges = self.parse_subsets(spec, subsets) if subsets else None
|
||||
for i in indices:
|
||||
entry = self.by_index.setdefault(i, Indexed_range(i, self.maxval))
|
||||
@@ -160,23 +178,24 @@ class Indexed_ranges:
|
||||
entry.add_ranges(ranges)
|
||||
|
||||
def parse_subsets(self, spec, subsets):
|
||||
# Subset indices run 0..maxval inclusive, so their count is
|
||||
# maxval + 1 and a negative subset index resolves against it.
|
||||
count = self.maxval + 1
|
||||
ranges = []
|
||||
for part in subsets.split(","):
|
||||
match = subset_rgx.match(part)
|
||||
if not match:
|
||||
self.error(f'In "{spec}", "{part}" is not a valid subset.')
|
||||
number, hyphen, end = match.groups()
|
||||
start = int(number)
|
||||
if not hyphen:
|
||||
last = start
|
||||
number, range_part, end = match.groups()
|
||||
if range_part is None:
|
||||
start = last = self.normalize(int(number), spec, count)
|
||||
else:
|
||||
last = int(end) if end else self.maxval
|
||||
start = self.normalize(int(number), spec, count)
|
||||
last = (self.normalize(int(end), spec, count)
|
||||
if end else self.maxval)
|
||||
if start > last:
|
||||
self.error(f'In "{spec}", the subset start {start} '
|
||||
f"is greater than its end {last}.")
|
||||
if last > self.maxval:
|
||||
self.error(f'In "{spec}", {last} is out of range '
|
||||
f"(0 through {self.maxval}).")
|
||||
ranges.append([start, last])
|
||||
return ranges
|
||||
|
||||
|
||||
Reference in New Issue
Block a user