resforge 0.4.2__tar.gz → 0.4.3__tar.gz

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 (30) hide show
  1. {resforge-0.4.2 → resforge-0.4.3}/.gitignore +43 -29
  2. {resforge-0.4.2 → resforge-0.4.3}/PKG-INFO +5 -8
  3. {resforge-0.4.2 → resforge-0.4.3}/README.md +3 -5
  4. {resforge-0.4.2 → resforge-0.4.3}/pyproject.toml +4 -32
  5. {resforge-0.4.2 → resforge-0.4.3}/src/resforge/__init__.py +1 -1
  6. {resforge-0.4.2 → resforge-0.4.3}/src/resforge/android/compose.py +7 -6
  7. {resforge-0.4.2 → resforge-0.4.3}/src/resforge/android/values.py +4 -4
  8. {resforge-0.4.2 → resforge-0.4.3}/src/resforge/apple/catalog.py +2 -1
  9. {resforge-0.4.2 → resforge-0.4.3}/src/resforge/color.py +14 -0
  10. {resforge-0.4.2 → resforge-0.4.3}/src/resforge/io.py +1 -1
  11. {resforge-0.4.2 → resforge-0.4.3}/tests/test_android_compose.py +11 -10
  12. {resforge-0.4.2 → resforge-0.4.3}/tests/test_android_values.py +1 -2
  13. {resforge-0.4.2 → resforge-0.4.3}/tests/test_apple_catalog.py +9 -7
  14. {resforge-0.4.2 → resforge-0.4.3}/tests/test_atomic_write.py +5 -4
  15. {resforge-0.4.2 → resforge-0.4.3}/tests/test_color.py +0 -1
  16. resforge-0.4.2/.github/workflows/ci.yml +0 -48
  17. resforge-0.4.2/.github/workflows/publish.yml +0 -29
  18. resforge-0.4.2/LICENSE +0 -21
  19. {resforge-0.4.2 → resforge-0.4.3}/src/resforge/_codegen/__init__.py +0 -0
  20. {resforge-0.4.2 → resforge-0.4.3}/src/resforge/_codegen/kotlin.py +0 -0
  21. {resforge-0.4.2 → resforge-0.4.3}/src/resforge/_utils.py +0 -0
  22. {resforge-0.4.2 → resforge-0.4.3}/src/resforge/android/__init__.py +0 -0
  23. {resforge-0.4.2 → resforge-0.4.3}/src/resforge/android/types.py +0 -0
  24. {resforge-0.4.2 → resforge-0.4.3}/src/resforge/apple/__init__.py +0 -0
  25. {resforge-0.4.2 → resforge-0.4.3}/src/resforge/apple/_base.py +0 -0
  26. {resforge-0.4.2 → resforge-0.4.3}/src/resforge/apple/_colorset.py +0 -0
  27. {resforge-0.4.2 → resforge-0.4.3}/src/resforge/apple/types.py +0 -0
  28. {resforge-0.4.2 → resforge-0.4.3}/src/resforge/py.typed +0 -0
  29. {resforge-0.4.2 → resforge-0.4.3}/tests/__init__.py +0 -0
  30. {resforge-0.4.2 → resforge-0.4.3}/tests/test_codegen_kotlin.py +0 -0
@@ -27,8 +27,8 @@ share/python-wheels/
27
27
  MANIFEST
28
28
 
29
29
  # PyInstaller
30
- # Usually these files are written by a python script from a template
31
- # before PyInstaller builds the exe, so as to inject date/other infos into it.
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
32
  *.manifest
33
33
  *.spec
34
34
 
@@ -92,34 +92,34 @@ ipython_config.py
92
92
  # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
93
  # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
94
  # install all needed dependencies.
95
- #Pipfile.lock
95
+ # Pipfile.lock
96
96
 
97
97
  # UV
98
98
  # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
99
  # This is especially recommended for binary packages to ensure reproducibility, and is more
100
100
  # commonly ignored for libraries.
101
- #uv.lock
101
+ # uv.lock
102
102
 
103
103
  # poetry
104
104
  # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
105
  # This is especially recommended for binary packages to ensure reproducibility, and is more
106
106
  # commonly ignored for libraries.
107
107
  # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
- #poetry.lock
109
- #poetry.toml
108
+ # poetry.lock
109
+ # poetry.toml
110
110
 
111
111
  # pdm
112
112
  # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
113
  # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
114
  # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
