Source code elsie/slides/show.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import re


def replace_relative_steps(match, current_max):
    item = match.get("from")
    if item:
        if item in ("next", "last") and current_max is None:
            raise Exception(
                "You have to pass the current maximum fragment if you use relative values"
            )

        if item == "next":
            match["from"] = str(current_max + 1)
        if item == "last":
            match["from"] = str(current_max)


class ShowInfo:
    parser = re.compile(r"^(?P<from>\d+|next|last)(?:(?P<open>\+)|-(?P<end>\d+))?$")

    def __init__(self, steps=None, open_step=None, min_steps=None):
        if steps is None:
            if open_step is None:
                open_step = 1
            steps = ()
        self.steps = tuple(steps)
        self.open_step = open_step

        if min_steps:
            self._min_steps = min_steps
        else:
            self._min_steps = self.max_step()

    @classmethod
    def from_label(cls, label):
        if label is None or "**" not in label:
            return None
        show = label.split("**", 2)[1]
        return cls.parse(show)

    @classmethod
    def parse(cls, obj, current_max=None):
        if obj is None:
            return ShowInfo()
        if isinstance(obj, int):
            return ShowInfo((obj,))
        if isinstance(obj, str):
            steps = set()
            open_step = None
            intervals = [i.strip() for i in obj.split(",")]
            if not intervals:
                raise Exception("Invalid format of 'show' string: {!r}".format(obj))

            for interval in intervals:
                m = cls.parser.match(interval)
                if m is None:
                    raise Exception("Invalid format of 'show' string: {!r}".format(obj))
                m = m.groupdict()
                replace_relative_steps(m, current_max)
                if m["open"] is not None:
                    if open_step is not None:
                        raise Exception(
                            "Multiple open steps ({}, {}) in input {!r}".format(
                                open_step, m["open"], obj
                            )
                        )
                    open_step = int(m["from"])
                    continue

                start = int(m["from"])
                end = start
                if m["end"] is not None:
                    end = int(m["end"])
                assert start <= end
                steps.update(range(start, end + 1))
            return ShowInfo(sorted(steps), open_step)
        else:
            raise Exception("Invalid show argument")

    def min_steps(self):
        return self._min_steps

    def max_step(self):
        return max(max(self.steps, default=1), self.open_step if self.open_step else 1)

    def ensure_steps(self, steps):
        min_steps = max(self._min_steps, steps)
        return ShowInfo(self.steps, self.open_step, min_steps)

    def is_visible(self, step):
        return step in self.steps or (
            self.open_step is not None and self.open_step <= step
        )

    def __repr__(self):
        return "<ShowInfo steps={} open_step={} min={}>".format(
            self.steps, self.open_step, self._min_steps
        )