Skip to content

Commit f3c9e06

Browse files
committed
fix(format): only sort blueprints by version when semver is used
1 parent a54757f commit f3c9e06

File tree

4 files changed

+79
-26
lines changed

4 files changed

+79
-26
lines changed

pyproject.toml

-4
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,6 @@ exclude = '''
7979
)
8080
'''
8181

82-
[tool.isort]
83-
line_length = 90
84-
profile = "black"
85-
8682
[tool.mypy]
8783
files = "src"
8884
strict = true

src/ops2deb/formatter.py

+13-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import Any, OrderedDict, Tuple
44

55
import yaml
6+
from semver.version import Version
67

78
from ops2deb.exceptions import Ops2debFormatterError
89
from ops2deb.parser import (
@@ -15,12 +16,20 @@
1516

1617

1718
def sort_blueprints(blueprints: list[OrderedDict[str, Any]]) -> list[dict[str, Any]]:
18-
def key(blueprint: dict[str, Any]) -> Tuple[str, str, int]:
19+
def key(blueprint: dict[str, Any]) -> Tuple[str, Version, int]:
1920
try:
20-
version = blueprint["matrix"]["versions"][-1]
21+
version_str = blueprint["matrix"]["versions"][-1]
2122
except KeyError:
22-
version = blueprint["version"]
23-
return blueprint["name"], version, blueprint.get("revision", "1")
23+
version_str = blueprint["version"]
24+
version: Version = Version(0, 0, 0)
25+
if Version.isvalid(version_str):
26+
version = Version.parse(version_str)
27+
revision_str = blueprint.get("revision", "1")
28+
try:
29+
revision = int(revision_str)
30+
except ValueError:
31+
revision = 1
32+
return blueprint["name"], version, revision
2433

2534
return sorted(blueprints, key=key)
2635

src/ops2deb/utils.py

+6
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,9 @@ class FixIndentEmitter(Emitter):
4343
def expect_block_sequence(self) -> None:
4444
self.increase_indent(flow=False, indentless=False)
4545
self.state = self.expect_first_block_sequence_item
46+
47+
def choose_scalar_style(self) -> str:
48+
style: str = super().choose_scalar_style()
49+
style = '"' if style == "'" else style
50+
style = "|" if self.analysis.multiline else style
51+
return style

tests/test_formatter.py

+60-18
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pytest
44

5-
from ops2deb.formatter import format_blueprint, format_description
5+
from ops2deb.formatter import format_blueprint, format_description, sort_blueprints
66
from ops2deb.parser import Blueprint
77

88
description_with_empty_line = """
@@ -17,14 +17,14 @@
1717
"""
1818

1919

20-
def test_format_description_should_only_remove_empty_lines_at_start_or_end():
20+
def test_format_description__should_only_remove_empty_lines_at_start_or_end():
2121
result = format_description(description_with_empty_line)
2222
assert result[0] != "\n"
2323
assert result[-1] != "\n"
2424
assert "\n" in result
2525

2626

27-
def test_format_description_should_remove_trailing_spaces():
27+
def test_format_description__should_remove_trailing_spaces():
2828
lines = description_with_empty_line.split("\n")
2929
lines = [line + " " for line in lines]
3030
description_with_trailing_spaces = "\n".join(lines)
@@ -33,20 +33,20 @@ def test_format_description_should_remove_trailing_spaces():
3333
assert result[1] == ""
3434

3535

36-
def test_format_description_should_wrap_long_lines():
36+
def test_format_description__should_wrap_long_lines():
3737
result = format_description(description_with_long_line)
3838
assert len(result.split("\n")) == 2
3939

4040

4141
@pytest.mark.parametrize(
4242
"description", [description_with_empty_line, description_with_long_line]
4343
)
44-
def test_format_description_should_be_idempotent(description):
44+
def test_format_description__should_be_idempotent(description):
4545
result = format_description(description)
4646
assert format_description(result) == result
4747

4848

49-
def test_format_blueprint_should_remove_default_values():
49+
def test_format_blueprint__should_remove_default_values():
5050
raw_blueprint = dict(
5151
name="great-app",
5252
version="1.0.0",
@@ -59,20 +59,20 @@ def test_format_blueprint_should_remove_default_values():
5959
assert format_blueprint(raw_blueprint_with_defaults) == raw_blueprint
6060

6161

62-
def test_format_blueprint_should_not_remove_field_when_value_is_not_default(
62+
def test_format_blueprint__should_not_remove_field_when_value_is_not_default(
6363
blueprint_factory,
6464
):
6565
blueprint = blueprint_factory(revision=2, depends=["test"])
6666
blueprint = format_blueprint(blueprint.dict())
6767
assert {"revision", "depends", "fetch", "script"}.issubset(blueprint.keys())
6868

6969

70-
def test_format_blueprint_should_not_render_templated_values(blueprint_factory):
70+
def test_format_blueprint__should_not_render_templated_values(blueprint_factory):
7171
blueprint = blueprint_factory(version="{{env('TEST', 0)}}", construct=True)
7272
assert format_blueprint(blueprint.dict())["version"] == "{{env('TEST', 0)}}"
7373

7474

75-
def test_format_blueprint_should_replace_fetch_object_with_string_when_only_key_is_url():
75+
def test_format_blueprint__replaces_fetch_object_with_string_when_only_key_is_url():
7676
raw_blueprint = dict(
7777
name="great-app",
7878
version="1.0.0",
@@ -82,13 +82,55 @@ def test_format_blueprint_should_replace_fetch_object_with_string_when_only_key_
8282
assert format_blueprint(raw_blueprint)["fetch"] == "http://test/app.tar.gz"
8383

8484

85-
def test_format_blueprint_should_replace_field_arch_by_architecture():
86-
raw_blueprint = dict(
87-
name="great-app",
88-
version="1.0.0",
89-
arch="all",
90-
summary="A summary",
85+
def test_sort_blueprints__sorts_by_name_and_version_when_blueprint_uses_semver():
86+
# Given
87+
blueprint_0 = dict(name="great-app", version="2.0.0", summary="A summary")
88+
blueprint_1 = dict(name="great-app", version="1.0.0", summary="A summary")
89+
blueprint_2 = dict(
90+
name="great-app", matrix=dict(versions=["0.1.0", "0.2.0"]), summary="A summary"
9191
)
92-
formatted_blueprint = format_blueprint(raw_blueprint)
93-
assert formatted_blueprint["architecture"] == "all"
94-
assert "arch" not in formatted_blueprint
92+
93+
# When
94+
result = sort_blueprints([blueprint_0, blueprint_1, blueprint_2])
95+
96+
# Then
97+
assert result == [blueprint_2, blueprint_1, blueprint_0]
98+
99+
100+
def test_sort_blueprints__does_not_sort_by_version_when_blueprint_does_not_use_semver():
101+
# Given
102+
blueprint_0 = dict(name="great-app", version="2020", summary="A summary")
103+
blueprint_1 = dict(name="great-app", version="2019", summary="A summary")
104+
blueprint_2 = dict(name="great-app", version="2023", summary="A summary")
105+
106+
# When
107+
result = sort_blueprints([blueprint_0, blueprint_1, blueprint_2])
108+
109+
# Then
110+
assert result == [blueprint_0, blueprint_1, blueprint_2]
111+
112+
113+
def test_sort_blueprints__sorts_by_name_version_and_revision_when_revision_is_an_int():
114+
# Given
115+
blueprint_0 = dict(name="great-app", version="1.0.0", summary="A summary", revision=2)
116+
blueprint_1 = dict(name="great-app", version="1.0.0", summary="A summary", revision=3)
117+
blueprint_2 = dict(name="great-app", version="1.0.0", summary="A summary", revision=1)
118+
119+
# When
120+
result = sort_blueprints([blueprint_0, blueprint_1, blueprint_2])
121+
122+
# Then
123+
assert result == [blueprint_2, blueprint_0, blueprint_1]
124+
125+
126+
def test_sort_blueprints__does_not_sort_by_revision_when_revision_is_not_an_int():
127+
# Given
128+
blueprint_0 = dict(name="great-app", version="1.0.0", summary="summary", revision="c")
129+
blueprint_1 = dict(name="great-app", version="1.0.0", summary="summary", revision="b")
130+
blueprint_2 = dict(name="great-app", version="1.0.0", summary="summary", revision="a")
131+
132+
# When
133+
result = sort_blueprints([blueprint_0, blueprint_1, blueprint_2])
134+
135+
# Then
136+
assert result == [blueprint_0, blueprint_1, blueprint_2]

0 commit comments

Comments
 (0)