donna 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. donna/__init__.py +1 -0
  2. donna/artifacts/__init__.py +0 -0
  3. donna/artifacts/usage/__init__.py +0 -0
  4. donna/artifacts/usage/artifacts.md +224 -0
  5. donna/artifacts/usage/cli.md +117 -0
  6. donna/artifacts/usage/worlds.md +36 -0
  7. donna/artifacts/work/__init__.py +0 -0
  8. donna/artifacts/work/do_it.md +142 -0
  9. donna/artifacts/work/do_it_fast.md +98 -0
  10. donna/artifacts/work/planning.md +245 -0
  11. donna/cli/__init__.py +0 -0
  12. donna/cli/__main__.py +6 -0
  13. donna/cli/application.py +17 -0
  14. donna/cli/commands/__init__.py +0 -0
  15. donna/cli/commands/artifacts.py +110 -0
  16. donna/cli/commands/projects.py +49 -0
  17. donna/cli/commands/sessions.py +77 -0
  18. donna/cli/types.py +138 -0
  19. donna/cli/utils.py +53 -0
  20. donna/core/__init__.py +0 -0
  21. donna/core/entities.py +27 -0
  22. donna/core/errors.py +126 -0
  23. donna/core/result.py +99 -0
  24. donna/core/utils.py +37 -0
  25. donna/domain/__init__.py +0 -0
  26. donna/domain/errors.py +47 -0
  27. donna/domain/ids.py +497 -0
  28. donna/lib/__init__.py +21 -0
  29. donna/lib/sources.py +5 -0
  30. donna/lib/worlds.py +7 -0
  31. donna/machine/__init__.py +0 -0
  32. donna/machine/action_requests.py +50 -0
  33. donna/machine/artifacts.py +200 -0
  34. donna/machine/changes.py +91 -0
  35. donna/machine/errors.py +122 -0
  36. donna/machine/operations.py +31 -0
  37. donna/machine/primitives.py +77 -0
  38. donna/machine/sessions.py +215 -0
  39. donna/machine/state.py +244 -0
  40. donna/machine/tasks.py +89 -0
  41. donna/machine/templates.py +83 -0
  42. donna/primitives/__init__.py +1 -0
  43. donna/primitives/artifacts/__init__.py +0 -0
  44. donna/primitives/artifacts/specification.py +20 -0
  45. donna/primitives/artifacts/workflow.py +195 -0
  46. donna/primitives/directives/__init__.py +0 -0
  47. donna/primitives/directives/goto.py +44 -0
  48. donna/primitives/directives/task_variable.py +73 -0
  49. donna/primitives/directives/view.py +45 -0
  50. donna/primitives/operations/__init__.py +0 -0
  51. donna/primitives/operations/finish_workflow.py +37 -0
  52. donna/primitives/operations/request_action.py +89 -0
  53. donna/primitives/operations/run_script.py +250 -0
  54. donna/protocol/__init__.py +0 -0
  55. donna/protocol/cell_shortcuts.py +9 -0
  56. donna/protocol/cells.py +44 -0
  57. donna/protocol/errors.py +17 -0
  58. donna/protocol/formatters/__init__.py +0 -0
  59. donna/protocol/formatters/automation.py +25 -0
  60. donna/protocol/formatters/base.py +15 -0
  61. donna/protocol/formatters/human.py +36 -0
  62. donna/protocol/formatters/llm.py +39 -0
  63. donna/protocol/modes.py +40 -0
  64. donna/protocol/nodes.py +59 -0
  65. donna/world/__init__.py +0 -0
  66. donna/world/artifacts.py +122 -0
  67. donna/world/artifacts_discovery.py +90 -0
  68. donna/world/config.py +198 -0
  69. donna/world/errors.py +232 -0
  70. donna/world/initialization.py +42 -0
  71. donna/world/markdown.py +267 -0
  72. donna/world/sources/__init__.py +1 -0
  73. donna/world/sources/base.py +62 -0
  74. donna/world/sources/markdown.py +260 -0
  75. donna/world/templates.py +181 -0
  76. donna/world/tmp.py +33 -0
  77. donna/world/worlds/__init__.py +0 -0
  78. donna/world/worlds/base.py +68 -0
  79. donna/world/worlds/filesystem.py +189 -0
  80. donna/world/worlds/python.py +196 -0
  81. donna-0.2.0.dist-info/METADATA +44 -0
  82. donna-0.2.0.dist-info/RECORD +85 -0
  83. donna-0.2.0.dist-info/WHEEL +4 -0
  84. donna-0.2.0.dist-info/entry_points.txt +3 -0
  85. donna-0.2.0.dist-info/licenses/LICENSE +28 -0
