summaryrefslogtreecommitdiff
path: root/python/subunit/progress_model.py
blob: f1ed7af8729a826d1ff5695f072fbb431444a277 (plain)
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
99
100
101
102
103
104
105
106
107
108
109
#
#  subunit: extensions to Python unittest to get test results from subprocesses.
#  Copyright (C) 2009  Robert Collins <robertc@robertcollins.net>
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

"""Support for dealing with progress state."""

class ProgressModel(object):
    """A model of progress indicators as subunit defines it.
    
    Instances of this class represent a single logical operation that is
    progressing. The operation may have many steps, and some of those steps may
    supply their own progress information. ProgressModel uses a nested concept
    where the overall state can be pushed, creating new starting state, and
    later pushed to return to the prior state. Many user interfaces will want
    to display an overall summary though, and accordingly the pos() and width()
    methods return overall summary information rather than information on the
    current subtask.

    The default state is 0/0 - indicating that the overall progress is unknown.
    Anytime the denominator of pos/width is 0, rendering of a ProgressModel
    should should take this into consideration.

    :ivar: _tasks. This private attribute stores the subtasks. Each is a tuple:
        pos, width, overall_numerator, overall_denominator. The overall fields
        store the calculated overall numerator and denominator for the state
        that was pushed.
    """

    def __init__(self):
        """Create a ProgressModel.
        
        The new model has no progress data at all - it will claim a summary
        width of zero and position of 0.
        """
        self._tasks = []
        self.push()

    def adjust_width(self, offset):
        """Adjust the with of the current subtask."""
        self._tasks[-1][1] += offset

    def advance(self):
        """Advance the current subtask."""
        self._tasks[-1][0] += 1

    def pop(self):
        """Pop a subtask off the ProgressModel.

        See push for a description of how push and pop work.
        """
        self._tasks.pop()

    def pos(self):
        """Return how far through the operation has progressed."""
        if not self._tasks:
            return 0
        task = self._tasks[-1]
        if len(self._tasks) > 1:
            # scale up the overall pos by the current task or preserve it if
            # no current width is known.
            offset = task[2] * (task[1] or 1)
        else:
            offset = 0
        return offset + task[0]

    def push(self):
        """Push a new subtask.

        After pushing a new subtask, the overall progress hasn't changed. Calls
        to adjust_width, advance, set_width will only after the progress within
        the range that calling 'advance' would have before - the subtask
        represents progressing one step in the earlier task.

        Call pop() to restore the progress model to the state before push was
        called.
        """
        self._tasks.append([0, 0, self.pos(), self.width()])

    def set_width(self, width):
        """Set the width of the current subtask."""
        self._tasks[-1][1] = width

    def width(self):
        """Return the total width of the operation."""
        if not self._tasks:
            return 0
        task = self._tasks[-1]
        if len(self._tasks) > 1:
            # scale up the overall width by the current task or preserve it if
            # no current width is known.
            return task[3] * (task[1] or 1)
        else:
            return task[1]