"""Kat script utility functions."""
from collections import defaultdict
from itertools import groupby
[docs]def index_ranges(intlist):
"""Create a sequence of range strings from a sequence of integers.
Based on https://stackoverflow.com/a/9471386/2251982.
Parameters
----------
intlist : sequence
Integers to convert to range strings.
Yields
------
str
The next range string.
Examples
--------
>>> list(index_ranges([1, 2, 4, 5, 6, 7, 9]))
["1-2", "4-7", "9"]
"""
def sub(x):
return x[1] - x[0]
for _, iterable in groupby(enumerate(sorted(intlist)), sub):
indices = list(iterable)
if len(indices) == 1:
yield str(indices[0][1])
else:
yield f"{indices[0][1]}-{indices[-1][1]}"
[docs]def scriptsorted(items, reverse=False):
"""Sort container items by their script position.
Parameters
----------
items : sequence of :class:`.TokenContainer`
The container items to sort.
reverse : bool, optional
Request the result in descending instead of ascending order.
Returns
-------
list of :class:`.TokenContainer`
Container items sorted by line number then column.
"""
return sorted(items, key=lambda item: item.start, reverse=reverse)
[docs]def duplicates(items, key=None):
"""Get duplicate keys and values in the 1D sequence `items`.
Parameters
----------
items : sequence
The sequence to find duplicates in.
key : callable, optional
The key function to use for comparisons. If not specified, defaults to the
identity function.
Returns
-------
list
The duplicates in `items`. This is a sequence of tuples containing the result
of the key function for each entry of `items`, where at least two such keys
exist, and the original items that matched that `key`.
Examples
--------
>>> [k for k, _ in duplicates("AAAABBCDAAB")]
["A", "B", "C", "D"]
>>> [list(g) for _, g in duplicates("AAAABBCDAAB")]
[["A", "A", "A", "A", "A", "A"], ["B", "B", "B"], ["C"], ["D"]]
"""
if key is None:
key = lambda item: item
groups = defaultdict(list)
for item in items:
groups[key(item)].append(item)
return [(k, v) for k, v in groups.items() if len(v) > 1]
[docs]def merge_attributes(attr1, attr2):
"""Perform a deep attribute dictionary merge.
This merges list and tuples in `attr1` and `attr2` into one dict. Values that are
not lists or tuples result in an error. Items in `attr1` appear before those in
`attr2` where they share the same key.
Parameters
----------
attr1, attr2 : dict
The attribute dictionaries to merge.
Returns
-------
dict
The merged attribute dictionary.
Raises
------
ValueError
If a dict value in `attr1` or `attr2` is not a list or tuple.
"""
out = {}
if attr2 is None:
secondkeys = set()
else:
secondkeys = set(attr2)
# Add keys in attr1 and optionally attr2.
for key, value in attr1.items():
if isinstance(value, list):
value = [*value, *attr2.get(key, [])]
elif isinstance(value, tuple):
value = (*value, *attr2.get(key, ()))
elif key in attr2:
raise ValueError(
f"don't know how to merge {type(value)} and {type(attr2[key])}"
)
out[key] = value
secondkeys.discard(key)
# Add keys only in attr2.
for key in secondkeys:
out[key] = attr2[key]
return out