- #pdm.lock
116
- #pdm.toml
115
+ # pdm.lock
116
+ # pdm.toml
117
117
  .pdm-python
118
118
  .pdm-build/
119
119
 
120
120
  # pixi
121
121
  # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
- #pixi.lock
122
+ # pixi.lock
123
123
  # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
124
  # in the .venv directory. It is recommended not to include this directory in version control.
125
125
  .pixi
@@ -131,6 +131,19 @@ __pypackages__/
131
131
  celerybeat-schedule
132
132
  celerybeat.pid
133
133
 
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
134
147
  # SageMath parsed files
135
148
  *.sage.py
136
149
 
@@ -169,24 +182,26 @@ dmypy.json
169
182
  cython_debug/
170
183
 
171
184
  # PyCharm
172
- # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
173
- # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174
- # and can be added to the global gitignore or merged into this file. For a more nuclear
175
- # option (not recommended) you can uncomment the following to ignore the entire idea folder.
176
- #.idea/
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
177
190
 
178
191
  # Abstra
179
- # Abstra is an AI-powered process automation framework.
180
- # Ignore directories containing user credentials, local state, and settings.
181
- # Learn more at https://abstra.io/docs
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
182
195
  .abstra/
183
196
 
184
197
  # Visual Studio Code
185
- # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
186
- # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
187
- # and can be added to the global gitignore or merged into this file. However, if you prefer,
188
- # you could uncomment the following to ignore the entire vscode folder
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
189
202
  # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
190
205
 
191
206
  # Ruff stuff:
192
207
  .ruff_cache/
@@ -194,17 +209,16 @@ cython_debug/
194
209
  # PyPI configuration file
195
210
  .pypirc
196
211
 
197
- # Cursor
198
- # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
199
- # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
200
- # refer to https://docs.cursor.com/context/ignore-files
201
- .cursorignore
202
- .cursorindexingignore
203
-
204
212
  # Marimo
205
213
  marimo/_static/
206
214
  marimo/_lsp/
207
215
  __marimo__/
208
216
 
209
- pyrightconfig.json
217
+ # Streamlit
218
+ .streamlit/secrets.toml
219
+
220
+ # Forje build files
221
+ *.forje
222
+
223
+ # macOS
210
224
  .DS_Store
@@ -1,12 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: resforge
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: A type-safe Python DSL for cross-platform mobile resource generation
5
5
  Project-URL: Homepage, https://kipila.dev
6
- Project-URL: Repository, https://github.com/kipila-dev/resforge
6
+ Project-URL: Repository, https://github.com/kipila-dev/forje
7
7
  Author-email: Kipila Ltd <hello@kipila.dev>
8
8
  License: MIT
9
- License-File: LICENSE
10
9
  Keywords: android-xml,automation,code-generation,design-tokens,dsl,mobile-infra,type-safety,white-label,xcassets
11
10
  Classifier: Intended Audience :: Developers
12
11
  Classifier: License :: OSI Approved :: MIT License
@@ -35,7 +34,7 @@ design tokens.
35
34
 
36
35
  ## Installation
37
36
 
38
- ```bash
37
+ ```sh
39
38
  pip install resforge
40
39
  ```
41
40
 
@@ -79,12 +78,10 @@ with ValuesWriter("res/values/resources.xml") as res:
79
78
  res.string(
80
79
  app_name="My App",
81
80
  welcome_message="Welcome!",
82
- )
83
- res.color(
81
+ ).color(
84
82
  primary="#6200EE",
85
83
  secondary="#03DAC5",
86
- )
87
- res.dimension(
84
+ ).dimension(
88
85
  padding_small=dp(8),
89
86
  text_body=sp(16),
90
87
  )
@@ -14,7 +14,7 @@ design tokens.
14
14
 
15
15
  ## Installation
16
16
 
17
- ```bash
17
+ ```sh
18
18
  pip install resforge
19
19
  ```
20
20
 
@@ -58,12 +58,10 @@ with ValuesWriter("res/values/resources.xml") as res:
58
58
  res.string(
59
59
  app_name="My App",
60
60
  welcome_message="Welcome!",
61
- )
62
- res.color(
61
+ ).color(
63
62
  primary="#6200EE",
64
63
  secondary="#03DAC5",
65
- )
66
- res.dimension(
64
+ ).dimension(
67
65
  padding_small=dp(8),
68
66
  text_body=sp(16),
69
67
  )