@@ -0,0 +1,215 @@
1
+ import functools
2
+ from typing import Callable, ParamSpec
3
+
4
+ from donna.core.errors import ErrorsList
5
+ from donna.core.result import Err, Ok, Result, unwrap_to_error
6
+ from donna.domain.ids import ActionRequestId, FullArtifactId, FullArtifactSectionId, WorldId
7
+ from donna.machine import errors as machine_errors
8
+ from donna.machine.operations import OperationMeta
9
+ from donna.machine.state import ConsistentState, MutableState
10
+ from donna.protocol.cell_shortcuts import operation_succeeded
11
+ from donna.protocol.cells import Cell
12
+ from donna.world import artifacts
13
+ from donna.world import tmp as world_tmp
14
+ from donna.world.config import config
15
+ from donna.world.worlds.base import World
16
+
17
+
18
+ def _errors_to_cells(errors: ErrorsList) -> list[Cell]:
19
+ return [error.node().info() for error in errors]
20
+
21
+
22
+ @unwrap_to_error
23
+ def _session() -> Result[World, ErrorsList]:
24
+ world = config().get_world(WorldId("session")).unwrap()
25
+
26
+ if not world.is_initialized():
27
+ world.initialize(reset=False)
28
+
29
+ return Ok(world)
30
+
31
+
32
+ @unwrap_to_error
33
+ def _load_state() -> Result[ConsistentState, ErrorsList]:
34
+ content = _session().unwrap().read_state("state.json").unwrap()
35
+ if content is None:
36
+ return Err([machine_errors.SessionStateNotInitialized()])
37
+
38
+ return Ok(ConsistentState.from_json(content.decode("utf-8")))
39
+
40
+
41
+ @unwrap_to_error
42
+ def _save_state(state: ConsistentState) -> Result[None, ErrorsList]:
43
+ _session().unwrap().write_state("state.json", state.to_json().encode("utf-8")).unwrap()
44
+ return Ok(None)
45
+
46
+
47
+ @unwrap_to_error
48
+ def _state_run(mutator: MutableState) -> Result[None, ErrorsList]:
49
+ while mutator.has_work():
50
+ mutator.exectute_next_work_unit().unwrap()
51
+ _save_state(mutator.freeze()).unwrap()
52
+
53
+ return Ok(None)
54
+
55
+
56
+ @unwrap_to_error
57
+ def _state_cells() -> Result[list[Cell], ErrorsList]:
58
+ return Ok(_load_state().unwrap().node().details())
59
+
60
+
61
+ P = ParamSpec("P")
62
+
63
+
64
+ def _session_required(func: Callable[P, list[Cell]]) -> Callable[P, list[Cell]]:
65
+ # TODO: refactor to catch domain exception from load_state
66
+ # when we implement domain exceptions
67
+ @functools.wraps(func)
68
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> list[Cell]:
69
+ state_result = _load_state()
70
+ if state_result.is_err():
71
+ return _errors_to_cells(state_result.unwrap_err())
72
+
73
+ return func(*args, **kwargs)
74
+
75
+ return wrapper
76
+
77
+
78
+ def start() -> list[Cell]:
79
+ world_tmp.clear()
80
+ session_result = _session()
81
+ if session_result.is_err():
82
+ return _errors_to_cells(session_result.unwrap_err())
83
+
84
+ session_result.unwrap().initialize(reset=True)
85
+
86
+ save_result = _save_state(MutableState.build().freeze())
87
+ if save_result.is_err():
88
+ return _errors_to_cells(save_result.unwrap_err())
89
+
90
+ return [operation_succeeded("Started new session.")]
91
+
92
+
93
+ def clear() -> list[Cell]:
94
+ world_tmp.clear()
95
+ session_result = _session()
96
+ if session_result.is_err():
97
+ return _errors_to_cells(session_result.unwrap_err())
98
+
99
+ session_result.unwrap().initialize(reset=True)
100
+ return [operation_succeeded("Cleared session.")]
101
+
102
+
103
+ @_session_required
104
+ def continue_() -> list[Cell]:
105
+ state_result = _load_state()
106
+ if state_result.is_err():
107
+ return _errors_to_cells(state_result.unwrap_err())
108
+
109
+ mutator = state_result.unwrap().mutator()
110
+ run_result = _state_run(mutator)
111
+ if run_result.is_err():
112
+ return _errors_to_cells(run_result.unwrap_err())
113
+
114
+ cells_result = _state_cells()
115
+ if cells_result.is_err():
116
+ return _errors_to_cells(cells_result.unwrap_err())
117
+
118
+ return cells_result.unwrap()
119
+
120
+
121
+ @_session_required
122
+ def status() -> list[Cell]:
123
+ state_result = _load_state()
124
+ if state_result.is_err():
125
+ return _errors_to_cells(state_result.unwrap_err())
126
+
127
+ return [state_result.unwrap().node().info()]
128
+
129
+
130
+ @_session_required
131
+ def details() -> list[Cell]:
132
+ state_result = _load_state()
133
+ if state_result.is_err():
134
+ return _errors_to_cells(state_result.unwrap_err())
135
+
136
+ return state_result.unwrap().node().details()
137
+
138
+
139
+ @_session_required
140
+ def start_workflow(artifact_id: FullArtifactId) -> list[Cell]:
141
+ workflow_result = artifacts.load_artifact(artifact_id)
142
+ if workflow_result.is_err():
143
+ return _errors_to_cells(workflow_result.unwrap_err())
144
+
145
+ workflow = workflow_result.unwrap()
146
+ primary_section_result = workflow.primary_section()
147
+ if primary_section_result.is_err():
148
+ return _errors_to_cells(primary_section_result.unwrap_err())
149
+
150
+ primary_section = primary_section_result.unwrap()
151
+
152
+ state_result = _load_state()
153
+ if state_result.is_err():
154
+ return _errors_to_cells(state_result.unwrap_err())
155
+
156
+ mutator = state_result.unwrap().mutator()
157
+ mutator.start_workflow(workflow.id.to_full_local(primary_section.id))
158
+ save_result = _save_state(mutator.freeze())
159
+ if save_result.is_err():
160
+ return _errors_to_cells(save_result.unwrap_err())
161
+
162
+ run_result = _state_run(mutator)
163
+ if run_result.is_err():
164
+ return _errors_to_cells(run_result.unwrap_err())
165
+
166
+ cells_result = _state_cells()
167
+ if cells_result.is_err():
168
+ return _errors_to_cells(cells_result.unwrap_err())
169
+
170
+ return cells_result.unwrap()
171
+
172
+
173
+ @unwrap_to_error
174
+ def _validate_operation_transition(
175
+ state: MutableState, request_id: ActionRequestId, next_operation_id: FullArtifactSectionId
176
+ ) -> Result[None, ErrorsList]:
177
+ operation_id = state.get_action_request(request_id).unwrap().operation_id
178
+ workflow = artifacts.load_artifact(operation_id.full_artifact_id).unwrap()
179
+ operation = workflow.get_section(operation_id.local_id).unwrap()
180
+
181
+ assert isinstance(operation.meta, OperationMeta)
182
+
183
+ if next_operation_id.local_id not in operation.meta.allowed_transtions:
184
+ return Err(
185
+ [machine_errors.InvalidOperationTransition(operation_id=operation_id, next_operation_id=next_operation_id)]
186
+ )
187
+
188
+ return Ok(None)
189
+
190
+
191
+ @_session_required
192
+ def complete_action_request(request_id: ActionRequestId, next_operation_id: FullArtifactSectionId) -> list[Cell]:
193
+ state_result = _load_state()
194
+ if state_result.is_err():
195
+ return _errors_to_cells(state_result.unwrap_err())
196
+
197
+ mutator = state_result.unwrap().mutator()
198
+ transition_result = _validate_operation_transition(mutator, request_id, next_operation_id)
199
+ if transition_result.is_err():
200
+ return _errors_to_cells(transition_result.unwrap_err())
201
+
202
+ mutator.complete_action_request(request_id, next_operation_id)
203
+ save_result = _save_state(mutator.freeze())
204
+ if save_result.is_err():
205
+ return _errors_to_cells(save_result.unwrap_err())
206
+
207
+ run_result = _state_run(mutator)
208
+ if run_result.is_err():
209
+ return _errors_to_cells(run_result.unwrap_err())
210
+
211
+ cells_result = _state_cells()
212
+ if cells_result.is_err():
213
+ return _errors_to_cells(cells_result.unwrap_err())
214
+
215
+ return cells_result.unwrap()
donna/machine/state.py ADDED
@@ -0,0 +1,244 @@
1
+ import copy
2
+ import textwrap
3
+ from typing import Sequence, cast
4
+
5
+ import pydantic
6
+
7
+ from donna.core.entities import BaseEntity
8
+ from donna.core.errors import ErrorsList
9
+ from donna.core.result import Err, Ok, Result, unwrap_to_error
10
+ from donna.domain.ids import (
11
+ ActionRequestId,
12
+ FullArtifactSectionId,
13
+ InternalId,
14
+ TaskId,
15
+ WorkUnitId,
16
+ )
17
+ from donna.machine import errors as machine_errors
18
+ from donna.machine.action_requests import ActionRequest
19
+ from donna.machine.changes import (
20
+ Change,
21
+ ChangeAddTask,
22
+ ChangeAddWorkUnit,
23
+ ChangeRemoveActionRequest,
24
+ ChangeRemoveTask,
25
+ ChangeRemoveWorkUnit,
26
+ )
27
+ from donna.machine.tasks import Task, WorkUnit
28
+ from donna.protocol.cells import Cell
29
+ from donna.protocol.nodes import Node
30
+
31
+
32
+ class BaseState(BaseEntity):
33
+ tasks: list[Task]
34
+ work_units: list[WorkUnit]
35
+ action_requests: list[ActionRequest]
36
+ started: bool
37
+ last_id: int
38
+
39
+ def has_work(self) -> bool:
40
+ return bool(self.work_units)
41
+
42
+ def node(self) -> "StateNode":
43
+ return StateNode(self)
44
+
45
+ ###########
46
+ # Accessors
47
+ ###########
48
+
49
+ @property
50
+ def current_task(self) -> Task:
51
+ return self.tasks[-1]
52
+
53
+ def get_action_request(self, request_id: ActionRequestId) -> Result[ActionRequest, ErrorsList]:
54
+ for request in self.action_requests:
55
+ if request.id == request_id:
56
+ return Ok(request)
57
+
58
+ return Err([machine_errors.ActionRequestNotFound(request_id=request_id)])
59
+
60
+ # Currently we execute first work unit found for the current task
61
+ # Since we only append work units, this effectively works as a queue per task
62
+ # In the future we may want to have more sophisticated scheduling
63
+ def get_next_work_unit(self) -> WorkUnit | None:
64
+ for work_unit in self.work_units:
65
+ if work_unit.task_id != self.current_task.id:
66
+ continue
67
+
68
+ return work_unit
69
+
70
+ return None
71
+
72
+
73
+ class ConsistentState(BaseState):
74
+
75
+ def mutator(self) -> "MutableState":
76
+ return MutableState.model_validate(copy.deepcopy(self.model_dump()))
77
+
78
+
79
+ class MutableState(BaseState):
80
+ model_config = pydantic.ConfigDict(frozen=False)
81
+
82
+ @classmethod
83
+ def build(cls) -> "MutableState":
84
+ return cls(
85
+ tasks=[],
86
+ action_requests=[],
87
+ work_units=[],
88
+ started=False,
89
+ last_id=0,
90
+ )
91
+
92
+ def freeze(self) -> ConsistentState:
93
+ return ConsistentState.model_validate(copy.deepcopy(self.model_dump()))
94
+
95
+ ################
96
+ # Ids generation
97
+ ################
98
+
99
+ def next_id(self, prefix: str) -> InternalId:
100
+ self.last_id += 1
101
+ new_id = InternalId.build(prefix, self.last_id)
102
+ return new_id
103
+
104
+ def next_task_id(self) -> TaskId:
105
+ return cast(TaskId, self.next_id("T"))
106
+
107
+ def next_work_unit_id(self) -> WorkUnitId:
108
+ return cast(WorkUnitId, self.next_id("WU"))
109
+
110
+ def next_action_request_id(self) -> ActionRequestId:
111
+ return cast(ActionRequestId, self.next_id("AR"))
112
+
113
+ ##########
114
+ # Mutators
115
+ ##########
116
+
117
+ def mark_started(self) -> None:
118
+ self.started = True
119
+
120
+ def add_action_request(self, action_request: ActionRequest) -> None:
121
+ action_request.id = self.next_action_request_id()
122
+ self.action_requests.append(action_request)
123
+
124
+ def add_work_unit(self, work_unit: WorkUnit) -> None:
125
+ self.work_units.append(work_unit)
126
+
127
+ def add_task(self, task: Task) -> None:
128
+ self.tasks.append(task)
129
+
130
+ def remove_action_request(self, request_id: ActionRequestId) -> None:
131
+ self.action_requests = [request for request in self.action_requests if request.id != request_id]
132
+
133
+ def remove_work_unit(self, work_unit_id: WorkUnitId) -> None:
134
+ self.work_units = [unit for unit in self.work_units if unit.id != work_unit_id]
135
+
136
+ def remove_task(self, task_id: TaskId) -> None:
137
+ self.tasks = [task for task in self.tasks if task.id != task_id]
138
+
139
+ def apply_changes(self, changes: Sequence[Change]) -> None:
140
+ for change in changes:
141
+ change.apply_to(self)
142
+
143
+ ####################
144
+ # Complex operations
145
+ ####################
146
+
147
+ def complete_action_request(self, request_id: ActionRequestId, next_operation_id: FullArtifactSectionId) -> None:
148
+ changes = [
149
+ ChangeAddWorkUnit(task_id=self.current_task.id, operation_id=next_operation_id),
150
+ ChangeRemoveActionRequest(action_request_id=request_id),
151
+ ]
152
+ self.apply_changes(changes)
153
+
154
+ def start_workflow(self, full_operation_id: FullArtifactSectionId) -> None:
155
+ changes = [ChangeAddTask(operation_id=full_operation_id)]
156
+ self.apply_changes(changes)
157
+
158
+ def finish_workflow(self, task_id: TaskId) -> None:
159
+ changes = [ChangeRemoveTask(task_id=task_id)]
160
+ self.apply_changes(changes)
161
+
162
+ @unwrap_to_error
163
+ def exectute_next_work_unit(self) -> Result[None, ErrorsList]:
164
+ next_work_unit = self.get_next_work_unit()
165
+ assert next_work_unit is not None
166
+
167
+ changes = next_work_unit.run(self.current_task).unwrap()
168
+ changes.append(ChangeRemoveWorkUnit(work_unit_id=next_work_unit.id))
169
+
170
+ self.apply_changes(changes)
171
+ return Ok(None)
172
+
173
+
174
+ class StateNode(Node):
175
+ __slots__ = ("_state",)
176
+
177
+ def __init__(self, state: BaseState) -> None:
178
+ self._state = state
179
+
180
+ def status(self) -> Cell:
181
+ if not self._state.started:
182
+ message = textwrap.dedent(
183
+ """
184
+ The session has not been started yet. You can safely start a new session and then run a workflow.
185
+ """
186
+ )
187
+
188
+ elif not self._state.tasks:
189
+ message = textwrap.dedent(
190
+ """
191
+ The session is IDLE. There are no active tasks.
192
+
193
+ - If the developer asked you to start working on a new task, you can do so by initiating a new workflow.
194
+ - If you have been working on a task, consider it completed and REPORT THE RESULTS TO THE DEVELOPER.
195
+ """
196
+ )
197
+
198
+ elif self._state.work_units:
199
+ message = textwrap.dedent(
200
+ """
201
+ The session has PENDING WORK UNITS. Donna has work to complete.
202
+
203
+ - If the developer asked you to start working on a new task, you MUST warn that there are pending work
204
+ units and ask if you should start a new session or continue working on the current work units.
205
+ - If you have been working on a task, you can continue session.
206
+ """
207
+ )
208
+
209
+ elif self._state.action_requests:
210
+ message = textwrap.dedent(
211
+ """
212
+ The session is AWAITING YOUR ACTION. You have pending action requests to address.
213
+
214
+ - If the developer asked you to start working on a new task, you MUST ask if you should start a new session
215
+ or continue working on the current action requests.
216
+ - Otherwise, you MUST address the pending action requests before proceeding.
217
+ """
218
+ )
219
+
220
+ elif self._state.tasks:
221
+ message = textwrap.dedent(
222
+ """
223
+ The session has unfinished TASKS but no pending work units or action requests.
224
+
225
+ - If the developer asked you to start working on a new task , you MUST ask if you should start a new
226
+ session or run a new workflow in the current one.
227
+ - If you have been working on a task, you can consider it completed and output the results to the
228
+ developer.
229
+ """
230
+ )
231
+
232
+ else:
233
+ raise machine_errors.SessionStateStatusInvalid()
234
+
235
+ return Cell.build_markdown(
236
+ kind="session_state_status",
237
+ content=message,
238
+ tasks=len(self._state.tasks),
239
+ queued_work_units=len(self._state.work_units),
240
+ pending_action_requests=len(self._state.action_requests),
241
+ )
242
+
243
+ def references(self) -> list[Node]:
244
+ return [action_request.node() for action_request in self._state.action_requests]
donna/machine/tasks.py ADDED
@@ -0,0 +1,89 @@
1
+ import copy
2
+ import sys
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ import pydantic
6
+
7
+ from donna.core.entities import BaseEntity
8
+ from donna.core.errors import ErrorsList
9
+ from donna.core.result import Ok, Result, unwrap_to_error
10
+ from donna.domain.ids import FullArtifactSectionId, TaskId, WorkUnitId
11
+ from donna.protocol.cells import Cell
12
+ from donna.protocol.modes import get_cell_formatter
13
+
14
+ if TYPE_CHECKING:
15
+ from donna.machine.changes import Change
16
+
17
+
18
+ class Task(BaseEntity):
19
+ id: TaskId
20
+ context: dict[str, Any]
21
+
22
+ # TODO: we may want to make queue items frozen later
23
+ model_config = pydantic.ConfigDict(frozen=False)
24
+
25
+ @classmethod
26
+ def build(cls, id: TaskId) -> "Task":
27
+ return Task(
28
+ id=id,
29
+ context={},
30
+ )
31
+
32
+
33
+ class WorkUnit(BaseEntity):
34
+ id: WorkUnitId
35
+ task_id: TaskId
36
+ operation_id: FullArtifactSectionId
37
+ context: dict[str, Any]
38
+
39
+ @classmethod
40
+ def build(
41
+ cls,
42
+ id: WorkUnitId,
43
+ task_id: TaskId,
44
+ operation_id: FullArtifactSectionId,
45
+ context: dict[str, Any] | None = None,
46
+ ) -> "WorkUnit":
47
+
48
+ if context is None:
49
+ context = {}
50
+
51
+ unit = cls(
52
+ task_id=task_id,
53
+ id=id,
54
+ operation_id=operation_id,
55
+ context=copy.deepcopy(context),
56
+ )
57
+
58
+ return unit
59
+
60
+ @unwrap_to_error
61
+ def run(self, task: Task) -> Result[list["Change"], ErrorsList]:
62
+ from donna.machine import artifacts as machine_artifacts
63
+ from donna.machine.primitives import resolve_primitive
64
+ from donna.world.artifacts import ArtifactRenderContext
65
+ from donna.world.templates import RenderMode
66
+
67
+ render_context = ArtifactRenderContext(
68
+ primary_mode=RenderMode.execute,
69
+ current_task=task,
70
+ current_work_unit=self,
71
+ )
72
+ operation = machine_artifacts.resolve(self.operation_id, render_context).unwrap()
73
+ operation_kind = resolve_primitive(operation.kind).unwrap()
74
+
75
+ ##########################
76
+ # We log each operation here to help agent display the progress to the user
77
+ # TODO: not a good solution from the agent perspective
78
+ # let's hope there will some protocol appear that helps with that later
79
+ # TODO: not so good place for and way of logging, should do smth with that
80
+ log_message = f"{self.operation_id}: {operation.title}"
81
+ log_cell = Cell.build(kind="donna_log", media_type="text/plain", content=log_message)
82
+ formatter = get_cell_formatter()
83
+ sys.stdout.buffer.write(formatter.format_log(log_cell, single_mode=True) + b"\n\n")
84
+ sys.stdout.buffer.flush()
85
+ ##########################
86
+
87
+ changes = list(operation_kind.execute_section(task, self, operation))
88
+
89
+ return Ok(changes)
@@ -0,0 +1,83 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import TYPE_CHECKING, Any, TypeAlias
3
+
4
+ from jinja2.runtime import Context
5
+
6
+ from donna.core.errors import ErrorsList
7
+ from donna.core.result import Ok, Result
8
+ from donna.machine import errors as machine_errors
9
+ from donna.machine.primitives import Primitive
10
+
11
+ if TYPE_CHECKING:
12
+ pass
13
+
14
+ PreparedDirectiveArguments: TypeAlias = tuple[Any, ...]
15
+ PreparedDirectiveResult: TypeAlias = Result[PreparedDirectiveArguments, ErrorsList]
16
+
17
+
18
+ class DirectiveUnsupportedRenderMode(machine_errors.InternalError):
19
+ message: str = "Render mode {render_mode} not implemented in directive {directive_name}."
20
+ render_mode: Any
21
+ directive_name: str
22
+
23
+
24
+ class Directive(Primitive, ABC):
25
+ analyze_id: str
26
+
27
+ def apply_directive( # noqa: E704
28
+ self,
29
+ context: Context,
30
+ *argv: Any,
31
+ ) -> Result[Any, ErrorsList]:
32
+ from donna.world import templates as world_templates
33
+
34
+ render_mode = context["render_mode"]
35
+ arguments_result = self._prepare_arguments(context, *argv)
36
+ if arguments_result.is_err():
37
+ return arguments_result
38
+
39
+ argv = arguments_result.unwrap()
40
+
41
+ match render_mode:
42
+ case world_templates.RenderMode.view:
43
+ return self.render_view(context, *argv)
44
+ case world_templates.RenderMode.execute:
45
+ return self.render_execute(context, *argv)
46
+ case world_templates.RenderMode.analysis:
47
+ return self.render_analyze(context, *argv)
48
+ case _:
49
+ raise DirectiveUnsupportedRenderMode(render_mode=render_mode, directive_name=self.__class__.__name__)
50
+
51
+ def _prepare_arguments(
52
+ self,
53
+ context: Context,
54
+ *argv: Any,
55
+ ) -> PreparedDirectiveResult:
56
+ return Ok(argv)
57
+
58
+ @abstractmethod
59
+ def render_view( # noqa: E704
60
+ self,
61
+ context: Context,
62
+ *argv: Any,
63
+ ) -> Result[Any, ErrorsList]: ...
64
+
65
+ def render_execute(
66
+ self,
67
+ context: Context,
68
+ *argv: Any,
69
+ ) -> Result[Any, ErrorsList]:
70
+ return self.render_view(context, *argv)
71
+
72
+ def render_analyze(
73
+ self,
74
+ context: Context,
75
+ *argv: Any,
76
+ ) -> Result[str, ErrorsList]:
77
+ parts = [str(arg) for arg in argv]
78
+ arguments = " ".join(parts)
79
+
80
+ if arguments:
81
+ return Ok(f"$$donna {self.analyze_id} {arguments} donna$$")
82
+
83
+ return Ok(f"$$donna {self.analyze_id} donna$$")
@@ -0,0 +1 @@
1
+ """Concrete implementations for Donna primitives."""
File without changes
@@ -0,0 +1,20 @@
1
+ from typing import TYPE_CHECKING, ClassVar
2
+
3
+ from donna.machine.artifacts import ArtifactSectionConfig
4
+ from donna.machine.primitives import Primitive
5
+ from donna.world.sources.markdown import MarkdownSectionMixin
6
+
7
+ if TYPE_CHECKING:
8
+ pass
9
+
10
+
11
+ class TextConfig(ArtifactSectionConfig):
12
+ pass
13
+
14
+
15
+ class Text(MarkdownSectionMixin, Primitive):
16
+ config_class: ClassVar[type[TextConfig]] = TextConfig
17
+
18
+
19
+ class Specification(MarkdownSectionMixin, Primitive):
20
+ pass