From 00b5ddb2e8a355a0aa09fb1db1fc3a3f5e23da0e Mon Sep 17 00:00:00 2001 From: VitjanZ Date: Mon, 15 Jun 2026 16:15:58 +0200 Subject: [PATCH 1/4] Implemented a check for larger base64 encoded blobs --- file_utils.py | 18 ++++++-- plain2code.py | 2 + plain2code_exceptions.py | 5 +++ plain2code_utils.py | 18 ++++++++ plain_file.py | 13 +++++- tests/data/sample_base64_image.txt | 1 + tests/test_file_utils.py | 42 ++++++++++++++++++- tests/test_git_utils.py | 66 ++++++++++++++++++++---------- 8 files changed, 138 insertions(+), 27 deletions(-) create mode 100644 tests/data/sample_base64_image.txt diff --git a/file_utils.py b/file_utils.py index e12260ba..97697136 100644 --- a/file_utils.py +++ b/file_utils.py @@ -8,8 +8,9 @@ import plain_spec from plain2code_console import console -from plain2code_exceptions import UnsupportedResourceType +from plain2code_exceptions import UnsupportedBase64Content, UnsupportedResourceType from plain2code_nodes import Plain2CodeIncludeTag, Plain2CodeLoaderMixin +from plain2code_utils import find_large_base64_blob from plain_modules import CODEPLAIN_MEMORY_SUBFOLDER, CODEPLAIN_METADATA_FOLDER BINARY_FILE_EXTENSIONS = [".pyc"] @@ -196,7 +197,8 @@ def load_linked_resources(template_dirs: list[str], resources_list, module_name: ) if content is None: - raise FileNotFoundError(f""" + raise FileNotFoundError( + f""" Resource file {file_name} not found. Resource files are searched in the following order (highest to lowest precedence): 1. The directory containing your .plain file @@ -204,7 +206,17 @@ def load_linked_resources(template_dirs: list[str], resources_list, module_name: 3. The built-in 'standard_template_library' directory Please ensure that the resource exists in one of these locations, or specify the correct --template-dir if using custom templates. - """) + """ + ) + + blob = find_large_base64_blob(content) + if blob is not None: + raise UnsupportedBase64Content( + f"Referenced resource '{file_name}' in module '{module_name}' contains a large " + f"base64-encoded blob ({len(blob)} characters), such as an embedded image. Inline " + "base64 data is not supported. Remove the base64 data " + "from the resource. If necessary, provide the binary file path in the specification." + ) linked_resources[file_name] = content diff --git a/plain2code.py b/plain2code.py index 6cfc8578..53e39634 100644 --- a/plain2code.py +++ b/plain2code.py @@ -37,6 +37,7 @@ PlainSyntaxError, RenderCancelledError, RenderingCreditBalanceTooLow, + UnsupportedBase64Content, UnsupportedResourceType, ) from plain2code_logger import ( @@ -74,6 +75,7 @@ NetworkConnectionError, ModuleDoesNotExistError, UnsupportedResourceType, + UnsupportedBase64Content, GitNotInstalledError, SystemExit, ) diff --git a/plain2code_exceptions.py b/plain2code_exceptions.py index c18eba6c..8425a573 100644 --- a/plain2code_exceptions.py +++ b/plain2code_exceptions.py @@ -29,6 +29,11 @@ class PlainSyntaxError(Exception): pass +class UnsupportedBase64Content(Exception): + + pass + + class InternalClientError(Exception): pass diff --git a/plain2code_utils.py b/plain2code_utils.py index 1c16358f..7b955698 100644 --- a/plain2code_utils.py +++ b/plain2code_utils.py @@ -1,3 +1,21 @@ +import re +from typing import Optional + +# +MIN_BASE64_BLOB_LENGTH = 8192 + +# Matches a long contiguous base64 / base64url run, optionally preceded by a data: URI header. +_BASE64_BLOB_PATTERN = re.compile( + r"(?:data:[\w.+-]+/[\w.+-]+;base64,)?[A-Za-z0-9+/_-]{%d,}={0,2}" % MIN_BASE64_BLOB_LENGTH +) + + +def find_large_base64_blob(text: str) -> Optional[str]: + """Return the first contiguous base64 blob at or above the threshold, or None.""" + match = _BASE64_BLOB_PATTERN.search(text) + return match.group(0) if match else None + + def format_duration_hms(total_seconds: int) -> str: """Format a duration in seconds as hours, minutes, and seconds (e.g. ``1h 2m 3.45s``, ``45.67s``).""" if total_seconds < 0: diff --git a/plain_file.py b/plain_file.py index c116d334..bb7aeb8a 100644 --- a/plain_file.py +++ b/plain_file.py @@ -17,8 +17,9 @@ import concept_utils import file_utils import plain_spec -from plain2code_exceptions import ModuleDoesNotExistError, PlainSyntaxError +from plain2code_exceptions import ModuleDoesNotExistError, PlainSyntaxError, UnsupportedBase64Content from plain2code_nodes import Plain2CodeIncludeTag, Plain2CodeLoaderMixin +from plain2code_utils import find_large_base64_blob RESOURCE_MARKER = "[resource]" @@ -536,6 +537,16 @@ def read_module_plain_source(module_name: str, template_dirs: list[str]) -> str: plain_source_text = file_utils.open_from(template_dirs, module_name + PLAIN_SOURCE_FILE_EXTENSION) if plain_source_text is None: raise ModuleDoesNotExistError(f"Module does not exist ({module_name}).") + + blob = find_large_base64_blob(plain_source_text) + if blob is not None: + raise UnsupportedBase64Content( + f"Module '{module_name}' contains a base64-encoded blob ({len(blob)} characters) " + "inlined in the specification. This is not supported." + "Remove the base64 data from the .plain file or if necessary," + "include the binary file path in the specification." + ) + return plain_source_text diff --git a/tests/data/sample_base64_image.txt b/tests/data/sample_base64_image.txt new file mode 100644 index 00000000..aab011d9 --- /dev/null +++ b/tests/data/sample_base64_image.txt @@ -0,0 +1 @@ +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BiUUUUCCiiigAooooAKWkpaBiUUUUCCiiigAozRRQAtFFFIYlFFFMQUUUUALSUUGgAooooAKKKKACjNFFABRRRQAUUUUAFFFFABRRRQAUooFLTGhKAKdigVSQnoJiinAZ6U9YXY/dp2C5BRRRWYBRRRQAUUUUAFFFFABRRRQAUUUUAFOYgsSFCg9h2ptFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUGgBaSlpKACiiigAooooAKKKKADNFFFAC0lLSUAFFFFAC0lFFABRRRQAUUUUAHSil60lABRRRQAUUUUAFFFFABRRRQAUUUtACUUUUALS0lLTQwpasWto9zIFUda6C08KOwVpJMZ7VdiHJI5sROwyBTltZWfaqc128OhWsC8pk+pqSOC2gOUiFMjnOdtdAlaMPINmO1W49OitxjJz9a15rgMuAAMVnO7GgOZnGUUUVkahRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFLRQAlFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRTgxAYDHIweKbQAUtJRQAtApKeqljgCrWoABU9vA08gUCrthpM14DhTxXRadoAtmEknOO1NImcrEmjaYsSK7dcdK3fMSJelNykSYBqpNMDwKowepJcTqVIAqhLKKHOSahdcA4zSKiRu5Aqoz81LIwGe9VWIz2pF2OXooorM0CiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigBaKKKBiUUUUCCiiigAooooAKKKKACiiigAooooAWkpaSgAooooAKKKKAFopKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKWkp6jJ6U0AqRO5+VSa2NP0iaQhynHvV3RbHJDPjbXVRLCi4UACtLaGcp9Crp8bQReXtHT0qy0rYp0rRqBgiqcswPQ1Rna4Syk96rsxJpjOd1I0gjXOeaLjUSQnCkmqNxdAcCmT3RY8HiqLMSxNRc0URzTlmpobmmYPeilcpIwaKKKgYUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAC1ZtoDM4GO+Paoooy7YrfsLZUUH1q4omTNWzjNvEqk8gVcM4H8WKg+6uKrshOTWjZjbUsTXQ7HNVjcsxxmonOD1pm4oM0irFgylRzVSS5JzzUck/HWq+7PvUtlxRI7lm602gAk0/YR2qW7GyQynttVaGWoqkDBooooICiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoopaACkpaSgAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAFopKKACiiigAooooAKKKKACiiigAooooAKKKKACiilxQAlFLSgc0AX9N3NIAOgroYl+UYrLsINi5Yc1qg4FbLYzkW1KqoLPVaefsvAoY/L1qpKQSKGyRrSZOSahmlzhc0shABxVbkuDUtmqih2ctUiRZpsa9zUyKBzUtlLQdsopaa1QyhjMDTM0HrSUmyjBoooqjIKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAFIwSD2pKKKACiiigAooooAKKKKACiiigAooooAWkpaSgAooooAKKKKACiiigBaKSlNAxKKKKBBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFAoAdVi0TdJVcda09PjHDVSQNmpDhEwKm8zgAVAp5wKk2YHIqzPcbJIRUBcnrUh+dsCopOOO9S2VGJE/PFKi5NOSMu1WFjCjHeoNER44pzH0NOOKiZvShalIUvTWkzTWOaZSZQbqGpuz6UNSJMSiiiqMwooooAKKKKACiiigAooooAKKKKACiiigAooooAKXJxikooAKKKKACiiigAooooAKK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KhHNJUjrTCKY7DaKWkoJCg0UUAFAoooAKKKKACiiigBaWkopFBRRRQIWiiigYlFFFAgooooAWikpaAG0CilFMQuKevIpFXPFWIbZjSASKMsw4rQjhAXmlitwnarHAqihuwelNZQOlSbqjY5NOwNERGAagljDZqzimsM1IzLkXbVc1flGapspBoJaI6M0UUzMKKKKACiiigAooooAKKKKACnq2ARTKKAQ9qbS0lIoUUZxSCloAcCacOlNpcmkyiKiiiqMwooooAKKKKACiiigAooooAKKKKAAHBqzG2RVanxtg4oGi2KWmo1OpFC04HFNB4pM0mO46lptLQADg04daaeaUUwJKSjNJnmlcYtNo20UAOpaSlqSQJxSg0wnNKKsodml703PFAouIDxSMcjFO6mozzikAN1pMUp7U2gCNwMGoGX8qtMM4qBhxSJuQmilb71JVDDFGKKTmmAppKWikMSiiigQtFFFIYUUUUAFFFFACUtJRTELRRRQMKKKKAFpKWkpDCl60lOXlgKYi1bQlm6VqIgAHFVLYDAGKvKc07BYWkp1NamOKGNjFMzzQ7EEgdKZSGDGo2Oae30qPIJoHYY4JFVZVOKuMAeKrSrxSJ1KbDBptTNHk8VFg0yGhKKKKCQooooAKKKKACiiigAoopx24GAc9zmgAB4pD1pKWkNBQKKXNAwpaQdaKBjaKKKZAUUUUAFFFFABRRRQAUUUUAFFFFABSqcGkooAtqflpQelQxNxjNSDGKRY8Gnios1IDikNDVxUpNR496eKEISlpKKYxaWkopDJB0pDSjpSNQITOKUNTKcaQAMGnYxTF607NMYnSl3Gmk03NAnqSg0bRTNwNIWpgLu96SkpaQhCOKjYc07NIwzSuLqQvim09h7UjHIFUURYpaXFFAWExSClooAKKSloASiiigQUtFJigYUUUUAFFFFAgooopgLRS0lIoKKKKQgpy/epopyjJpgadufkB9quI2OaoQEbQMVZVuKoCwzcUxmpm4gcUhakUMopajpAOLcUwk0jfWkzVDEBprDdmgGjPekIruvJpmwHvVnb3phX5aQiq8ZXPpUdW8AqQRUTxEcgUyGiGiiigkKKKKACiiigAooooAKUUlFAAaWkozQA6nCmZpaRdxtFFFMgKKKKACiiigAooooAKKKKACiiigAooooAUHBqdWyOtV6kjbFB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 \ No newline at end of file diff --git a/tests/test_file_utils.py b/tests/test_file_utils.py index 45976046..561ddd22 100644 --- a/tests/test_file_utils.py +++ b/tests/test_file_utils.py @@ -4,7 +4,8 @@ import pytest from file_utils import load_linked_resources, store_response_files -from plain2code_exceptions import UnsupportedResourceType +from plain2code_exceptions import UnsupportedBase64Content, UnsupportedResourceType +from plain2code_utils import MIN_BASE64_BLOB_LENGTH @pytest.fixture @@ -41,6 +42,45 @@ def test_load_linked_resources_missing_file_raises_file_not_found(template_dir): load_linked_resources([template_dir], [{"text": "Missing", "target": "missing.md"}], "my_thing") +def test_load_linked_resources_base64_blob_raises(template_dir): + file_path = os.path.join(template_dir, "request.txt") + with open(file_path, "w") as f: + f.write("curl -d 'selfie_image=" + "A" * (MIN_BASE64_BLOB_LENGTH + 100) + "'") + + with pytest.raises(UnsupportedBase64Content) as exc_info: + load_linked_resources([template_dir], [{"text": "Request", "target": "request.txt"}], "my_thing") + + assert "request.txt" in str(exc_info.value) + assert "my_thing" in str(exc_info.value) + + +def test_load_linked_resources_small_base64_allowed(template_dir): + file_path = os.path.join(template_dir, "token.txt") + with open(file_path, "w") as f: + f.write("token=" + "A" * (MIN_BASE64_BLOB_LENGTH - 100)) + + result = load_linked_resources([template_dir], [{"text": "Token", "target": "token.txt"}], "my_thing") + + assert "token.txt" in result + + +def test_load_linked_resources_real_base64_image_raises(template_dir): + # Real base64-encoded JPEG that made Gemini 3 fail, inlined into a curl example resource. + sample_path = os.path.join(os.path.dirname(__file__), "data", "sample_base64_image.txt") + with open(sample_path) as f: + blob = f.read() + + file_path = os.path.join(template_dir, "face_match_request.txt") + with open(file_path, "w") as f: + f.write(f"curl -d 'selfie_image={blob}' https://example.com/face-match") + + with pytest.raises(UnsupportedBase64Content) as exc_info: + load_linked_resources([template_dir], [{"text": "Request", "target": "face_match_request.txt"}], "face_match") + + assert "face_match_request.txt" in str(exc_info.value) + assert str(len(blob)) in str(exc_info.value) + + def test_store_response_files_writes_unicode_as_utf8(template_dir): # Content with a non-cp1252 character (📍 U+1F4CD) must be written as UTF-8 # regardless of the platform's default text encoding (e.g. cp1252 on Windows). diff --git a/tests/test_git_utils.py b/tests/test_git_utils.py index dac3675d..62ad013a 100644 --- a/tests/test_git_utils.py +++ b/tests/test_git_utils.py @@ -65,7 +65,8 @@ def test_single_file_change(temp_repo): result = diff(temp_repo, "1.1") assert "test.txt" in result - expected_diff = dedent(""" + expected_diff = dedent( + """ --- a/test.txt +++ b/test.txt @@ -1,3 +1,3 @@ @@ -73,7 +74,8 @@ def test_single_file_change(temp_repo): +modified content line2 line3 - """).strip() + """ + ).strip() assert result["test.txt"] == expected_diff @@ -98,7 +100,8 @@ def test_multiple_file_changes(temp_repo): # Check first file assert "test.txt" in result - expected_diff1 = dedent(""" + expected_diff1 = dedent( + """ --- a/test.txt +++ b/test.txt @@ -1,3 +1,2 @@ @@ -106,19 +109,22 @@ def test_multiple_file_changes(temp_repo): +file1 modified line2 -line3 - """).strip() + """ + ).strip() assert result["test.txt"] == expected_diff1 # Check second file assert "file2.txt" in result - expected_diff2 = dedent(""" + expected_diff2 = dedent( + """ --- /dev/null +++ b/file2.txt @@ -0,0 +1,2 @@ +file2 modified +line2 \\ No newline at end of file - """).strip() + """ + ).strip() assert result["file2.txt"] == expected_diff2 @@ -153,7 +159,8 @@ def test_multiple_commits_diff(temp_repo): # Check first file assert "test.txt" in result - expected_diff1 = dedent(""" + expected_diff1 = dedent( + """ --- a/test.txt +++ b/test.txt @@ -1,3 +1,2 @@ @@ -161,30 +168,35 @@ def test_multiple_commits_diff(temp_repo): +file1 frid1.2 refactored version line2 -line3 - """).strip() + """ + ).strip() assert result["test.txt"] == expected_diff1 # Check second file assert "file2.txt" in result - expected_diff2 = dedent(""" + expected_diff2 = dedent( + """ --- a/file2.txt +++ b/file2.txt @@ -1,2 +1,2 @@ -file2 frid1.1 refactored version +file2 frid1.2 version line2 - """).strip() + """ + ).strip() assert result["file2.txt"] == expected_diff2 # Check third file assert "file3.txt" in result - expected_diff3 = dedent(""" + expected_diff3 = dedent( + """ --- /dev/null +++ b/file3.txt @@ -0,0 +1,2 @@ +file3 frid1.2 new file +line2 - """).strip() + """ + ).strip() assert result["file3.txt"] == expected_diff3 @@ -203,23 +215,27 @@ def test_diff_without_previous_frid_and_no_base_folder(empty_repo): result = diff(empty_repo) assert "new.txt" in result - expected_diff = dedent(""" + expected_diff = dedent( + """ --- /dev/null +++ b/new.txt @@ -0,0 +1,2 @@ +new file content +line2 - """).strip() + """ + ).strip() assert result["new.txt"] == expected_diff assert "new2.txt" in result - expected_diff2 = dedent(""" + expected_diff2 = dedent( + """ --- /dev/null +++ b/new2.txt @@ -0,0 +1,2 @@ +new file content +line2 - """).strip() + """ + ).strip() assert result["new2.txt"] == expected_diff2 @@ -237,14 +253,16 @@ def test_diff_without_previous_frid_and_base_folder(temp_repo): result = diff(temp_repo) assert "new.txt" in result - expected_diff = dedent(""" + expected_diff = dedent( + """ --- a/new.txt +++ b/new.txt @@ -1,2 +1,2 @@ -base folder content +updated base folder content line2 - """).strip() + """ + ).strip() assert result["new.txt"] == expected_diff @@ -260,13 +278,15 @@ def test_new_file(temp_repo): result = diff(temp_repo, "1.1") assert "new.txt" in result - expected_diff = dedent(""" + expected_diff = dedent( + """ --- /dev/null +++ b/new.txt @@ -0,0 +1,2 @@ +new file content +line2 - """).strip() + """ + ).strip() assert result["new.txt"] == expected_diff @@ -282,14 +302,16 @@ def test_deleted_file(temp_repo): result = diff(temp_repo, "1.1") assert "test.txt" in result - expected_diff = dedent(""" + expected_diff = dedent( + """ --- a/test.txt +++ /dev/null @@ -1,3 +0,0 @@ -initial content -line2 -line3 - """).strip() + """ + ).strip() assert result["test.txt"] == expected_diff From 46fafa54eb9c4beb8a5c720ab7a553563681a1f5 Mon Sep 17 00:00:00 2001 From: VitjanZ Date: Mon, 15 Jun 2026 16:27:43 +0200 Subject: [PATCH 2/4] Format --- file_utils.py | 6 ++-- tests/test_git_utils.py | 66 ++++++++++++++--------------------------- 2 files changed, 24 insertions(+), 48 deletions(-) diff --git a/file_utils.py b/file_utils.py index 97697136..cec1f11e 100644 --- a/file_utils.py +++ b/file_utils.py @@ -197,8 +197,7 @@ def load_linked_resources(template_dirs: list[str], resources_list, module_name: ) if content is None: - raise FileNotFoundError( - f""" + raise FileNotFoundError(f""" Resource file {file_name} not found. Resource files are searched in the following order (highest to lowest precedence): 1. The directory containing your .plain file @@ -206,8 +205,7 @@ def load_linked_resources(template_dirs: list[str], resources_list, module_name: 3. The built-in 'standard_template_library' directory Please ensure that the resource exists in one of these locations, or specify the correct --template-dir if using custom templates. - """ - ) + """) blob = find_large_base64_blob(content) if blob is not None: diff --git a/tests/test_git_utils.py b/tests/test_git_utils.py index 62ad013a..dac3675d 100644 --- a/tests/test_git_utils.py +++ b/tests/test_git_utils.py @@ -65,8 +65,7 @@ def test_single_file_change(temp_repo): result = diff(temp_repo, "1.1") assert "test.txt" in result - expected_diff = dedent( - """ + expected_diff = dedent(""" --- a/test.txt +++ b/test.txt @@ -1,3 +1,3 @@ @@ -74,8 +73,7 @@ def test_single_file_change(temp_repo): +modified content line2 line3 - """ - ).strip() + """).strip() assert result["test.txt"] == expected_diff @@ -100,8 +98,7 @@ def test_multiple_file_changes(temp_repo): # Check first file assert "test.txt" in result - expected_diff1 = dedent( - """ + expected_diff1 = dedent(""" --- a/test.txt +++ b/test.txt @@ -1,3 +1,2 @@ @@ -109,22 +106,19 @@ def test_multiple_file_changes(temp_repo): +file1 modified line2 -line3 - """ - ).strip() + """).strip() assert result["test.txt"] == expected_diff1 # Check second file assert "file2.txt" in result - expected_diff2 = dedent( - """ + expected_diff2 = dedent(""" --- /dev/null +++ b/file2.txt @@ -0,0 +1,2 @@ +file2 modified +line2 \\ No newline at end of file - """ - ).strip() + """).strip() assert result["file2.txt"] == expected_diff2 @@ -159,8 +153,7 @@ def test_multiple_commits_diff(temp_repo): # Check first file assert "test.txt" in result - expected_diff1 = dedent( - """ + expected_diff1 = dedent(""" --- a/test.txt +++ b/test.txt @@ -1,3 +1,2 @@ @@ -168,35 +161,30 @@ def test_multiple_commits_diff(temp_repo): +file1 frid1.2 refactored version line2 -line3 - """ - ).strip() + """).strip() assert result["test.txt"] == expected_diff1 # Check second file assert "file2.txt" in result - expected_diff2 = dedent( - """ + expected_diff2 = dedent(""" --- a/file2.txt +++ b/file2.txt @@ -1,2 +1,2 @@ -file2 frid1.1 refactored version +file2 frid1.2 version line2 - """ - ).strip() + """).strip() assert result["file2.txt"] == expected_diff2 # Check third file assert "file3.txt" in result - expected_diff3 = dedent( - """ + expected_diff3 = dedent(""" --- /dev/null +++ b/file3.txt @@ -0,0 +1,2 @@ +file3 frid1.2 new file +line2 - """ - ).strip() + """).strip() assert result["file3.txt"] == expected_diff3 @@ -215,27 +203,23 @@ def test_diff_without_previous_frid_and_no_base_folder(empty_repo): result = diff(empty_repo) assert "new.txt" in result - expected_diff = dedent( - """ + expected_diff = dedent(""" --- /dev/null +++ b/new.txt @@ -0,0 +1,2 @@ +new file content +line2 - """ - ).strip() + """).strip() assert result["new.txt"] == expected_diff assert "new2.txt" in result - expected_diff2 = dedent( - """ + expected_diff2 = dedent(""" --- /dev/null +++ b/new2.txt @@ -0,0 +1,2 @@ +new file content +line2 - """ - ).strip() + """).strip() assert result["new2.txt"] == expected_diff2 @@ -253,16 +237,14 @@ def test_diff_without_previous_frid_and_base_folder(temp_repo): result = diff(temp_repo) assert "new.txt" in result - expected_diff = dedent( - """ + expected_diff = dedent(""" --- a/new.txt +++ b/new.txt @@ -1,2 +1,2 @@ -base folder content +updated base folder content line2 - """ - ).strip() + """).strip() assert result["new.txt"] == expected_diff @@ -278,15 +260,13 @@ def test_new_file(temp_repo): result = diff(temp_repo, "1.1") assert "new.txt" in result - expected_diff = dedent( - """ + expected_diff = dedent(""" --- /dev/null +++ b/new.txt @@ -0,0 +1,2 @@ +new file content +line2 - """ - ).strip() + """).strip() assert result["new.txt"] == expected_diff @@ -302,16 +282,14 @@ def test_deleted_file(temp_repo): result = diff(temp_repo, "1.1") assert "test.txt" in result - expected_diff = dedent( - """ + expected_diff = dedent(""" --- a/test.txt +++ /dev/null @@ -1,3 +0,0 @@ -initial content -line2 -line3 - """ - ).strip() + """).strip() assert result["test.txt"] == expected_diff From 59781babcbbb0f98ee5a8f960d13c97410f298f6 Mon Sep 17 00:00:00 2001 From: VitjanZ Date: Tue, 16 Jun 2026 13:15:49 +0200 Subject: [PATCH 3/4] Changed the output text when detecting blob in reference files. --- file_utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/file_utils.py b/file_utils.py index cec1f11e..bc1c9e1a 100644 --- a/file_utils.py +++ b/file_utils.py @@ -212,8 +212,9 @@ def load_linked_resources(template_dirs: list[str], resources_list, module_name: raise UnsupportedBase64Content( f"Referenced resource '{file_name}' in module '{module_name}' contains a large " f"base64-encoded blob ({len(blob)} characters), such as an embedded image. Inline " - "base64 data is not supported. Remove the base64 data " - "from the resource. If necessary, provide the binary file path in the specification." + "base64 data is not supported. Remove the data from the resource. " + "If the data should be used by the end software, " + "save the data to a separate file and include the file path in the specification without it being a reference file." ) linked_resources[file_name] = content From 0979c880e67dc787f6afd8b7e80868ec2efe3b83 Mon Sep 17 00:00:00 2001 From: VitjanZ Date: Wed, 24 Jun 2026 09:56:23 +0200 Subject: [PATCH 4/4] Replaced MIN length with MAX length --- plain2code_utils.py | 4 ++-- tests/test_file_utils.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plain2code_utils.py b/plain2code_utils.py index 7b955698..80dd0a8b 100644 --- a/plain2code_utils.py +++ b/plain2code_utils.py @@ -2,11 +2,11 @@ from typing import Optional # -MIN_BASE64_BLOB_LENGTH = 8192 +MAX_BASE64_BLOB_LENGTH = 8192 # Matches a long contiguous base64 / base64url run, optionally preceded by a data: URI header. _BASE64_BLOB_PATTERN = re.compile( - r"(?:data:[\w.+-]+/[\w.+-]+;base64,)?[A-Za-z0-9+/_-]{%d,}={0,2}" % MIN_BASE64_BLOB_LENGTH + r"(?:data:[\w.+-]+/[\w.+-]+;base64,)?[A-Za-z0-9+/_-]{%d,}={0,2}" % MAX_BASE64_BLOB_LENGTH ) diff --git a/tests/test_file_utils.py b/tests/test_file_utils.py index 561ddd22..6b306792 100644 --- a/tests/test_file_utils.py +++ b/tests/test_file_utils.py @@ -5,7 +5,7 @@ from file_utils import load_linked_resources, store_response_files from plain2code_exceptions import UnsupportedBase64Content, UnsupportedResourceType -from plain2code_utils import MIN_BASE64_BLOB_LENGTH +from plain2code_utils import MAX_BASE64_BLOB_LENGTH @pytest.fixture @@ -45,7 +45,7 @@ def test_load_linked_resources_missing_file_raises_file_not_found(template_dir): def test_load_linked_resources_base64_blob_raises(template_dir): file_path = os.path.join(template_dir, "request.txt") with open(file_path, "w") as f: - f.write("curl -d 'selfie_image=" + "A" * (MIN_BASE64_BLOB_LENGTH + 100) + "'") + f.write("curl -d 'selfie_image=" + "A" * (MAX_BASE64_BLOB_LENGTH + 100) + "'") with pytest.raises(UnsupportedBase64Content) as exc_info: load_linked_resources([template_dir], [{"text": "Request", "target": "request.txt"}], "my_thing") @@ -57,7 +57,7 @@ def test_load_linked_resources_base64_blob_raises(template_dir): def test_load_linked_resources_small_base64_allowed(template_dir): file_path = os.path.join(template_dir, "token.txt") with open(file_path, "w") as f: - f.write("token=" + "A" * (MIN_BASE64_BLOB_LENGTH - 100)) + f.write("token=" + "A" * (MAX_BASE64_BLOB_LENGTH - 100)) result = load_linked_resources([template_dir], [{"text": "Token", "target": "token.txt"}], "my_thing")