@@ -34,38 +34,10 @@ dependencies = ["coloraide"]
34
34
 
35
35
  [project.urls]
36
36
  Homepage = "https://kipila.dev"
37
- Repository = "https://github.com/kipila-dev/resforge"
37
+ Repository = "https://github.com/kipila-dev/forje"
38
+
39
+ [dependency-groups]
40
+ dev = ["defusedxml"]
38
41
 
39
42
  [tool.hatch.version]
40
43
  path = "src/resforge/__init__.py"
41
-
42
- [tool.hatch.envs.default]
43
- dependencies = ["basedpyright", "defusedxml", "pytest", "ruff"]
44
-
45
- [tool.ruff]
46
- target-version = "py312"
47
-
48
- [tool.ruff.lint]
49
- select = ["ALL"]
50
- ignore = ["D100", "D104", "D105", "D107", "ANN401", "PLR2004", "E741"]
51
-
52
- [tool.ruff.lint.extend-per-file-ignores]
53
- "tests/**/*.py" = [
54
- "ANN001",
55
- "ANN201",
56
- "D101",
57
- "D102",
58
- "D103",
59
- "EM101",
60
- "S101",
61
- "TRY301",
62
- ]
63
-
64
- [tool.ruff.lint.pydocstyle]
65
- convention = "google"
66
-
67
- [tool.basedpyright]
68
- include = ["src"]
69
- typeCheckingMode = "recommended"
70
- pythonVersion = "3.12"
71
- reportUnusedCallResult = "none"
@@ -1,4 +1,4 @@
1
1
  from .color import Color
2
2
 
3
3
  __all__ = ["Color"]
4
- __version__ = "0.4.2"
4
+ __version__ = "0.4.3"
@@ -40,7 +40,7 @@ class _BaseComposeScope:
40
40
 
41
41
  def _to_compose_color_literal(self, color: Color) -> str:
42
42
  if color.in_srgb_gamut():
43
- return f"Color({color.to_srgb_argb_hex(prefix="0x")})"
43
+ return f"Color({color.to_srgb_argb_hex(prefix='0x')})"
44
44
 
45
45
  self._ctx.add_imports("androidx.compose.ui.graphics.colorspace.ColorSpaces")
46
46
  r, g, b, a = color.to_p3_components()
@@ -100,16 +100,16 @@ class _ObjectScope(_BaseComposeScope):
100
100
 
101
101
  @final
102
102
  class ComposeWriter(_BaseComposeScope):
103
- """A fluent context manager for generating Jetpack Compose color definitions.
103
+ """A context manager for generating Jetpack Compose color definitions.
104
104
 
105
105
  Writes a Kotlin file containing typed Color or Dimension properties.
106
106
 
107
107
  Example:
108
108
  >>> with ComposeWriter(
109
- "ui/theme/Color.kt",
110
- package="com.example.theme",
111
- object_name="AppColors"
112
- ) as compose:
109
+ ... "ui/theme/Color.kt",
110
+ ... package="com.example.theme",
111
+ ... object_name="AppColors",
112
+ ... ) as compose:
113
113
  ... compose.color(primary="#6200EE", background="#FFFFFF")
114
114
  """
115
115
 
@@ -117,6 +117,7 @@ class ComposeWriter(_BaseComposeScope):
117
117
  self,
118
118
  path: str | Path,
119
119
  package: str,
120
+ *,
120
121
  sink: WriteSink | None = None,
121
122
  ) -> None:
122
123
  """Initializes the ComposeWriter.
@@ -19,7 +19,7 @@ _STYLE_NAME_PATTERN = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_\.]*$")
19
19
 
20
20
  @final
21
21
  class ValuesWriter:
22
- """A fluent context manager for generating Android XML resource files.
22
+ """A context manager for generating Android XML resource files.
23
23
 
24
24
  Provides a type-safe interface for creating strings, dimensions, colors,
25
25
  and arrays. Validates resource names and color formats at runtime.
@@ -30,7 +30,7 @@ class ValuesWriter:
30
30
 
31
31
  """
32
32
 
33
- def __init__(self, path: str | Path, sink: WriteSink | None = None) -> None:
33
+ def __init__(self, path: str | Path, *, sink: WriteSink | None = None) -> None:
34
34
  """Initializes the ValuesWriter.
35
35
 
36
36
  Args:
@@ -153,8 +153,8 @@ class ValuesWriter:
153
153
 
