├── .gitignore ├── LICENSE ├── README.md ├── RELEASE_NOTES ├── app ├── __init__.py ├── administration │ ├── __init__.py │ └── statemachine.py └── common │ ├── __init__.py │ ├── command.py │ ├── task.py │ └── taskwarrior.py ├── kanban-warrior.py └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * ----------------------------------------------------------------------------------------------- 3 | * "THE APPRECIATION LICENSE" (Revision 0xFF): 4 | * Copyright (c) 2013 M. Joosten 5 | * You can do anything with this program and its code, even use it to run a nuclear reactor (why should you) 6 | But I won't be responsible for possible damage and mishap, because i never tested it on a nuclear reactor (why should I..) 7 | If you think this program/code is absolutely great and supercalifragilisticexpialidocious (or just plain useful), just 8 | let me know by sending me a nice email or postcard from your country of origin and leave this header in the code 9 | See my blog (http://keigezellig.blog.com), for contact info 10 | * --------------------------------------------------------------------------------------------------- 11 | */ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | kanbanwarrior 2 | ============= 3 | 4 | A python script supporting my kanban workflow for Task Warrior. 5 | For more info about the workflow itself see http://blog.joosten-industries.nl/2013/03/07/kanban-warrior/ 6 | 7 | Installation 8 | ------------ 9 | - Make sure Python 2.7.x is installed. (see http://wiki.python.org/moin/BeginnersGuide/Download) 10 | - Make sure Taskwarrior 2.1.2 or higher is installed. (see http://taskwarrior.org/projects/taskwarrior/wiki/Download) 11 | - Download the scripts by cloning this repository (if you have a git client) or click the link above to download it as an archive. 12 | - Unzip or copy the downloaded files to a directory of your choice (preferably on your path) 13 | - (optional) Set execute bit of the main kanban-warrior.py script by executing: chmod kanban-warrior.py u+x, if you don't want to type 'python' everytime when you 14 | execute a command. 15 | - Setting up TaskWarrior: 16 | task config journal.time=on 17 | task config dateformat.annotation=d-m-Y H:N 18 | task config dateformat=d-m-Y H:N 19 | task config dateformat.report=d-m-Y H:N 20 | task config dateformat.edit=d-m-Y H:N 21 | task config dateformat.info=d-m-Y H:N 22 | task config xterm.title=on 23 | Of course you can use another date format if you want by replacing the d-m-Y. See the Task Warrior website for more information about this 24 | 25 | 26 | 27 | Short usage summary 28 | ------------------- 29 | 30 | Commands 31 | -------- 32 | - Add task to backlog: kanban-warrior addtobacklog [projectname.storyname] [taskdescription] [priority] 33 | - Add task to In Progress: kanban-warrior addtowip [taskid] 34 | - Start task: kanban-warrior start [taskid] 35 | - Stop task: kanban-warrior stop [taskid] 36 | - Set task on hold: kanban-warrior hold [taskid] [reason] 37 | - Finish a task: kanban-warrior finish [taskid] 38 | 39 | Reports 40 | -------- 41 | - List backlog: kanban-warrior list backlog [projectname] 42 | - List work in progress: kanban-warriot list wip [projectname] 43 | - List finished work: kanban-warrior list done [projectname] 44 | - List work in progress: kanban-warrior list onhold [projectname] 45 | 46 | 47 | -------------------------------------------------------------------------------- /RELEASE_NOTES: -------------------------------------------------------------------------------- 1 | v1.0 2 | ---- 3 | - It's possible to administer tasks and projects. 4 | - Assigning a priority with the addbacklog command doesn't work yet 5 | - Only works under Linux/Unix 6 | - Path to the TaskWarrior binary is hardcoded in the application to /usr/bin/ -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keigezellig/kanbanwarrior/0bad4ba4c65783e7f1de24933c48da9533eaab9c/app/__init__.py -------------------------------------------------------------------------------- /app/administration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keigezellig/kanbanwarrior/0bad4ba4c65783e7f1de24933c48da9533eaab9c/app/administration/__init__.py -------------------------------------------------------------------------------- /app/administration/statemachine.py: -------------------------------------------------------------------------------- 1 | from app.common.task import * 2 | from app.common.taskwarrior import * 3 | 4 | __version__ = '1.0' 5 | 6 | class TransitionError(Exception): 7 | """Raised when an operation attempts a state transition that's not 8 | allowed. 9 | 10 | Attributes: 11 | prev -- state at beginning of transition 12 | next -- attempted new state 13 | msg -- explanation of why the specific transition is not allowed 14 | """ 15 | 16 | def __init__(self, prev, next, msg): 17 | self.prev = prev 18 | self.next = next 19 | self.msg = msg 20 | 21 | def __str__(self): 22 | return self.msg 23 | 24 | class StateMachine: 25 | pathToTaskWarrior = None 26 | 27 | def __init__(self, pathToTW): 28 | self.pathToTaskWarrior = pathToTW; 29 | 30 | def addToWip(self, task): 31 | if (task.state != States.BACKLOG) and (task.state != States.ONHOLD): 32 | raise TransitionError(task.state, States.INPROGRESS_INACTIVE, "Task must be in backlog or on hold") 33 | 34 | self.__taskwarriorAddToWip(task) 35 | 36 | 37 | 38 | def __taskwarriorAddToWip(self, task): 39 | # Command line is: task modify +inprogress -backlog|-onhold 40 | verb = "-backlog" 41 | 42 | if (task.state == States.ONHOLD): 43 | verb = "-onhold" 44 | 45 | subprocess.call([self.pathToTaskWarrior, task.taskid.__str__(), 'modify', '+inprogress', verb ]) 46 | 47 | 48 | def start(self, task): 49 | if (task.state != States.BACKLOG) and (task.state != States.ONHOLD) and (task.state != States.INPROGRESS_INACTIVE): 50 | raise TransitionError(task.state, States.INPROGRESS_ACTIVE, "Task must be in backlog or on hold or inactive") 51 | 52 | if (task.state == States.BACKLOG) | (task.state == States.ONHOLD) : 53 | self.addToWip(task) 54 | 55 | self.__taskwarriorStartTask(task) 56 | 57 | 58 | def __taskwarriorStartTask(self, task): 59 | # Command line is: task start 60 | subprocess.call([self.pathToTaskWarrior, task.taskid.__str__(), 'start' ]) 61 | 62 | def stop(self, task): 63 | if (task.state != States.INPROGRESS_ACTIVE): 64 | raise TransitionError(task.state, States.INPROGRESS_INACTIVE, "Task must be active") 65 | 66 | self.__taskwarriorStopTask(task) 67 | 68 | 69 | def __taskwarriorStopTask(self, task): 70 | # Command line is: task stop 71 | subprocess.call([self.pathToTaskWarrior, task.taskid.__str__(), 'stop' ]) 72 | 73 | def hold(self, task, reason): 74 | if (task.state != States.INPROGRESS_ACTIVE) and (task.state != States.INPROGRESS_INACTIVE): 75 | raise TransitionError(task.state, States.ONHOLD, "Task must be in progress") 76 | 77 | if (task.state == States.INPROGRESS_ACTIVE): 78 | self.stop(task) 79 | 80 | self.__taskwarriorHoldTask(task, reason) 81 | 82 | 83 | def __taskwarriorHoldTask(self, task, reason): 84 | # Command line is: task modify -inprogress +onhold 85 | # task annotate 86 | subprocess.call([self.pathToTaskWarrior, task.taskid.__str__(), 'modify', '+onhold', '-inprogress' ]) 87 | subprocess.call([self.pathToTaskWarrior, task.taskid.__str__(), 'annotate', 'PUT ON HOLD: '+reason ]) 88 | 89 | def finish(self, task): 90 | if (task.state != States.INPROGRESS_ACTIVE) and (task.state != States.INPROGRESS_INACTIVE): 91 | raise TransitionError(task.state, States.INPROGRESS_INACTIVE, "Task must be in progress") 92 | 93 | self.__taskwarriorFinishTask(task) 94 | 95 | 96 | def __taskwarriorFinishTask(self, task): 97 | # Command line is: task modify -inprogress 98 | # task done 99 | subprocess.call([self.pathToTaskWarrior, task.taskid.__str__(), 'modify', '-inprogress' ]) 100 | subprocess.call([self.pathToTaskWarrior, task.taskid.__str__(),'done' ]) 101 | -------------------------------------------------------------------------------- /app/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keigezellig/kanbanwarrior/0bad4ba4c65783e7f1de24933c48da9533eaab9c/app/common/__init__.py -------------------------------------------------------------------------------- /app/common/command.py: -------------------------------------------------------------------------------- 1 | #/* 2 | # * ----------------------------------------------------------------------------------------------- 3 | # * "THE APPRECIATION LICENSE" (Revision 0xFF): 4 | # * Copyright (c) 2013 M. Joosten 5 | # * You can do anything with this program and its code, even use it to run a nuclear reactor (why should you) 6 | # But I won't be responsible for possible damage and mishap, because i never tested it on a nuclear reactor (why should I..) 7 | # If you think this program/code is absolutely great and supercalifragilisticexpialidocious (or just plain useful), just 8 | # let me know by sending me a nice email or postcard from your country of origin and leave this header in the code 9 | # See my blog (http://keigezellig.blog.com), for contact info 10 | # * --------------------------------------------------------------------------------------------------- 11 | # */ 12 | 13 | import argparse 14 | __version__ = '1.0' 15 | 16 | def constructArgParser(): 17 | parser = argparse.ArgumentParser() 18 | 19 | subparsers = parser.add_subparsers(title='subcommands', description='Valid subcommands are:', dest="command") 20 | addtobacklog_parser = subparsers.add_parser('addtobacklog', help='Adds a new task to the backlog') 21 | addtowip_parser = subparsers.add_parser('addtowip', help='Adds an existing task from the backlog to the in progress queue') 22 | start_parser = subparsers.add_parser('start', help='Starts a task from the in progress queue') 23 | stop_parser = subparsers.add_parser('stop', help='Stops a task from the in progress queue') 24 | finish_parser = subparsers.add_parser('finish', help='Finishes a task') 25 | hold_parser = subparsers.add_parser('hold', help='Puts a task on hold') 26 | 27 | report_parser = subparsers.add_parser('list', help='Reports') 28 | reportsubparsers = report_parser.add_subparsers(title='report types', description='Valid reports are:', dest='report' ) 29 | backlog_reportparser = reportsubparsers.add_parser('backlog', help='Displays the contents of the backlog') 30 | wip_reportparser = reportsubparsers.add_parser('wip', help='Displays the contents of the in progress queue') 31 | done_reportparser = reportsubparsers.add_parser('done', help='Displays finished tasks') 32 | onhold_reportparser = reportsubparsers.add_parser('onhold', help='Displays tasks on hold') 33 | 34 | 35 | addtobacklog_parser.add_argument('projectname', help='The name of project for which the task is added. May be in the form project.subproject') 36 | addtobacklog_parser.add_argument('taskname', help='Task description') 37 | addtobacklog_parser.add_argument('--priority', choices=['H', 'M', 'L', ''], default='', help='Optional task priority. H=High, M=Medium, L=Low. When not specified, no priority is assigned') 38 | addtowip_parser.add_argument('taskid', type=int, help='Id of the task to be added to the In Progress queue (can be obtained by executing ''task list'' )') 39 | start_parser.add_argument('taskid', type=int, help='Id of the task to be started (can be obtained by executing ''task list'' )') 40 | stop_parser.add_argument('taskid', type=int, help='Id of the task to be stopped (can be obtained by executing ''task list'' )') 41 | finish_parser.add_argument('taskid', type=int, help='Id of the task to be finished (can be obtained by executing ''task list'' )') 42 | hold_parser.add_argument('taskid', type=int, help='Id of the task to be put on hold (can be obtained by executing ''task list'' )') 43 | hold_parser.add_argument('reason', help='Reason of putting task on hold') 44 | 45 | backlog_reportparser.add_argument('projectname', help='The project for which the report is generated. May be in the form project.subproject') 46 | wip_reportparser.add_argument('projectname', help='The project for which the report is generated. May be in the form project.subproject') 47 | done_reportparser.add_argument('projectname', help='The project for which the report is generated. May be in the form project.subproject') 48 | onhold_reportparser.add_argument('projectname', help='The project for which the report is generated. May be in the form project.subproject') 49 | 50 | return parser 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/common/task.py: -------------------------------------------------------------------------------- 1 | #/* 2 | # * ----------------------------------------------------------------------------------------------- 3 | # * "THE APPRECIATION LICENSE" (Revision 0xFF): 4 | # * Copyright (c) 2013 M. Joosten 5 | # * You can do anything with this program and its code, even use it to run a nuclear reactor (why should you) 6 | # But I won't be responsible for possible damage and mishap, because i never tested it on a nuclear reactor (why should I..) 7 | # If you think this program/code is absolutely great and supercalifragilisticexpialidocious (or just plain useful), just 8 | # let me know by sending me a nice email or postcard from your country of origin and leave this header in the code 9 | # See my blog (http://keigezellig.blog.com), for contact info 10 | # * --------------------------------------------------------------------------------------------------- 11 | # */ 12 | import json 13 | import subprocess 14 | 15 | __version__ = '1.0' 16 | 17 | class States: 18 | NONE = 0 19 | BACKLOG = 1 20 | INPROGRESS_INACTIVE = 2 21 | INPROGRESS_ACTIVE = 3 22 | DONE = 4 23 | ONHOLD = 5 24 | 25 | class Task: 26 | taskid = 0 27 | state = States.NONE 28 | 29 | def __init__(self, taskid, state): 30 | self.taskid = taskid 31 | self.state = state 32 | 33 | class Tasklist: 34 | __list = [] 35 | 36 | def __init__(self, pathToTW): 37 | self.pathToTaskWarrior = pathToTW; 38 | if (self.pathToTaskWarrior == None): 39 | raise IOException("Cannot find Task Warrior program. Please install it") 40 | self.__list = self.__getTaskList() 41 | 42 | 43 | def getTaskById(self, id): 44 | taskfilter = filter(lambda x: x.taskid == id, self.__list) 45 | if taskfilter == []: 46 | raise LookupError('Unknown task specified.') 47 | else: 48 | return taskfilter[0] 49 | 50 | def __mapToTask(self, taskitem): 51 | if ('tags' in taskitem) and (len(taskitem['tags']) == 1) and ('backlog' in taskitem['tags']): 52 | return Task(taskitem['id'], States.BACKLOG) 53 | 54 | if ('tags' in taskitem) and (len(taskitem['tags']) == 1) and ('inprogress' in taskitem['tags']): 55 | if ('start' in taskitem): 56 | return Task(taskitem['id'], States.INPROGRESS_ACTIVE) 57 | else: 58 | return Task(taskitem['id'], States.INPROGRESS_INACTIVE) 59 | 60 | if ('tags' in taskitem) and (len(taskitem['tags']) == 1) and ('onhold' in taskitem['tags']): 61 | return Task(taskitem['id'], States.ONHOLD) 62 | 63 | 64 | def __filterIllegalTasks(self, listitem): 65 | return listitem != None and listitem.taskid != 0 66 | 67 | 68 | def __getTaskList(self): 69 | # excecute and get output from task export command 70 | exportOutput = subprocess.check_output([self.pathToTaskWarrior,'export']) 71 | 72 | # strip funky header (goes until first \n) 73 | jsonstring = exportOutput[exportOutput.index('\n')+1:] 74 | # add array brackets for json deserialization 75 | jsonstring = '[' + jsonstring 76 | jsonstring = jsonstring + ']' 77 | 78 | tasklist = json.loads(jsonstring) 79 | 80 | # map it to a list of task objects 81 | tasklist = map(self.__mapToTask, tasklist) 82 | # filter out the illegal ones 83 | tasklist = filter(self.__filterIllegalTasks, tasklist) 84 | 85 | return tasklist 86 | -------------------------------------------------------------------------------- /app/common/taskwarrior.py: -------------------------------------------------------------------------------- 1 | #/* 2 | # * ----------------------------------------------------------------------------------------------- 3 | # * "THE APPRECIATION LICENSE" (Revision 0xFF): 4 | # * Copyright (c) 2013 M. Joosten 5 | # * You can do anything with this program and its code, even use it to run a nuclear reactor (why should you) 6 | # But I won't be responsible for possible damage and mishap, because i never tested it on a nuclear reactor (why should I..) 7 | # If you think this program/code is absolutely great and supercalifragilisticexpialidocious (or just plain useful), just 8 | # let me know by sending me a nice email or postcard from your country of origin and leave this header in the code 9 | # See my blog (http://keigezellig.blog.com), for contact info 10 | # * --------------------------------------------------------------------------------------------------- 11 | # */ 12 | 13 | import os 14 | 15 | __version__ = '1.0' 16 | 17 | def findTaskWarrior(): 18 | #Default path for now, maybe in the future configurable 19 | filename='task' 20 | path='/usr/bin' 21 | for root, dirs, names in os.walk(path): 22 | if filename in names: 23 | return os.path.join(root, filename) 24 | else: 25 | raise IOError('Cannot find TaskWarrior binary. It should be located in the /usr/bin folder') 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /kanban-warrior.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | #/* 4 | # * ----------------------------------------------------------------------------------------------- 5 | # * "THE APPRECIATION LICENSE" (Revision 0xFF): 6 | # * Copyright (c) 2013 M. Joosten 7 | # * You can do anything with this program and its code, even use it to run a nuclear reactor (why should you) 8 | # But I won't be responsible for possible damage and mishap, because i never tested it on a nuclear reactor (why should I..) 9 | # If you think this program/code is absolutely great and supercalifragilisticexpialidocious (or just plain useful), just 10 | # let me know by sending me a nice email or postcard from your country of origin and leave this header in the code 11 | # See my blog (http://keigezellig.blog.com), for contact info 12 | # * --------------------------------------------------------------------------------------------------- 13 | # */ 14 | 15 | from app.administration.statemachine import * 16 | from app.common.task import * 17 | from app.common.taskwarrior import * 18 | from app.common.command import * 19 | 20 | import subprocess 21 | 22 | import sys 23 | 24 | __version__ = '1.0' 25 | 26 | if __name__=="__main__": 27 | 28 | try: 29 | argParser = constructArgParser() 30 | args = argParser.parse_args() 31 | 32 | pathToTaskWarrior = findTaskWarrior() 33 | 34 | 35 | if args.command=='addtobacklog': 36 | subprocess.check_call([pathToTaskWarrior, 'add', args.taskname, 'project:'+args.projectname, 'priority:'+args.priority, '+backlog' ]) 37 | sys.exit(0) 38 | 39 | 40 | if args.command != 'list': 41 | print "Loading task list.." 42 | tasklist = Tasklist(pathToTaskWarrior) 43 | sm = StateMachine(pathToTaskWarrior) 44 | task = tasklist.getTaskById(args.taskid) 45 | if task == None: 46 | sys.exit('Unknown task specified. Aborting program') 47 | 48 | if args.command == 'addtowip': 49 | sm.addToWip(task) 50 | elif args.command == 'start': 51 | sm.start(task) 52 | elif args.command == 'stop': 53 | sm.stop(task) 54 | elif args.command == 'finish': 55 | sm.finish(task) 56 | elif args.command == 'hold': 57 | sm.hold(task, args.reason) 58 | 59 | else: 60 | if args.report == 'backlog': 61 | subprocess.call([pathToTaskWarrior, 'long' , 'project:'+args.projectname, '+backlog' ]) 62 | elif args.report == 'wip': 63 | subprocess.call([pathToTaskWarrior, 'long' , 'project:'+args.projectname, '+inprogress' ]) 64 | elif args.report == 'done': 65 | subprocess.call([pathToTaskWarrior, 'completed' ]) 66 | elif args.report == 'onhold': 67 | subprocess.call([pathToTaskWarrior, 'long' , 'project:'+args.projectname, '+onhold' ]) 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | except (IOError, LookupError, TransitionError) as e: 76 | sys.exit('Error: '+e.__str__()) 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | 2 | from app.administration.statemachine import * 3 | 4 | 5 | def AddCorrectTaskToWipTest(): 6 | print "***** AddCorrectTaskToWipTest ********" 7 | task1 = Task(1,States.BACKLOG) 8 | task2 = Task(1,States.ONHOLD) 9 | sm = StateMachine() 10 | sm.AddToWip(task1) 11 | sm.AddToWip(task2) 12 | print task1.state 13 | print task2.state 14 | 15 | 16 | def AddWrongTaskToWipTest(): 17 | print "***** AddWrongTaskToWipTest ********" 18 | try: 19 | task = Task(1,States.INPROGRESS_INACTIVE) 20 | sm = StateMachine() 21 | sm.AddToWip(task) 22 | print task.state 23 | except TransitionError as e: 24 | print e.msg, e.prev, e.next 25 | 26 | try: 27 | task = Task(1,States.INPROGRESS_ACTIVE) 28 | sm = StateMachine() 29 | sm.AddToWip(task) 30 | print task.state 31 | except TransitionError as e: 32 | print e.msg, e.prev, e.next 33 | 34 | try: 35 | task = Task(1,States.DONE) 36 | sm = StateMachine() 37 | sm.AddToWip(task) 38 | print task.state 39 | except TransitionError as e: 40 | print e.msg, e.prev, e.next 41 | 42 | def StartCorrectTaskTest(): 43 | print "***** StartCorrectTaskTest ********" 44 | task1 = Task(1,States.BACKLOG) 45 | task2 = Task(1,States.ONHOLD) 46 | task3 = Task(1,States.INPROGRESS_INACTIVE) 47 | sm = StateMachine() 48 | sm.Start(task1) 49 | sm.Start(task2) 50 | sm.Start(task3) 51 | print task1.state 52 | print task2.state 53 | print task3.state 54 | 55 | 56 | def StartWrongTaskTest(): 57 | print "***** StartWrongTaskTest ********" 58 | try: 59 | task = Task(1,States.INPROGRESS_ACTIVE) 60 | sm = StateMachine() 61 | sm.Start(task) 62 | print task.state 63 | except TransitionError as e: 64 | print e.msg, e.prev, e.next 65 | 66 | try: 67 | task = Task(1,States.DONE) 68 | sm = StateMachine() 69 | sm.Start(task) 70 | print task.state 71 | except TransitionError as e: 72 | print e.msg, e.prev, e.next 73 | 74 | def StopCorrectTaskTest(): 75 | print "***** StopCorrectTaskTest ********" 76 | task1 = Task(1,States.INPROGRESS_ACTIVE) 77 | sm = StateMachine() 78 | sm.Stop(task1) 79 | print task1.state 80 | 81 | 82 | def StopWrongTaskTest(): 83 | print "***** StopWrongTaskTest ********" 84 | try: 85 | task = Task(1,States.INPROGRESS_INACTIVE) 86 | sm = StateMachine() 87 | sm.Stop(task) 88 | print task.state 89 | except TransitionError as e: 90 | print e.msg, e.prev, e.next 91 | 92 | try: 93 | task = Task(1,States.DONE) 94 | sm = StateMachine() 95 | sm.Stop(task) 96 | print task.state 97 | except TransitionError as e: 98 | print e.msg, e.prev, e.next 99 | 100 | try: 101 | task = Task(1,States.BACKLOG) 102 | sm = StateMachine() 103 | sm.Stop(task) 104 | print task.state 105 | except TransitionError as e: 106 | print e.msg, e.prev, e.next 107 | 108 | try: 109 | task = Task(1,States.ONHOLD) 110 | sm = StateMachine() 111 | sm.Stop(task) 112 | print task.state 113 | except TransitionError as e: 114 | print e.msg, e.prev, e.next 115 | 116 | 117 | def HoldCorrectTaskTest(): 118 | print "***** HoldCorrectTaskTest ********" 119 | task1 = Task(1,States.INPROGRESS_ACTIVE) 120 | task2 = Task(2,States.INPROGRESS_INACTIVE) 121 | sm = StateMachine() 122 | sm.Hold(task1, "Oops") 123 | sm.Hold(task2, "Oops") 124 | print task1.state, task2.state 125 | 126 | 127 | def HoldWrongTaskTest(): 128 | print "***** HoldWrongTaskTest ********" 129 | try: 130 | task = Task(1,States.DONE) 131 | sm = StateMachine() 132 | sm.Hold(task, "Oops") 133 | print task.state 134 | except TransitionError as e: 135 | print e.msg, e.prev, e.next 136 | 137 | try: 138 | task = Task(1,States.BACKLOG) 139 | sm = StateMachine() 140 | sm.Hold(task, "Oops") 141 | print task.state 142 | except TransitionError as e: 143 | print e.msg, e.prev, e.next 144 | 145 | try: 146 | task = Task(1,States.ONHOLD) 147 | sm = StateMachine() 148 | sm.Hold(task, "Oops") 149 | print task.state 150 | except TransitionError as e: 151 | print e.msg, e.prev, e.next 152 | 153 | def DoneCorrectTaskTest(): 154 | print "***** DoneCorrectTaskTest ********" 155 | task1 = Task(1,States.INPROGRESS_ACTIVE) 156 | task2 = Task(2,States.INPROGRESS_INACTIVE) 157 | sm = StateMachine() 158 | sm.Finish(task1) 159 | sm.Finish(task2) 160 | print task1.state, task2.state 161 | 162 | 163 | def DoneWrongTaskTest(): 164 | print "***** DoneWrongTaskTest ********" 165 | try: 166 | task = Task(1,States.DONE) 167 | sm = StateMachine() 168 | sm.Finish(task) 169 | print task.state 170 | except TransitionError as e: 171 | print e.msg, e.prev, e.next 172 | 173 | try: 174 | task = Task(1,States.BACKLOG) 175 | sm = StateMachine() 176 | sm.Finish(task) 177 | print task.state 178 | except TransitionError as e: 179 | print e.msg, e.prev, e.next 180 | 181 | try: 182 | task = Task(1,States.ONHOLD) 183 | sm = StateMachine() 184 | sm.Finish(task) 185 | print task.state 186 | except TransitionError as e: 187 | print e.msg, e.prev, e.next 188 | 189 | if __name__=="__main__": 190 | AddCorrectTaskToWipTest() 191 | AddWrongTaskToWipTest() 192 | StartCorrectTaskTest() 193 | StartWrongTaskTest() 194 | StopCorrectTaskTest() 195 | StopWrongTaskTest() 196 | HoldCorrectTaskTest() 197 | HoldWrongTaskTest() 198 | DoneCorrectTaskTest() 199 | DoneWrongTaskTest() 200 | --------------------------------------------------------------------------------