donna 0.2.0__py3-none-any.whl → 0.2.1__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 (68) hide show
  1. donna/artifacts/intro.md +39 -0
  2. donna/artifacts/research/specs/report.md +163 -0
  3. donna/artifacts/research/work/research.md +198 -0
  4. donna/artifacts/rfc/specs/request_for_change.md +271 -0
  5. donna/artifacts/rfc/work/create.md +120 -0
  6. donna/artifacts/rfc/work/do.md +109 -0
  7. donna/artifacts/rfc/work/plan.md +68 -0
  8. donna/artifacts/usage/artifacts.md +41 -6
  9. donna/artifacts/usage/cli.md +106 -37
  10. donna/artifacts/usage/worlds.md +8 -2
  11. donna/cli/__main__.py +1 -1
  12. donna/cli/commands/artifacts.py +104 -17
  13. donna/cli/commands/sessions.py +7 -7
  14. donna/cli/commands/workspaces.py +42 -0
  15. donna/cli/errors.py +18 -0
  16. donna/cli/types.py +16 -9
  17. donna/cli/utils.py +2 -2
  18. donna/core/errors.py +1 -11
  19. donna/core/result.py +5 -8
  20. donna/core/utils.py +0 -3
  21. donna/lib/__init__.py +4 -0
  22. donna/lib/sources.py +1 -1
  23. donna/lib/worlds.py +2 -2
  24. donna/machine/action_requests.py +0 -5
  25. donna/machine/artifacts.py +8 -6
  26. donna/machine/primitives.py +4 -4
  27. donna/machine/sessions.py +12 -4
  28. donna/machine/state.py +2 -2
  29. donna/machine/tasks.py +4 -18
  30. donna/machine/templates.py +4 -2
  31. donna/primitives/artifacts/specification.py +13 -2
  32. donna/primitives/artifacts/workflow.py +11 -2
  33. donna/primitives/directives/list.py +86 -0
  34. donna/primitives/directives/view.py +52 -11
  35. donna/primitives/operations/finish_workflow.py +13 -2
  36. donna/primitives/operations/output.py +87 -0
  37. donna/primitives/operations/request_action.py +3 -9
  38. donna/primitives/operations/run_script.py +2 -2
  39. donna/protocol/utils.py +22 -0
  40. donna/workspaces/artifacts.py +238 -0
  41. donna/{world → workspaces}/artifacts_discovery.py +1 -1
  42. donna/{world → workspaces}/config.py +13 -6
  43. donna/{world → workspaces}/errors.py +55 -45
  44. donna/workspaces/initialization.py +78 -0
  45. donna/{world → workspaces}/markdown.py +21 -26
  46. donna/{world → workspaces}/sources/base.py +2 -2
  47. donna/{world → workspaces}/sources/markdown.py +7 -6
  48. donna/{world → workspaces}/templates.py +4 -4
  49. donna/{world → workspaces}/tmp.py +19 -1
  50. donna/{world → workspaces}/worlds/base.py +5 -2
  51. donna/{world → workspaces}/worlds/filesystem.py +23 -9
  52. donna/{world → workspaces}/worlds/python.py +12 -9
  53. {donna-0.2.0.dist-info → donna-0.2.1.dist-info}/METADATA +4 -1
  54. donna-0.2.1.dist-info/RECORD +92 -0
  55. {donna-0.2.0.dist-info → donna-0.2.1.dist-info}/WHEEL +1 -1
  56. donna/artifacts/work/do_it.md +0 -142
  57. donna/artifacts/work/do_it_fast.md +0 -98
  58. donna/artifacts/work/planning.md +0 -245
  59. donna/cli/commands/projects.py +0 -49
  60. donna/world/artifacts.py +0 -122
  61. donna/world/initialization.py +0 -42
  62. donna/world/worlds/__init__.py +0 -0
  63. donna-0.2.0.dist-info/RECORD +0 -85
  64. /donna/{artifacts/work → workspaces}/__init__.py +0 -0
  65. /donna/{world → workspaces}/sources/__init__.py +0 -0
  66. /donna/{world → workspaces/worlds}/__init__.py +0 -0
  67. {donna-0.2.0.dist-info → donna-0.2.1.dist-info}/entry_points.txt +0 -0
  68. {donna-0.2.0.dist-info → donna-0.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -8,11 +8,11 @@ from donna.core.errors import ErrorsList
8
8
  from donna.core.result import Err, Ok, Result
9
9
  from donna.domain.ids import PythonImportPath, WorldId
10
10
  from donna.machine.primitives import resolve_primitive
11
- from donna.world import errors as world_errors
12
- from donna.world.sources.base import SourceConfig as SourceConfigValue
13
- from donna.world.sources.base import SourceConstructor
14
- from donna.world.worlds.base import World as BaseWorld
15
- from donna.world.worlds.base import WorldConstructor
11
+ from donna.workspaces import errors as world_errors
12
+ from donna.workspaces.sources.base import SourceConfig as SourceConfigValue
13
+ from donna.workspaces.sources.base import SourceConstructor
14
+ from donna.workspaces.worlds.base import World as BaseWorld
15
+ from donna.workspaces.worlds.base import WorldConstructor
16
16
 
17
17
  DONNA_DIR_NAME = ".donna"
18
18
  DONNA_CONFIG_NAME = "config.toml"
@@ -152,7 +152,14 @@ class Config(BaseEntity):
152
152
  if source.kind == kind:
153
153
  return Ok(source)
154
154
 
155
- return Err([world_errors.SourceConfigNotConfigured(kind=kind)])
155
+ return Err(
156
+ [
157
+ world_errors.SourceConfigNotConfigured(
158
+ source_id=kind,
159
+ kind=kind,
160
+ )
161
+ ]
162
+ )
156
163
 
157
164
  def find_source_for_extension(self, extension: str) -> SourceConfigValue | None:
158
165
  for source in self._sources_instances:
@@ -5,64 +5,82 @@ from donna.domain.ids import ArtifactId, FullArtifactId, WorldId
5
5
 
6
6
 
7
7
  class InternalError(core_errors.InternalError):
8
- """Base class for internal errors in donna.world."""
8
+ """Base class for internal errors in donna.workspaces."""
9
9
 
10
10
 
11
- class WorldError(core_errors.EnvironmentError):
12
- cell_kind: str = "world_error"
11
+ class WorkspaceError(core_errors.EnvironmentError):
12
+ cell_kind: str = "workspace_error"
13
13
 
14
14
 
15
- class WorldConfigError(WorldError):
16
- cell_kind: str = "world_config_error"
15
+ class WorkspaceConfigError(WorkspaceError):
16
+ cell_kind: str = "workspace_config_error"
17
17
  config_path: pathlib.Path
18
18
 
19
19
  def content_intro(self) -> str:
20
- return f"Error in world config file '{self.config_path}'"
20
+ return f"Error in workspace config file '{self.config_path}'"
21
21
 
22
22
 
23
- class ConfigParseFailed(WorldConfigError):
24
- code: str = "donna.world.config_parse_failed"
23
+ class ConfigParseFailed(WorkspaceConfigError):
24
+ code: str = "donna.workspaces.config_parse_failed"
25
25
  message: str = "Failed to parse config file: {error.details}"
26
26
  details: str
27
27
 
28
28
 
29
- class ConfigValidationFailed(WorldConfigError):
30
- code: str = "donna.world.config_validation_failed"
29
+ class ConfigValidationFailed(WorkspaceConfigError):
30
+ code: str = "donna.workspaces.config_validation_failed"
31
31
  message: str = "Failed to validate config file: {error.details}"
32
32
  details: str
33
33
 
34
34
 
35
+ class WorkspaceAlreadyInitialized(WorkspaceError):
36
+ code: str = "donna.workspaces.workspace_already_initialized"
37
+ message: str = "Workspace already initialized at `{error.project_dir}`"
38
+ ways_to_fix: list[str] = [
39
+ "Continue using the existing workspace.",
40
+ "Remove the existing `.donna` directory if you want to reinitialize.",
41
+ "Choose a different project directory.",
42
+ ]
43
+ project_dir: pathlib.Path
44
+
45
+
46
+ class WorldError(WorkspaceError):
47
+ cell_kind: str = "world_error"
48
+ world_id: WorldId
49
+
50
+
35
51
  class WorldNotConfigured(WorldError):
36
- code: str = "donna.world.world_not_configured"
52
+ code: str = "donna.workspaces.world_not_configured"
37
53
  message: str = "World with id `{error.world_id}` is not configured"
38
- world_id: WorldId
39
54
 
40
55
 
41
- class SourceConfigNotConfigured(WorldError):
42
- code: str = "donna.world.source_config_not_configured"
56
+ class SourceError(WorkspaceError):
57
+ cell_kind: str = "source_error"
58
+ source_id: str
59
+
60
+
61
+ class SourceConfigNotConfigured(SourceError):
62
+ code: str = "donna.workspaces.source_config_not_configured"
43
63
  message: str = "Source config `{error.kind}` is not configured"
44
64
  kind: str
45
65
 
46
66
 
47
67
  class WorldReadonly(WorldError):
48
- code: str = "donna.world.world_readonly"
68
+ code: str = "donna.workspaces.world_readonly"
49
69
  message: str = "World `{error.world_id}` is read-only"
50
70
  ways_to_fix: list[str] = [
51
71
  "Use a world configured with readonly = false. Most likely they are `project` and `session`.",
52
72
  ]
53
- world_id: WorldId
54
73
 
55
74
 
56
75
  class WorldStateStorageUnsupported(WorldError):
57
- code: str = "donna.world.state_storage_unsupported"
76
+ code: str = "donna.workspaces.state_storage_unsupported"
58
77
  message: str = "World `{error.world_id}` does not support state storage"
59
78
  ways_to_fix: list[str] = [
60
79
  "Use the session world.",
61
80
  ]
62
- world_id: WorldId
63
81
 
64
82
 
65
- class ArtifactError(WorldError):
83
+ class ArtifactError(WorkspaceError):
66
84
  cell_kind: str = "artifact_error"
67
85
  artifact_id: ArtifactId
68
86
  world_id: WorldId
@@ -72,7 +90,7 @@ class ArtifactError(WorldError):
72
90
 
73
91
 
74
92
  class ArtifactNotFound(ArtifactError):
75
- code: str = "donna.world.artifact_not_found"
93
+ code: str = "donna.workspaces.artifact_not_found"
76
94
  message: str = "Artifact `{error.artifact_id}` does not exist in world `{error.world_id}`"
77
95
  ways_to_fix: list[str] = [
78
96
  "Check the artifact id for typos.",
@@ -81,7 +99,7 @@ class ArtifactNotFound(ArtifactError):
81
99
 
82
100
 
83
101
  class ArtifactMultipleFiles(ArtifactError):
84
- code: str = "donna.world.artifact_multiple_files"
102
+ code: str = "donna.workspaces.artifact_multiple_files"
85
103
  message: str = "Artifact `{error.artifact_id}` has multiple files in world `{error.world_id}`"
86
104
  ways_to_fix: list[str] = [
87
105
  "Keep a single source file per artifact in the world.",
@@ -89,7 +107,7 @@ class ArtifactMultipleFiles(ArtifactError):
89
107
 
90
108
 
91
109
  class UnsupportedArtifactSourceExtension(ArtifactError):
92
- code: str = "donna.world.unsupported_artifact_source_extension"
110
+ code: str = "donna.workspaces.unsupported_artifact_source_extension"
93
111
  message: str = "Unsupported artifact source extension `{error.extension}` in world `{error.world_id}`"
94
112
  ways_to_fix: list[str] = [
95
113
  "Use a supported extension for the configured sources.",
@@ -97,7 +115,7 @@ class UnsupportedArtifactSourceExtension(ArtifactError):
97
115
  extension: str
98
116
 
99
117
 
100
- class MarkdownError(WorldError):
118
+ class MarkdownError(WorkspaceError):
101
119
  cell_kind: str = "markdown_error"
102
120
  artifact_id: FullArtifactId | None = None
103
121
 
@@ -108,7 +126,7 @@ class MarkdownError(WorldError):
108
126
  return f"Error in markdown artifact '{self.artifact_id}'"
109
127
 
110
128
 
111
- class TemplateDirectiveError(WorldError):
129
+ class TemplateDirectiveError(WorkspaceError):
112
130
  cell_kind: str = "template_directive_error"
113
131
  artifact_id: FullArtifactId | None = None
114
132
 
@@ -120,14 +138,14 @@ class TemplateDirectiveError(WorldError):
120
138
 
121
139
 
122
140
  class DirectivePathIncomplete(TemplateDirectiveError):
123
- code: str = "donna.world.directive_path_incomplete"
141
+ code: str = "donna.workspaces.directive_path_incomplete"
124
142
  message: str = "Directive path must include module and directive parts, got `{error.path}`."
125
143
  ways_to_fix: list[str] = ["Use a directive path with both module and directive names."]
126
144
  path: str
127
145
 
128
146
 
129
147
  class DirectiveModuleNotImportable(TemplateDirectiveError):
130
- code: str = "donna.world.directive_module_not_importable"
148
+ code: str = "donna.workspaces.directive_module_not_importable"
131
149
  message: str = "Directive module `{error.module_path}` is not importable."
132
150
  ways_to_fix: list[str] = [
133
151
  "Check the module path for typos.",
@@ -137,7 +155,7 @@ class DirectiveModuleNotImportable(TemplateDirectiveError):
137
155
 
138
156
 
139
157
  class DirectiveNotAvailable(TemplateDirectiveError):
140
- code: str = "donna.world.directive_not_available"
158
+ code: str = "donna.workspaces.directive_not_available"
141
159
  message: str = "Directive `{error.module_path}.{error.directive_name}` is not available."
142
160
  ways_to_fix: list[str] = [
143
161
  "Check the directive name for typos.",
@@ -148,7 +166,7 @@ class DirectiveNotAvailable(TemplateDirectiveError):
148
166
 
149
167
 
150
168
  class DirectiveNotDirective(TemplateDirectiveError):
151
- code: str = "donna.world.directive_not_directive"
169
+ code: str = "donna.workspaces.directive_not_directive"
152
170
  message: str = "`{error.module_path}.{error.directive_name}` is not a directive."
153
171
  ways_to_fix: list[str] = [
154
172
  "Check the directive path for typos.",
@@ -160,7 +178,7 @@ class DirectiveNotDirective(TemplateDirectiveError):
160
178
 
161
179
 
162
180
  class DirectiveUnexpectedError(TemplateDirectiveError):
163
- code: str = "donna.world.directive_unexpected_error"
181
+ code: str = "donna.workspaces.directive_unexpected_error"
164
182
  message: str = "Unexpected error while applying directive `{error.directive_path}`: {error.details}"
165
183
  ways_to_fix: list[str] = [
166
184
  "Check the documentation for the directive to ensure correct usage.",
@@ -171,7 +189,7 @@ class DirectiveUnexpectedError(TemplateDirectiveError):
171
189
 
172
190
 
173
191
  class MarkdownUnsupportedCodeFormat(MarkdownError):
174
- code: str = "donna.world.markdown_unsupported_code_format"
192
+ code: str = "donna.workspaces.markdown_unsupported_code_format"
175
193
  message: str = "Unsupported code block format `{error.format}`"
176
194
  ways_to_fix: list[str] = [
177
195
  "Use one of the supported formats: json, yaml, yml, toml.",
@@ -180,36 +198,28 @@ class MarkdownUnsupportedCodeFormat(MarkdownError):
180
198
 
181
199
 
182
200
  class MarkdownMultipleH1Sections(MarkdownError):
183
- code: str = "donna.world.markdown_multiple_h1_sections"
201
+ code: str = "donna.workspaces.markdown_multiple_h1_sections"
184
202
  message: str = "Multiple H1 sections are not supported"
185
203
  ways_to_fix: list[str] = [
186
204
  "Keep a single H1 section in the artifact.",
187
205
  ]
188
206
 
189
207
 
190
- class MarkdownMultipleH1Titles(MarkdownError):
191
- code: str = "donna.world.markdown_multiple_h1_titles"
192
- message: str = "Multiple H1 titles are not supported"
193
- ways_to_fix: list[str] = [
194
- "Keep a single H1 title in the artifact.",
195
- ]
196
-
197
-
198
- class MarkdownH2BeforeH1Title(MarkdownError):
199
- code: str = "donna.world.markdown_h2_before_h1_title"
200
- message: str = "H2 section found before H1 title"
208
+ class MarkdownH1SectionMustBeFirst(MarkdownError):
209
+ code: str = "donna.workspaces.markdown_h1_section_must_be_first"
210
+ message: str = "H1 section must be the first section in the artifact"
201
211
  ways_to_fix: list[str] = [
202
- "Ensure the first heading is an H1 title before any H2 sections.",
212
+ "Ensure the H1 section is the first section in the artifact.",
203
213
  ]
204
214
 
205
215
 
206
216
  class MarkdownArtifactWithoutSections(MarkdownError):
207
- code: str = "donna.world.markdown_artifact_without_sections"
217
+ code: str = "donna.workspaces.markdown_artifact_without_sections"
208
218
  message: str = "Artifact MUST have at least one section"
209
219
 
210
220
 
211
221
  class PrimitiveDoesNotSupportMarkdown(MarkdownError):
212
- code: str = "donna.world.primitive_does_not_support_markdown"
222
+ code: str = "donna.workspaces.primitive_does_not_support_markdown"
213
223
  message: str = "Primitive {error.primitive_id} cannot construct artifact section from the Markdown source"
214
224
  ways_to_fix: list[str] = [
215
225
  "Ensure the section kind points to a primitive that supports Markdown sections.",
@@ -0,0 +1,78 @@
1
+ import pathlib
2
+ import tomllib
3
+
4
+ import tomli_w
5
+
6
+ from donna.core import errors as core_errors
7
+ from donna.core import utils
8
+ from donna.core.result import Err, Ok, Result, unwrap_to_error
9
+ from donna.domain.ids import WorldId
10
+ from donna.workspaces import config
11
+ from donna.workspaces import errors as world_errors
12
+
13
+
14
+ @unwrap_to_error
15
+ def initialize_runtime() -> Result[None, core_errors.ErrorsList]:
16
+ """Initialize the runtime environment for the application.
17
+
18
+ This function MUST be called before any other operations.
19
+ """
20
+ project_dir = utils.discover_project_dir(config.DONNA_DIR_NAME).unwrap()
21
+
22
+ config.project_dir.set(project_dir)
23
+
24
+ config_dir = project_dir / config.DONNA_DIR_NAME
25
+
26
+ config.config_dir.set(config_dir)
27
+
28
+ config_path = config_dir / config.DONNA_CONFIG_NAME
29
+
30
+ if not config_path.exists():
31
+ config.config.set(config.Config())
32
+ return Ok(None)
33
+
34
+ try:
35
+ data = tomllib.loads(config_path.read_text(encoding="utf-8"))
36
+ except tomllib.TOMLDecodeError as e:
37
+ return Err([world_errors.ConfigParseFailed(config_path=config_path, details=str(e))])
38
+
39
+ try:
40
+ loaded_config = config.Config.model_validate(data)
41
+ except Exception as e:
42
+ return Err([world_errors.ConfigValidationFailed(config_path=config_path, details=str(e))])
43
+
44
+ config.config.set(loaded_config)
45
+
46
+ return Ok(None)
47
+
48
+
49
+ @unwrap_to_error
50
+ def initialize_workspace(project_dir: pathlib.Path) -> Result[None, core_errors.ErrorsList]:
51
+ """Initialize the physical workspace for the project (`.donna` directory)."""
52
+ project_dir = project_dir.resolve()
53
+ workspace_dir = project_dir / config.DONNA_DIR_NAME
54
+
55
+ if workspace_dir.exists():
56
+ return Err([world_errors.WorkspaceAlreadyInitialized(project_dir=project_dir)])
57
+
58
+ config.project_dir.set(project_dir)
59
+ config.config_dir.set(workspace_dir)
60
+
61
+ workspace_dir.mkdir(parents=True, exist_ok=True)
62
+
63
+ default_config = config.Config()
64
+ config.config.set(default_config)
65
+
66
+ config_path = workspace_dir / config.DONNA_CONFIG_NAME
67
+ config_path.write_text(
68
+ tomli_w.dumps(default_config.model_dump(mode="json")),
69
+ encoding="utf-8",
70
+ )
71
+
72
+ project_world = default_config.get_world(WorldId(config.DONNA_WORLD_PROJECT_DIR_NAME)).unwrap()
73
+ project_world.initialize()
74
+
75
+ session_world = default_config.get_world(WorldId(config.DONNA_WORLD_SESSION_DIR_NAME)).unwrap()
76
+ session_world.initialize()
77
+
78
+ return Ok(None)
@@ -1,7 +1,6 @@
1
1
  import enum
2
2
  from typing import Any
3
3
 
4
- import pydantic
5
4
  from markdown_it import MarkdownIt
6
5
  from markdown_it.token import Token
7
6
  from markdown_it.tree import SyntaxTreeNode
@@ -11,7 +10,7 @@ from donna.core.entities import BaseEntity
11
10
  from donna.core.errors import ErrorsList
12
11
  from donna.core.result import Err, Ok, Result, unwrap_to_error
13
12
  from donna.domain.ids import FullArtifactId
14
- from donna.world import errors as world_errors
13
+ from donna.workspaces import errors as world_errors
15
14
 
16
15
 
17
16
  class SectionLevel(str, enum.Enum):
@@ -54,8 +53,6 @@ class SectionSource(BaseEntity):
54
53
  original_tokens: list[Token]
55
54
  analysis_tokens: list[Token]
56
55
 
57
- model_config = pydantic.ConfigDict(frozen=False)
58
-
59
56
  def _as_markdown(self, tokens: list[Token], with_title: bool) -> str:
60
57
  parts = []
61
58
 
@@ -110,15 +107,21 @@ def clear_heading(text: str) -> str:
110
107
  def _parse_h1(
111
108
  sections: list[SectionSource], node: SyntaxTreeNode, artifact_id: FullArtifactId | None
112
109
  ) -> Result[SyntaxTreeNode | None, ErrorsList]:
113
- section = sections[-1]
114
-
115
- if section.level != SectionLevel.h1:
110
+ if sections and any(section.level == SectionLevel.h1 for section in sections):
116
111
  return Err([world_errors.MarkdownMultipleH1Sections(artifact_id=artifact_id)])
117
112
 
118
- if section.title is not None:
119
- return Err([world_errors.MarkdownMultipleH1Titles(artifact_id=artifact_id)])
113
+ if sections:
114
+ return Err([world_errors.MarkdownH1SectionMustBeFirst(artifact_id=artifact_id)])
115
+
116
+ new_section = SectionSource(
117
+ level=SectionLevel.h1,
118
+ title=clear_heading(render_back(node.to_tokens()).strip()),
119
+ original_tokens=[],
120
+ analysis_tokens=[],
121
+ configs=[],
122
+ )
120
123
 
121
- section.title = clear_heading(render_back(node.to_tokens()).strip())
124
+ sections.append(new_section)
122
125
 
123
126
  return Ok(node.next_sibling)
124
127
 
@@ -126,10 +129,9 @@ def _parse_h1(
126
129
  def _parse_h2(
127
130
  sections: list[SectionSource], node: SyntaxTreeNode, artifact_id: FullArtifactId | None
128
131
  ) -> Result[SyntaxTreeNode | None, ErrorsList]:
129
- section = sections[-1]
130
132
 
131
- if section.title is None:
132
- return Err([world_errors.MarkdownH2BeforeH1Title(artifact_id=artifact_id)])
133
+ if not sections:
134
+ return Err([world_errors.MarkdownH1SectionMustBeFirst(artifact_id=artifact_id)])
133
135
 
134
136
  new_section = SectionSource(
135
137
  level=SectionLevel.h2,
@@ -147,7 +149,6 @@ def _parse_h2(
147
149
  def _parse_heading(
148
150
  sections: list[SectionSource], node: SyntaxTreeNode, artifact_id: FullArtifactId | None
149
151
  ) -> Result[SyntaxTreeNode | None, ErrorsList]:
150
- section = sections[-1]
151
152
 
152
153
  if node.tag == "h1":
153
154
  return _parse_h1(sections, node, artifact_id)
@@ -155,7 +156,11 @@ def _parse_heading(
155
156
  if node.tag == "h2":
156
157
  return _parse_h2(sections, node, artifact_id)
157
158
 
158
- section.original_tokens.extend(node.to_tokens())
159
+ if not sections:
160
+ return Err([world_errors.MarkdownH1SectionMustBeFirst(artifact_id=artifact_id)])
161
+
162
+ sections[-1].original_tokens.extend(node.to_tokens())
163
+
159
164
  return Ok(node.next_sibling)
160
165
 
161
166
 
@@ -231,15 +236,7 @@ def parse( # noqa: CCR001, CFQ001
231
236
  # we do not need root node
232
237
  node: SyntaxTreeNode | None = SyntaxTreeNode(tokens).children[0]
233
238
 
234
- sections: list[SectionSource] = [
235
- SectionSource(
236
- level=SectionLevel.h1,
237
- title=None,
238
- original_tokens=[],
239
- analysis_tokens=[],
240
- configs=[],
241
- )
242
- ]
239
+ sections: list[SectionSource] = []
243
240
 
244
241
  while node is not None:
245
242
 
@@ -263,5 +260,3 @@ def parse( # noqa: CCR001, CFQ001
263
260
  node = node.next_sibling
264
261
 
265
262
  return Ok(sections)
266
-
267
- return sections
@@ -13,8 +13,8 @@ from donna.machine.primitives import Primitive
13
13
  if TYPE_CHECKING:
14
14
  from donna.domain.ids import FullArtifactId
15
15
  from donna.machine.artifacts import Artifact
16
- from donna.world.artifacts import ArtifactRenderContext
17
- from donna.world.config import SourceConfig as SourceConfigModel
16
+ from donna.workspaces.artifacts import ArtifactRenderContext
17
+ from donna.workspaces.config import SourceConfig as SourceConfigModel
18
18
 
19
19
 
20
20
  class SourceConfig(BaseEntity, ABC):
@@ -6,11 +6,11 @@ from donna.core.result import Err, Ok, Result, unwrap_to_error
6
6
  from donna.domain.ids import ArtifactSectionId, FullArtifactId, PythonImportPath
7
7
  from donna.machine.artifacts import Artifact, ArtifactSection, ArtifactSectionConfig, ArtifactSectionMeta
8
8
  from donna.machine.primitives import Primitive, resolve_primitive
9
- from donna.world import errors as world_errors
10
- from donna.world import markdown
11
- from donna.world.artifacts import ArtifactRenderContext
12
- from donna.world.sources.base import SourceConfig, SourceConstructor
13
- from donna.world.templates import RenderMode, render
9
+ from donna.workspaces import errors as world_errors
10
+ from donna.workspaces import markdown
11
+ from donna.workspaces.artifacts import ArtifactRenderContext
12
+ from donna.workspaces.sources.base import SourceConfig, SourceConstructor
13
+ from donna.workspaces.templates import RenderMode, render
14
14
 
15
15
 
16
16
  class MarkdownSectionConstructor(Protocol):
@@ -111,6 +111,7 @@ class MarkdownSectionMixin:
111
111
  kind=section_config.kind,
112
112
  title=title,
113
113
  description=description,
114
+ tags=section_config.tags,
114
115
  primary=primary,
115
116
  meta=meta,
116
117
  )
@@ -257,4 +258,4 @@ def _ensure_markdown_constructible(
257
258
 
258
259
 
259
260
  if TYPE_CHECKING:
260
- from donna.world.config import SourceConfig as SourceConfigModel
261
+ from donna.workspaces.config import SourceConfig as SourceConfigModel
@@ -12,10 +12,10 @@ from donna.core.errors import EnvironmentErrorsProxy, ErrorsList
12
12
  from donna.core.result import Err, Ok, Result
13
13
  from donna.domain.ids import FullArtifactId
14
14
  from donna.machine.templates import Directive
15
- from donna.world import errors as world_errors
15
+ from donna.workspaces import errors as world_errors
16
16
 
17
17
  if TYPE_CHECKING:
18
- from donna.world.artifacts import ArtifactRenderContext
18
+ from donna.workspaces.artifacts import ArtifactRenderContext
19
19
 
20
20
 
21
21
  class RenderMode(enum.Enum):
@@ -57,7 +57,7 @@ class DirectivePathBuilder:
57
57
  return DirectivePathBuilder(self._parts + (name,))
58
58
 
59
59
  @jinja2.pass_context
60
- def __call__(self, context: jinja2.runtime.Context, *argv: object) -> object: # noqa: CCR001
60
+ def __call__(self, context: jinja2.runtime.Context, *argv: object, **kwargs: object) -> object: # noqa: CCR001
61
61
  artifact_id = context.get("artifact_id")
62
62
  directive_path = ".".join(self._parts)
63
63
  if len(self._parts) < 2:
@@ -112,7 +112,7 @@ class DirectivePathBuilder:
112
112
  )
113
113
 
114
114
  try:
115
- result = directive.apply_directive(context, *argv)
115
+ result = directive.apply_directive(context, *argv, **kwargs)
116
116
  except EnvironmentErrorsProxy:
117
117
  raise
118
118
  except core_errors.InternalError:
@@ -3,7 +3,7 @@ import shutil
3
3
  import time
4
4
 
5
5
  from donna.cli.types import FullArtifactIdArgument
6
- from donna.world.config import config, config_dir
6
+ from donna.workspaces.config import config, config_dir
7
7
 
8
8
 
9
9
  def dir() -> pathlib.Path:
@@ -29,5 +29,23 @@ def file_for_artifact(artifact_id: FullArtifactIdArgument, extention: str) -> pa
29
29
  return directory / artifact_file_name
30
30
 
31
31
 
32
+ def file_for_slug(slug: str, extension: str) -> pathlib.Path:
33
+ directory = dir()
34
+
35
+ directory.mkdir(parents=True, exist_ok=True)
36
+
37
+ normalized_slug = slug.replace("/", ".").replace("\\", ".")
38
+ normalized_extension = extension.lstrip(".")
39
+ artifact_file_name = f"{normalized_slug}.{int(time.time() * 1000)}.{normalized_extension}"
40
+
41
+ return directory / artifact_file_name
42
+
43
+
44
+ def create_file_for_slug(slug: str, extension: str) -> pathlib.Path:
45
+ path = file_for_slug(slug, extension)
46
+ path.touch()
47
+ return path
48
+
49
+
32
50
  def clear() -> None:
33
51
  shutil.rmtree(dir())
@@ -11,8 +11,8 @@ from donna.machine.artifacts import Artifact
11
11
  from donna.machine.primitives import Primitive
12
12
 
13
13
  if TYPE_CHECKING:
14
- from donna.world.artifacts import ArtifactRenderContext
15
- from donna.world.config import WorldConfig
14
+ from donna.workspaces.artifacts import ArtifactRenderContext
15
+ from donna.workspaces.config import WorldConfig
16
16
 
17
17
 
18
18
  class World(BaseEntity, ABC):
@@ -36,6 +36,9 @@ class World(BaseEntity, ABC):
36
36
  self, artifact_id: ArtifactId, content: bytes, extension: str
37
37
  ) -> Result[None, ErrorsList]: ... # noqa: E704
38
38
 
39
+ @abstractmethod
40
+ def remove(self, artifact_id: ArtifactId) -> Result[None, ErrorsList]: ... # noqa: E704
41
+
39
42
  @abstractmethod
40
43
  def file_extension_for(self, artifact_id: ArtifactId) -> Result[str, ErrorsList]: ... # noqa: E704
41
44
 
@@ -6,14 +6,14 @@ from donna.core.errors import ErrorsList
6
6
  from donna.core.result import Err, Ok, Result, unwrap_to_error
7
7
  from donna.domain.ids import ArtifactId, FullArtifactId, FullArtifactIdPattern
8
8
  from donna.machine.artifacts import Artifact
9
- from donna.world import errors as world_errors
10
- from donna.world.artifacts import ArtifactRenderContext
11
- from donna.world.artifacts_discovery import ArtifactListingNode, list_artifacts_by_pattern
12
- from donna.world.worlds.base import World as BaseWorld
13
- from donna.world.worlds.base import WorldConstructor
9
+ from donna.workspaces import errors as world_errors
10
+ from donna.workspaces.artifacts import ArtifactRenderContext
11
+ from donna.workspaces.artifacts_discovery import ArtifactListingNode, list_artifacts_by_pattern
12
+ from donna.workspaces.worlds.base import World as BaseWorld
13
+ from donna.workspaces.worlds.base import WorldConstructor
14
14
 
15
15
  if TYPE_CHECKING:
16
- from donna.world.config import SourceConfigValue, WorldConfig
16
+ from donna.workspaces.config import SourceConfigValue, WorldConfig
17
17
 
18
18
 
19
19
  class World(BaseWorld):
@@ -35,7 +35,7 @@ class World(BaseWorld):
35
35
  if not parent.exists():
36
36
  return Ok(None)
37
37
 
38
- from donna.world.config import config
38
+ from donna.workspaces.config import config
39
39
 
40
40
  supported_extensions = config().supported_extensions()
41
41
  matches = [
@@ -55,7 +55,7 @@ class World(BaseWorld):
55
55
  def _get_source_by_filename(
56
56
  self, artifact_id: ArtifactId, filename: str
57
57
  ) -> Result["SourceConfigValue", ErrorsList]:
58
- from donna.world.config import config
58
+ from donna.workspaces.config import config
59
59
 
60
60
  extension = pathlib.Path(filename).suffix
61
61
  source_config = config().find_source_for_extension(extension)
@@ -89,7 +89,7 @@ class World(BaseWorld):
89
89
  full_id = FullArtifactId((self.id, artifact_id))
90
90
 
91
91
  extension = pathlib.Path(path.name).suffix
92
- from donna.world.config import config
92
+ from donna.workspaces.config import config
93
93
 
94
94
  source_config = config().find_source_for_extension(extension)
95
95
  if source_config is None:
@@ -123,6 +123,20 @@ class World(BaseWorld):
123
123
  path.write_bytes(content)
124
124
  return Ok(None)
125
125
 
126
+ @unwrap_to_error
127
+ def remove(self, artifact_id: ArtifactId) -> Result[None, ErrorsList]:
128
+ if self.readonly:
129
+ return Err([world_errors.WorldReadonly(world_id=self.id)])
130
+
131
+ path = self._resolve_artifact_file(artifact_id).unwrap()
132
+
133
+ if path is None:
134
+ return Ok(None)
135
+
136
+ path.unlink()
137
+
138
+ return Ok(None)
139
+
126
140
  @unwrap_to_error
127
141
  def file_extension_for(self, artifact_id: ArtifactId) -> Result[str, ErrorsList]:
128
142
  path = self._resolve_artifact_file(artifact_id).unwrap()