154
154
  """
155
155
  for name, color in values.items():
156
- resolved = Color.parse(color)
157
- self._append("color", name, resolved.to_srgb_argb_hex())
156
+ parsed_color = Color.parse(color)
157
+ self._append("color", name, parsed_color.to_srgb_argb_hex())
158
158
  return self
159
159
 
160
160
  @require_context
@@ -15,7 +15,7 @@ __all__ = ["AssetCatalog"]
15
15
 
16
16
  @final
17
17
  class AssetCatalog:
18
- """A fluent context manager for generating Apple Asset Catalogs (.xcassets).
18
+ """A context manager for generating Apple Asset Catalogs (.xcassets).
19
19
 
20
20
  To ensure atomic writes, it works in a temporary directory and only swaps
21
21
  to the final destination upon successful completion of the context.
@@ -29,6 +29,7 @@ class AssetCatalog:
29
29
  self,
30
30
  path: str | Path,
31
31
  name: str,
32
+ *,
32
33
  sink: WriteSink | None = None,
33
34
  ) -> None:
34
35
  """Initializes the AssetCatalog.
@@ -110,6 +110,20 @@ class Color:
110
110
  round(self.alpha, 3) + 0.0,
111
111
  )
112
112
 
113
+ def to_oklch_components(self) -> tuple[float, float, float, float]:
114
+ """Convert the color to Oklch components (L, C, H, A).
115
+
116
+ Colors outside Oklch gamut are automatically clipped to the nearest
117
+ valid boundary.
118
+ """
119
+ oklch = self._to_coloraide().convert("oklch").clip()
120
+ return (
121
+ round(oklch["lightness"], 3) + 0.0,
122
+ round(oklch["chroma"], 3) + 0.0,
123
+ round(oklch["hue"], 3) + 0.0,
124
+ round(self.alpha, 3) + 0.0,
125
+ )
126
+
113
127
  def to_srgb_argb_hex(self, prefix: str = "#") -> str:
114
128
  """Convert the color to an ARGB hexadecimal string.
115
129
 
@@ -13,7 +13,7 @@ __all__ = ["FileSystemSink", "MemorySink", "WriteSink", "atomic_write"]
13
13
  def atomic_write(target_path: str | Path) -> Generator[IO[bytes], None, None]:
14
14
  """Yields a temporary file, then atomically replaces target_path on success.
15
15
 
