Kanban example

This commit is contained in:
lash 2022-02-09 19:20:59 +00:00
parent d074174f37
commit 9f71401bb5
Signed by: lash
GPG Key ID: 21D2E7BB88C2A746
2 changed files with 84 additions and 11 deletions

64
example/kanban.py Normal file
View File

@ -0,0 +1,64 @@
from shep.state import State
# we don't like "NEW" as the default label for a new item in the queue, so we change it to BACKLOG
State.set_default_state('backlog')
# define all the valid states
st = State(5)
st.add('pending')
st.add('blocked')
st.add('doing')
st.add('review')
st.add('finished')
# define a couple of states that give a bit more context to progress; something is blocked before starting development or something is blocked during development...
st.alias('startblock', st.BLOCKED, st.PENDING)
st.alias('doingblock', st.BLOCKED, st.DOING)
# create the foo key which will forever languish in backlog
k = 'foo'
st.put(k)
foo_state = st.state(k)
foo_state_name = st.name(foo_state)
foo_contents_r = st.get('foo')
print('{} {} {}'.format(k, foo_state_name, foo_contents_r))
# Create bar->baz and advance it from backlog to pending
k = 'bar'
bar_contents = 'baz'
st.put(k, contents=bar_contents)
st.next(k)
bar_state = st.state(k)
bar_state_name = st.name(bar_state)
bar_contents_r = st.get('bar')
print('{} {} {}'.format(k, bar_state_name, bar_contents_r))
# Create inky->pinky and move to doing then doing-blocked
k = 'inky'
inky_contents = 'pinky'
st.put(k, contents=inky_contents)
inky_state = st.state(k)
st.move(k, st.DOING)
st.set(k, st.BLOCKED)
inky_state = st.state(k)
inky_state_name = st.name(inky_state)
inky_contents_r = st.get('inky')
print('{} {} {}'.format(k, inky_state_name, bar_contents_r))
# then replace the content
# note that replace could potentially mean some VCS below
inky_new_contents = 'blinky'
st.replace(k, inky_new_contents)
inky_contents_r = st.get('inky')
print('{} {} {}'.format(k, inky_state_name, inky_contents_r))
# so now move to review
st.move(k, st.REVIEW)
inky_state = st.state(k)
inky_state_name = st.name(inky_state)
print('{} {} {}'.format(k, inky_state_name, inky_contents_r))

View File

@ -19,18 +19,27 @@ class State:
:param logger: Standard library logging instance to output to :param logger: Standard library logging instance to output to
:type logger: logging.Logger :type logger: logging.Logger
""" """
base_state_name = 'NEW'
def __init__(self, bits, logger=None): def __init__(self, bits, logger=None):
self.__bits = bits self.__bits = bits
self.__limit = (1 << bits) - 1 self.__limit = (1 << bits) - 1
self.__c = 0 self.__c = 0
self.NEW = 0 setattr(self, self.base_state_name, 0)
#self.NEW = 0
self.__reverse = {0: self.NEW} self.__reverse = {0: getattr(self, self.base_state_name)}
self.__keys = {self.NEW: []} self.__keys = {getattr(self, self.base_state_name): []}
self.__keys_reverse = {} self.__keys_reverse = {}
self.__contents = {} self.__contents = {}
@classmethod
def set_default_state(cls, state_name):
cls.base_state_name = state_name.upper()
# return true if v is a single-bit state # return true if v is a single-bit state
def __is_pure(self, v): def __is_pure(self, v):
if v == 0: if v == 0:
@ -197,7 +206,7 @@ class State:
:return: State name :return: State name
""" """
if v == None or v == 0: if v == None or v == 0:
return 'NEW' return self.base_state_name
k = self.__reverse.get(v) k = self.__reverse.get(v)
if k == None: if k == None:
raise StateInvalid(v) raise StateInvalid(v)
@ -252,13 +261,13 @@ class State:
def put(self, key, state=None, contents=None): def put(self, key, state=None, contents=None):
"""Add a key to an existing state. """Add a key to an existing state.
If no state it specified, the default state attribute "NEW" will be used. If no state it specified, the default state attribute State.base_state_name will be used.
Contents may be supplied as value to pair with the given key. Contents may be changed later by calling the `replace` method. Contents may be supplied as value to pair with the given key. Contents may be changed later by calling the `replace` method.
:param key: Content key to add :param key: Content key to add
:type key: str :type key: str
:param state: Initial state for the put. If not given, initial state will be NEW :param state: Initial state for the put. If not given, initial state will be State.base_state_name
:type state: int :type state: int
:param contents: Contents to associate with key. A valie of None should be recognized as an undefined value as opposed to a zero-length value throughout any backend :param contents: Contents to associate with key. A valie of None should be recognized as an undefined value as opposed to a zero-length value throughout any backend
:type contents: str :type contents: str
@ -268,7 +277,7 @@ class State:
:return: Resulting state that key is put under (should match the input state) :return: Resulting state that key is put under (should match the input state)
""" """
if state == None: if state == None:
state = self.NEW state = getattr(self, self.base_state_name)
elif self.__reverse.get(state) == None: elif self.__reverse.get(state) == None:
raise StateInvalid(state) raise StateInvalid(state)
self.__check_key(key) self.__check_key(key)
@ -351,13 +360,13 @@ class State:
def unset(self, key, not_state): def unset(self, key, not_state):
"""Unset a single bit, moving to a pure or alias state. """Unset a single bit, moving to a pure or alias state.
The resulting state cannot be NEW (0). The resulting state cannot be State.base_state_name (0).
:param key: Content key to modify state for :param key: Content key to modify state for
:type key: str :type key: str
:param or_state: Atomic stat to add :param or_state: Atomic stat to add
:type or_state: int :type or_state: int
:raises ValueError: State is not a single bit state, or attempts to revert to NEW :raises ValueError: State is not a single bit state, or attempts to revert to State.base_state_name
:raises StateItemNotFound: Content key is not registered :raises StateItemNotFound: Content key is not registered
:raises StateInvalid: Resulting state after addition of atomic state is unknown :raises StateInvalid: Resulting state after addition of atomic state is unknown
:rtype: int :rtype: int
@ -374,8 +383,8 @@ class State:
if to_state == current_state: if to_state == current_state:
raise ValueError('invalid change for state {}: {}'.format(key, not_state)) raise ValueError('invalid change for state {}: {}'.format(key, not_state))
if to_state == self.NEW: if to_state == getattr(self, self.base_state_name):
raise ValueError('State {} for {} cannot be reverted to NEW'.format(current_state, key)) raise ValueError('State {} for {} cannot be reverted to {}'.format(current_state, key, self.base_state_name))
new_state = self.__reverse.get(to_state) new_state = self.__reverse.get(to_state)
if new_state == None: if new_state == None: