Package translate :: Package storage :: Module workflow
[hide private]
[frames] | no frames]

Source Code for Module translate.storage.workflow

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3  # 
  4  # Copyright 2010 Zuza Software Foundation 
  5  # 
  6  # This file is part of the Translate Toolkit. 
  7  # 
  8  # This program is free software; you can redistribute it and/or modify 
  9  # it under the terms of the GNU General Public License as published by 
 10  # the Free Software Foundation; either version 2 of the License, or 
 11  # (at your option) any later version. 
 12  # 
 13  # This program is distributed in the hope that it will be useful, 
 14  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 16  # GNU General Public License for more details. 
 17  # 
 18  # You should have received a copy of the GNU General Public License 
 19  # along with this program; if not, see <http://www.gnu.org/licenses/>. 
 20   
 21  """ 
 22  A workflow is defined by a set of states that a translation unit can be in and 
 23  the (allowed) transitions between these states. A state is defined by a range 
 24  between -128 and 127, indicating its level of "completeness". The range is 
 25  closed at the beginning and open at the end. That is, if a workflow contains 
 26  states A, B and C where A < B < C, a unit with state number n is in state A if 
 27  A <= n < B, state B if B <= n < C or state C if C <= n < MAX. 
 28   
 29  A value of 0 is typically the "empty" or "new" state with negative values 
 30  reserved for states like "obsolete" or "do not use". 
 31   
 32  Format specific workflows should be defined in such a way that the numeric 
 33  state values correspond to similar states. For example state 0 should be 
 34  "untranslated" in PO and "new" or "empty" in XLIFF, state 100 should be 
 35  "translated" in PO and "final" in XLIFF. This allows formats to implicitly 
 36  define similar states. 
 37  """ 
 38   
 39   
