plan.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. from typing import List
  2. OPEN_STATE = 'open'
  3. COMPLETED_STATE = 'completed'
  4. ABANDONED_STATE = 'abandoned'
  5. IN_PROGRESS_STATE = 'in_progress'
  6. VERIFIED_STATE = 'verified'
  7. STATES = [OPEN_STATE, COMPLETED_STATE, ABANDONED_STATE, IN_PROGRESS_STATE, VERIFIED_STATE]
  8. class Task:
  9. id: str
  10. goal: str
  11. parent: "Task | None"
  12. subtasks: List["Task"]
  13. def __init__(self, parent: "Task | None", goal: str, state: str=OPEN_STATE, subtasks: List = []):
  14. if parent is None:
  15. self.id = '0'
  16. else:
  17. self.id = parent.id + '.' + str(len(parent.subtasks))
  18. self.parent = parent
  19. self.goal = goal
  20. self.subtasks = []
  21. for subtask in (subtasks or []):
  22. if isinstance(subtask, Task):
  23. self.subtasks.append(subtask)
  24. else:
  25. goal = subtask.get('goal')
  26. state = subtask.get('state')
  27. subtasks = subtask.get('subtasks')
  28. self.subtasks.append(Task(self, goal, state, subtasks))
  29. self.state = OPEN_STATE
  30. def to_string(self, indent=""):
  31. emoji = ''
  32. if self.state == VERIFIED_STATE:
  33. emoji = '✅'
  34. elif self.state == COMPLETED_STATE:
  35. emoji = '🟢'
  36. elif self.state == ABANDONED_STATE:
  37. emoji = '❌'
  38. elif self.state == IN_PROGRESS_STATE:
  39. emoji = '💪'
  40. elif self.state == OPEN_STATE:
  41. emoji = '🔵'
  42. result = indent + emoji + ' ' + self.id + ' ' + self.goal + '\n'
  43. for subtask in self.subtasks:
  44. result += subtask.to_string(indent + ' ')
  45. return result
  46. def to_dict(self):
  47. return {
  48. 'id': self.id,
  49. 'goal': self.goal,
  50. 'state': self.state,
  51. 'subtasks': [t.to_dict() for t in self.subtasks]
  52. }
  53. def set_state(self, state):
  54. if state not in STATES:
  55. raise ValueError('Invalid state:' + state)
  56. self.state = state
  57. if state == COMPLETED_STATE or state == ABANDONED_STATE or state == VERIFIED_STATE:
  58. for subtask in self.subtasks:
  59. if subtask.state != ABANDONED_STATE:
  60. subtask.set_state(state)
  61. elif state == IN_PROGRESS_STATE:
  62. if self.parent is not None:
  63. self.parent.set_state(state)
  64. def get_current_task(self) -> "Task | None":
  65. for subtask in self.subtasks:
  66. if subtask.state == IN_PROGRESS_STATE:
  67. return subtask.get_current_task()
  68. if self.state == IN_PROGRESS_STATE:
  69. return self
  70. return None
  71. class Plan:
  72. main_goal: str
  73. task: Task
  74. def __init__(self, task: str):
  75. self.main_goal = task
  76. self.task = Task(parent=None, goal=task, subtasks=[])
  77. def __str__(self):
  78. return self.task.to_string()
  79. def get_task_by_id(self, id: str) -> Task:
  80. try:
  81. parts = [int(p) for p in id.split('.')]
  82. except ValueError:
  83. raise ValueError('Invalid task id, non-integer:' + id)
  84. if parts[0] != 0:
  85. raise ValueError('Invalid task id, must start with 0:' + id)
  86. parts = parts[1:]
  87. task = self.task
  88. for part in parts:
  89. if part >= len(task.subtasks):
  90. raise ValueError('Task does not exist:' + id)
  91. task = task.subtasks[part]
  92. return task
  93. def add_subtask(self, parent_id: str, goal: str, subtasks: List = []):
  94. parent = self.get_task_by_id(parent_id)
  95. child = Task(parent=parent, goal=goal, subtasks=subtasks)
  96. parent.subtasks.append(child)
  97. def set_subtask_state(self, id: str, state: str):
  98. task = self.get_task_by_id(id)
  99. task.set_state(state)
  100. def get_current_task(self):
  101. return self.task.get_current_task()