From 3a63ec80f116642ae9485b68bd7d7ccb61608ddf Mon Sep 17 00:00:00 2001 From: "Aliaksei Yaletski (Tiendil)" Date: Sat, 28 Feb 2026 14:08:04 +0100 Subject: [PATCH 1/2] gh-60 Fixed CLI artifact extension recognition for `donna artifacts update` --- changes/unreleased.md | 2 ++ donna/artifacts/usage/cli.md | 2 +- donna/workspaces/artifacts.py | 62 ++++++++++++++++++++++++++--------- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/changes/unreleased.md b/changes/unreleased.md index ad783e1..16e3058 100644 --- a/changes/unreleased.md +++ b/changes/unreleased.md @@ -1,2 +1,4 @@ +### Changes - gh-62 Added `donna version` command that prints the version of the tool. +- gh-60 Fixed CLI artifact extension recognition for `donna artifacts update`. diff --git a/donna/artifacts/usage/cli.md b/donna/artifacts/usage/cli.md index 85104a8..3787bc2 100644 --- a/donna/artifacts/usage/cli.md +++ b/donna/artifacts/usage/cli.md @@ -146,7 +146,7 @@ Use the next commands to work with artifacts: - `donna -p artifacts view ` — get the meaningful (rendered) content of all matching artifacts. This command shows the rendered information about each artifact. Use this command when you need to read artifact content. - `donna -p artifacts fetch :` — download the original source of the artifact content, outputs the file path to the artifact's copy, you can change. Use this command when you need to change the content of the artifact. - `donna -p artifacts tmp .` — create a temporary file for artifact-related work and output its path. -- `donna -p artifacts update : [--extension ]` — upload content from a file path or from stdin (`-`) as the artifact. If `--extension` is omitted, Donna infers it from the existing target artifact. +- `donna -p artifacts update : [--extension ]` — upload content from a file path or from stdin (`-`) as the artifact. Donna gets an extension from three sources: `--extension`, ``, and a stored artifact; if there are multiple extensions or no extensions at all, Donna returns an error. - `donna -p artifacts copy ` — copy an artifact source to another artifact ID (can be in a different world). This overwrites the destination if it exists. - `donna -p artifacts move ` — copy an artifact source to another artifact ID and remove the original. This overwrites the destination if it exists. - `donna -p artifacts remove ` — remove artifacts matching a pattern. Use this command when you need to delete artifacts. diff --git a/donna/workspaces/artifacts.py b/donna/workspaces/artifacts.py index 969ee52..806f6ea 100644 --- a/donna/workspaces/artifacts.py +++ b/donna/workspaces/artifacts.py @@ -48,10 +48,11 @@ class CanNotRemoveReadonlyWorld(ArtifactRemoveError): class ArtifactExtensionCannotBeInferred(ArtifactUpdateError): code: str = "donna.workspaces.artifact_extension_cannot_be_inferred" - message: str = "Cannot infer artifact extension. Provide `--extension` or update an existing artifact." + message: str = "Cannot infer artifact extension." ways_to_fix: list[str] = [ "Pass `--extension ` when updating the artifact.", - "Create/update an artifact that already exists and has a known extension.", + "Provide an input path with extension (for example `*.md`).", + "Update an artifact that already exists and has a known extension if that is applicable.", ] @@ -126,28 +127,59 @@ def update_artifact( # noqa: CCR001 expected_extension = artifact_file_extension(full_id).unwrap_or(None) requested_extension = extension.lstrip(".").lower() if extension is not None else None + inferred_extension = input.suffix.lstrip(".").lower() or None - if expected_extension is None and requested_extension is None: - return Err([ArtifactExtensionCannotBeInferred(artifact_id=full_id, path=input)]) - - if expected_extension is None and requested_extension is not None: - source_suffix = requested_extension - elif expected_extension is not None and requested_extension is None: - source_suffix = expected_extension - elif expected_extension != requested_extension: + def mismatch_error(a: str, b: str) -> Result[None, ErrorsList]: return Err( [ ArtifactExtensionMismatch( artifact_id=full_id, path=input, - provided_extension=requested_extension or "", - existing_extension=expected_extension or "", + provided_extension=a, + existing_extension=b, ) ] ) - else: - assert expected_extension is not None - source_suffix = expected_extension + + match (expected_extension, requested_extension, inferred_extension): + case (None, None, None): + return Err([ArtifactExtensionCannotBeInferred(artifact_id=full_id, path=input)]) + + case (None, None, inferred): + source_suffix = inferred + + case (None, requested, None): + source_suffix = requested + + case (None, requested, inferred) if requested == inferred: + source_suffix = requested + + case (None, requested, inferred): + return mismatch_error(requested, inferred) + + case (expected, None, None): + source_suffix = expected + + case (expected, None, inferred) if expected == inferred: + source_suffix = expected + + case (expected, None, inferred): + return mismatch_error(inferred, expected) + + case (expected, requested, None) if expected == requested: + source_suffix = expected + + case (expected, requested, None): + return mismatch_error(requested, expected) + + case (expected, requested, inferred) if expected == requested == inferred: + source_suffix = expected + + case (expected, requested, inferred) if expected != requested: + return mismatch_error(requested, expected) + + case (expected, requested, inferred): + return mismatch_error(inferred, expected) normalized_source_suffix = f".{source_suffix}" From a840eedf1f8ce05c3bbf218b43f938f7874882ef Mon Sep 17 00:00:00 2001 From: "Aliaksei Yaletski (Tiendil)" Date: Sat, 28 Feb 2026 14:10:06 +0100 Subject: [PATCH 2/2] wip --- donna/workspaces/artifacts.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/donna/workspaces/artifacts.py b/donna/workspaces/artifacts.py index 806f6ea..5f8cf57 100644 --- a/donna/workspaces/artifacts.py +++ b/donna/workspaces/artifacts.py @@ -155,6 +155,8 @@ def mismatch_error(a: str, b: str) -> Result[None, ErrorsList]: source_suffix = requested case (None, requested, inferred): + assert requested is not None + assert inferred is not None return mismatch_error(requested, inferred) case (expected, None, None): @@ -164,21 +166,29 @@ def mismatch_error(a: str, b: str) -> Result[None, ErrorsList]: source_suffix = expected case (expected, None, inferred): + assert expected is not None + assert inferred is not None return mismatch_error(inferred, expected) case (expected, requested, None) if expected == requested: source_suffix = expected case (expected, requested, None): + assert expected is not None + assert requested is not None return mismatch_error(requested, expected) case (expected, requested, inferred) if expected == requested == inferred: source_suffix = expected case (expected, requested, inferred) if expected != requested: + assert expected is not None + assert requested is not None return mismatch_error(requested, expected) case (expected, requested, inferred): + assert expected is not None + assert inferred is not None return mismatch_error(inferred, expected) normalized_source_suffix = f".{source_suffix}"