40 -class StateEnum:
41 """Only contains the constants for default states.""" 42 MIN = -128 43 OBSOLETE = -100 44 EMPTY = 0 45 NEEDS_WORK = 30 46 REJECTED = 60 47 NEEDS_REVIEW = 80 48 UNREVIEWED = 100 49 FINAL = 120 50 MAX = 127
51 52
53 -class State(object):
54
55 - def __init__(self, name, enter_action=None, leave_action=None):
56 self.name = name 57 self.enter_action = enter_action 58 self.leave_action = leave_action
59
60 - def __eq__(self, rhs):
61 return self.name == rhs.name
62
63 - def __repr__(self):
64 return '<State "%s">' % (self.name)
65
66 - def enter(self, obj):
67 if not self.enter_action or not callable(self.enter_action): 68 return 69 self.enter_action(obj)
70
71 - def leave(self, obj):
72 if not self.leave_action or not callable(self.leave_action): 73 return 74 self.leave_action(obj)
75 76
77 -class UnitState(State):
78
79 - def __init__(self, name, state_value):
80 self.state_value = state_value 81 super(UnitState, self).__init__(name, self._enter)
82
83 - def __repr__(self):
84 return '<UnitState name=%s value=%d>' % (self.name, self.state_value)
85
86 - def _enter(self, unit):
87 unit.set_state_n(self.state_value)
88 89
90 -class WorkflowError(Exception):
91 pass
92 93
94 -class NoInitialStateError(WorkflowError):
95 pass
96 97
98 -class TransitionError(WorkflowError):
99 pass
100 101
102 -class InvalidStateObjectError(WorkflowError):
103
104 - def __init__(self, obj):
105 super(InvalidStateObjectError, self).__init__('Invalid state object: %s' % (obj))
106 107
108 -class StateNotInWorkflowError(Exception):
109
110 - def __init__(self, state):
111 super(StateNotInWorkflowError, self).__init__( 112 'State not in workflow: %s' % (state))
113 114
115 -class Workflow(object):
116 117 # INITIALISERS #
118 - def __init__(self, wf_obj=None):
119 self._current_state = None 120 self._edges = [] 121 self._initial_state = None 122 self._states = [] 123 self._workflow_obj = wf_obj
124 125 126 # ACCESSORS #
127 - def _get_edges(self):
128 return list(self._edges)
129 edges = property(_get_edges) 130
131 - def _get_states(self):
132 return list(self._states)
133 states = property(_get_states) 134 135 136 # METHODS #
137 - def add_edge(self, from_state, to_state):
138 if isinstance(from_state, basestring): 139 from_state = self.get_state_by_name(from_state) 140 if isinstance(to_state, basestring): 141 to_state = self.get_state_by_name(to_state) 142 for s in (from_state, to_state): 143 if s not in self.states: 144 raise StateNotInWorkflowError(s) 145 if (from_state, to_state) in self.edges: 146 return # Edge already exists. Return quietly 147 148 self._edges.append((from_state, to_state))
149
150 - def add_state(self, state):
151 if not isinstance(state, State): 152 raise InvalidStateObjectError(state) 153 if state in self.states: 154 raise ValueError('State already in workflow: %s' % (state)) 155 self._states.append(state) 156 if self._initial_state is None: 157 self._initial_state = state
158
159 - def get_from_states(self):
160 """Returns a list of states that can be transitioned from to the 161 current state.""" 162 return [e[0] for e in self.edges if e[1] is self._current_state]
163
164 - def get_to_states(self):
165 """Returns a list of states that can be transitioned to from the 166 current state.""" 167 return [e[1] for e in self.edges if e[0] is self._current_state]
168
169 - def get_state_by_name(self, state_name):
170 """Get the C{State} object for the given name.""" 171 for s in self.states: 172 if s.name == state_name: 173 return s 174 else: 175 raise StateNotInWorkflowError(state_name)
176
177 - def set_current_state(self, state):
178 """Set the current state. This is absolute and not subject to edge 179 constraints. The current state's C{leave} and the new state's 180 C{enter} method is still called. For edge transitions, see the 181 C{trans} method.""" 182 if isinstance(state, basestring): 183 state = self.get_state_by_name(state) 184 if state not in self.states: 185 raise StateNotInWorkflowError(state) 186 187 if self._current_state: 188 self._current_state.leave(self._workflow_obj) 189 self._current_state = state 190 self._current_state.enter(self._workflow_obj)
191
192 - def set_initial_state(self, state):
193 """Sets the initial state, used by the L{reset} method.""" 194 if isinstance(state, basestring): 195 state = self.get_state_by_name(state) 196 if not isinstance(state, State): 197 raise InvalidStateObjectError(state) 198 if state not in self.states: 199 raise StateNotInWorkflowError(state) 200 self._initial_state = state
201
202 - def reset(self, wf_obj, init_state=None):
203 """Reset the work flow to the initial state using the given object.""" 204 self._workflow_obj = wf_obj 205 if init_state is not None: 206 if isinstance(init_state, basestring): 207 init_state = self.get_state_by_name(init_state) 208 if init_state not in self.states: 209 raise StateNotInWorkflowError() 210 self._initial_state = init_state 211 self._current_state = init_state 212 return 213 if self._initial_state is None: 214 raise NoInitialStateError() 215 self._current_state = None 216 self.set_current_state(self._initial_state)
217
218 - def trans(self, to_state=None):
219 """Transition to the given state. If no state is given, the first one 220 returned by C{get_to_states} is used.""" 221 if self._current_state is None: 222 raise ValueError('No current state set') 223 if isinstance(to_state, basestring): 224 to_state = self.get_state_by_name(to_state) 225 if to_state is None: 226 to_state = self.get_to_states() 227 if not to_state: 228 raise TransitionError('No state to transition to') 229 to_state = to_state[0] 230 if to_state not in self.states: 231 raise StateNotInWorkflowError(to_state) 232 if (self._current_state, to_state) not in self.edges: 233 raise TransitionError('No edge between edges %s and %s' % (self._current_state, to_state)) 234 self._current_state.leave(self._workflow_obj) 235 self._current_state = to_state 236 self._current_state.enter(self._workflow_obj)
237 238
239 -def create_unit_workflow(unit, state_names):
240 wf = Workflow(unit) 241 242 state_info = unit.STATE.items() 243 state_info.sort(key=lambda x: x[0]) 244 245 init_state, prev_state = None, None 246 for state_id, state_range in state_info: 247 if state_range[0] < 0: 248 continue 249 state_name = state_names[state_id] 250 # We use the low end value below, because the range is closed there 251 state = UnitState(state_name, state_range[0]) 252 wf.add_state(state) 253 254 # Use the first non-negative state as the initial state... 255 if init_state is None and state_range[0] >= 0: 256 init_state = state 257 258 if prev_state: 259 wf.add_edge(prev_state, state_name) 260 prev_state = state_name 261 262 if init_state: 263 wf.set_initial_state(init_state) 264 265 return wf
266