16
- Preserves file permissions of target_path, if target_path already exists.
16
+ Preserves file permissions if `target_path` already exists.
17
17
  """
18
18
  target_path = Path(target_path)
19
19
  target_path.parent.mkdir(parents=True, exist_ok=True)
@@ -1,9 +1,10 @@
1
- import pytest
1
+ from pathlib import Path
2
2
 
3
+ import pytest
3
4
  from resforge.android import ComposeWriter, Dimension, dp, sp
4
5
 
5
6
 
6
- def test_object_colors(tmp_path):
7
+ def test_object_colors(tmp_path: Path):
7
8
  output = tmp_path / "AppColors.kt"
8
9
  with (
9
10
  ComposeWriter(output, package="com.example.theme") as compose,
@@ -23,7 +24,7 @@ def test_object_colors(tmp_path):
23
24
  )
24
25
 
25
26
 
26
- def test_top_level_dimension(tmp_path):
27
+ def test_top_level_dimension(tmp_path: Path):
27
28
  output = tmp_path / "Dimens.kt"
28
29
  with ComposeWriter(output, package="com.example.theme") as compose:
29
30
  compose.dimension(paddingSmall=dp(8), textBody=sp(16))
@@ -41,7 +42,7 @@ def test_top_level_dimension(tmp_path):
41
42
  )
42
43
 
43
44
 
44
- def test_mixed_top_level_and_object(tmp_path):
45
+ def test_mixed_top_level_and_object(tmp_path: Path):
45
46
  output = tmp_path / "Theme.kt"
46
47
  with ComposeWriter(output, package="com.example.theme") as compose:
47
48
  compose.dimension(border=dp(8))
@@ -64,7 +65,7 @@ def test_mixed_top_level_and_object(tmp_path):
64
65
  )
65
66
 
66
67
 
67
- def test_multiple_objects(tmp_path):
68
+ def test_multiple_objects(tmp_path: Path):
68
69
  output = tmp_path / "Theme.kt"
69
70
  with ComposeWriter(output, package="com.example.theme") as compose:
70
71
  with compose.object_("AppColors") as colors:
@@ -78,7 +79,7 @@ def test_multiple_objects(tmp_path):
78
79
  assert rendered.index("AppColors") < rendered.index("AppDimens")
79
80
 
80
81
 
81
- def test_object_require_context_raises(tmp_path):
82
+ def test_object_require_context_raises(tmp_path: Path):
82
83
  output = tmp_path / "Color.kt"
83
84
  with ComposeWriter(output, package="com.example.theme") as compose:
84
85
  scope = compose.object_("AppColors")
@@ -87,7 +88,7 @@ def test_object_require_context_raises(tmp_path):
87
88
  scope.color(primary="#6200EE")
88
89
 
89
90
 
90
- def test_no_write_on_exception(tmp_path):
91
+ def test_no_write_on_exception(tmp_path: Path):
91
92
  output = tmp_path / "Color.kt"
92
93
  with (
93
94
  pytest.raises(RuntimeError),
@@ -104,7 +105,7 @@ def test_require_context_raises():
104
105
  compose.color(primary="#6200EE")
105
106
 
106
107
 
107
- def test_creates_parent_directories(tmp_path):
108
+ def test_creates_parent_directories(tmp_path: Path):
108
109
  output = tmp_path / "ui" / "theme" / "Color.kt"
109
110
  with ComposeWriter(output, package="com.example.theme") as compose:
110
111
  compose.color(primary="#6200EE")
@@ -112,7 +113,7 @@ def test_creates_parent_directories(tmp_path):
112
113
  assert output.exists()
113
114
 
114
115
 
115
- def test_package_is_configurable(tmp_path):
116
+ def test_package_is_configurable(tmp_path: Path):
116
117
  output = tmp_path / "Color.kt"
117
118
  with ComposeWriter(output, package="dev.kipila.example") as compose:
118
119
  compose.color(primary="#6200EE")
@@ -120,7 +121,7 @@ def test_package_is_configurable(tmp_path):
120
121
  assert "package dev.kipila.example" in output.read_text()
121
122
 
122
123
 
123
- def test_unsupported_dimension_unit_raises(tmp_path):
124
+ def test_unsupported_dimension_unit_raises(tmp_path: Path):
124
125
  output = tmp_path / "Color.kt"
125
126
 
126
127
  with (
@@ -2,7 +2,6 @@ from pathlib import Path
2
2
 
3
3
  import pytest
4
4
  from defusedxml.ElementTree import parse
5
-
6
5
  from resforge import Color
7
6
  from resforge.android import PluralValues, ValuesWriter, dp, em, inch, mm, pt, px, sp
8
7
 
@@ -46,7 +45,7 @@ class TestDimensionType:
46
45
 
47
46
 
48
47
  @pytest.fixture
49
- def xml_path(tmp_path) -> Path:
48
+ def xml_path(tmp_path: Path) -> Path:
50
49
  return tmp_path / "res" / "values" / "test.xml"
51
50
 
52
51
 
@@ -1,12 +1,12 @@
1
1
  import json
2
+ from pathlib import Path
2
3
 
3
4
  import pytest
4
-
5
5
  from resforge.apple.catalog import AssetCatalog
6
6
  from resforge.apple.types import AppleColor
7
7
 
8
8
 
9
- def test_asset_catalog_lifecycle(tmp_path):
9
+ def test_asset_catalog_lifecycle(tmp_path: Path):
10
10
  catalog_name = "Assets"
11
11
  output_dir = tmp_path / "App"
12
12
  final_path = output_dir / f"{catalog_name}.xcassets"
@@ -21,12 +21,14 @@ def test_asset_catalog_lifecycle(tmp_path):
21
21
  assert (final_path / "Contents.json").exists()
22
22
  contents_file = final_path / "Primary.colorset" / "Contents.json"
23
23
  assert (contents_file).exists()
24
- data = json.loads(contents_file.read_text())
25
- assert data["colors"][0]["color"]["components"]["red"] == "1.000"
24
+ assert (
25
+ json.loads(contents_file.read_text())["colors"][0]["color"]["components"]["red"]
26
+ == "1.000"
27
+ )
26
28
  assert not temp_path.exists()
27
29
 
28
30
 
29
- def test_asset_catalog_atomic_failure(tmp_path):
31
+ def test_asset_catalog_atomic_failure(tmp_path: Path):
30
32
  catalog_name = "Assets"
31
33
  output_dir = tmp_path / "App"
32
34
  final_path = output_dir / f"{catalog_name}.xcassets"
@@ -44,7 +46,7 @@ def test_asset_catalog_atomic_failure(tmp_path):
44
46
  assert not temp_path.exists()
45
47
 
46
48
 
47
- def test_asset_catalog_overwrites_existing(tmp_path):
49
+ def test_asset_catalog_overwrites_existing(tmp_path: Path):
48
50
  catalog_name = "Assets"
49
51
  output_dir = tmp_path / "App"
50
52
  final_path = output_dir / f"{catalog_name}.xcassets"
@@ -60,7 +62,7 @@ def test_asset_catalog_overwrites_existing(tmp_path):
60
62
  assert (final_path / "Contents.json").exists()
61
63
 
62
64
 
63
- def test_require_context_enforcement(tmp_path):
65
+ def test_require_context_enforcement(tmp_path: Path):
64
66
  catalog = AssetCatalog(tmp_path / "App", "Assets")
65
67
 
66
68
  with pytest.raises(RuntimeError):
@@ -1,9 +1,10 @@
1
- import pytest
1
+ from pathlib import Path
2
2
 
3
+ import pytest
3
4
  from resforge.io import atomic_write
4
5
 
5
6
 
6
- def test_atomic_replace_existing(tmp_path):
7
+ def test_atomic_replace_existing(tmp_path: Path):
7
8
  target = tmp_path / "output.bin"
8
9
  target.write_bytes(b"old")
9
10
  with atomic_write(target) as f:
@@ -12,14 +13,14 @@ def test_atomic_replace_existing(tmp_path):
12
13
  assert not any(tmp_path.glob("*.tmp"))
13
14
 
14
15
 
15
- def test_creates_parent_dirs(tmp_path):
16
+ def test_creates_parent_dirs(tmp_path: Path):
16
17
  target = tmp_path / "a" / "b" / "c" / "output.bin"
17
18
  with atomic_write(target) as f:
18
19
  f.write(b"hello")
19
20
  assert target.exists()
20
21
 
21
22
 
22
- def test_no_corruption_on_exception(tmp_path):
23
+ def test_no_corruption_on_exception(tmp_path: Path):
23
24
  target = tmp_path / "output.bin"
24
25
  target.write_bytes(b"original")
25
26
 
@@ -1,5 +1,4 @@
1
1
  import pytest
2
-
3
2
  from resforge.color import Color
4
3
 
5
4
 
@@ -1,48 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- pull_request:
7
- branches: [main]
8
-
9
- jobs:
10
- test:
11
- runs-on: ubuntu-latest
12
- strategy:
13
- matrix:
14
- python-version: ["3.12", "3.13", "3.14"]
15
-
16
- steps:
17
- - uses: actions/checkout@v5
18
-
19
- - name: Set up Python
20
- uses: actions/setup-python@v5
21
- with:
22
- python-version: ${{ matrix.python-version }}
23
-
24
- - name: Install Hatch
25
- run: pip install hatch
26
-
27
- - name: Run tests
28
- run: hatch run pytest
29
-
30
- lint-and-check:
31
- runs-on: ubuntu-latest
32
- steps:
33
- - uses: actions/checkout@v5
34
-
35
- - name: Set up Python
36
- uses: actions/setup-python@v5
37
-
38
- - name: Install Hatch
39
- run: pip install hatch
40
-
41
- - name: Lint
42
- run: hatch run ruff check .
43
-
44
- - name: Type check
45
- run: hatch run basedpyright
46
-
47
- - name: Verify Build Metadata
48
- run: hatch build
@@ -1,29 +0,0 @@
1
- name: Publish
2
-
3
- on:
4
- release:
5
- types: [published]
6
-
7
- jobs:
8
- publish:
9
- runs-on: ubuntu-latest
10
-
11
- environment: pypi
12
- permissions:
13
- id-token: write
14
-
15
- steps:
16
- - uses: actions/checkout@v5
17
-
18
- - name: Set up Python
19
- uses: actions/setup-python@v5
20
- with:
21
- python-version: "3.12"
22
-
23
- - name: Build
24
- run: |
25
- pip install build
26
- python -m build
27
-
28
- - name: Publish to PyPI
29
- uses: pypa/gh-action-pypi-publish@release/v1
resforge-0.4.2/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Kipila Ltd
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
File without changes
File without changes