ohbin 0.2.2__tar.gz → 0.2.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.
- {ohbin-0.2.2 → ohbin-0.2.3}/PKG-INFO +2 -2
- {ohbin-0.2.2 → ohbin-0.2.3}/README.md +1 -1
- {ohbin-0.2.2 → ohbin-0.2.3}/pyproject.toml +1 -1
- {ohbin-0.2.2 → ohbin-0.2.3}/src/ohbin/_add.py +119 -39
- {ohbin-0.2.2 → ohbin-0.2.3}/LICENSE +0 -0
- {ohbin-0.2.2 → ohbin-0.2.3}/src/ohbin/__init__.py +0 -0
- {ohbin-0.2.2 → ohbin-0.2.3}/src/ohbin/__main__.py +0 -0
- {ohbin-0.2.2 → ohbin-0.2.3}/src/ohbin/_crypto.py +0 -0
- {ohbin-0.2.2 → ohbin-0.2.3}/src/ohbin/_engine.py +0 -0
- {ohbin-0.2.2 → ohbin-0.2.3}/src/ohbin/_errors.py +0 -0
- {ohbin-0.2.2 → ohbin-0.2.3}/src/ohbin/_gist.py +0 -0
- {ohbin-0.2.2 → ohbin-0.2.3}/src/ohbin/_github.py +0 -0
- {ohbin-0.2.2 → ohbin-0.2.3}/src/ohbin/_manifest.py +0 -0
- {ohbin-0.2.2 → ohbin-0.2.3}/src/ohbin/_platform.py +0 -0
- {ohbin-0.2.2 → ohbin-0.2.3}/src/ohbin/_retry.py +0 -0
- {ohbin-0.2.2 → ohbin-0.2.3}/src/ohbin/_types.py +0 -0
- {ohbin-0.2.2 → ohbin-0.2.3}/src/ohbin/cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ohbin
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Declarative GitHub-release binaries for uv projects — declare a tool in pyproject, `ohbin run <tool>` downloads, SHA256-verifies, caches, and execs it. POSIX only (uses flock).
|
|
5
5
|
Author: prostomarkeloff
|
|
6
6
|
Author-email: prostomarkeloff <prostomarkeloff@ohreally.me>
|
|
@@ -42,7 +42,7 @@ What it gives you:
|
|
|
42
42
|
It's a dev dependency, so with uv:
|
|
43
43
|
|
|
44
44
|
```sh
|
|
45
|
-
uv add --dev
|
|
45
|
+
uv add --dev ohbin
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
## How to?
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ohbin"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.3"
|
|
4
4
|
description = "Declarative GitHub-release binaries for uv projects — declare a tool in pyproject, `ohbin run <tool>` downloads, SHA256-verifies, caches, and execs it. POSIX only (uses flock)."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -174,8 +174,121 @@ def _detach_trailing_trivia(table: Table) -> list[BodyEntry]:
|
|
|
174
174
|
return trailing
|
|
175
175
|
|
|
176
176
|
|
|
177
|
+
def _set_asset_fields(sub: Table, asset: AssetEntry) -> None:
|
|
178
|
+
"""Set url/sha256/binary_sha256 on one asset sub-table, in place."""
|
|
179
|
+
sub["url"] = asset["url"]
|
|
180
|
+
sub["sha256"] = asset["sha256"]
|
|
181
|
+
if "binary_sha256" in asset:
|
|
182
|
+
sub["binary_sha256"] = asset["binary_sha256"]
|
|
183
|
+
elif "binary_sha256" in sub:
|
|
184
|
+
del sub["binary_sha256"]
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _build_assets_table(cfg_assets: dict[str, AssetEntry]) -> Table:
|
|
188
|
+
"""A fresh `[...assets.<plat>]` super-table from a resolved asset map."""
|
|
189
|
+
import tomlkit
|
|
190
|
+
|
|
191
|
+
assets = tomlkit.table(is_super_table=True)
|
|
192
|
+
for plat_key, asset in cfg_assets.items():
|
|
193
|
+
sub = tomlkit.table()
|
|
194
|
+
_set_asset_fields(sub, asset)
|
|
195
|
+
assets[plat_key] = sub
|
|
196
|
+
return assets
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _set_generated_fields(entry: Table, cfg: ToolConfig) -> None:
|
|
200
|
+
"""Write the fields ohbin owns: repo / version / binary / encrypted / password.
|
|
201
|
+
|
|
202
|
+
Assigning an existing key updates its value in place — position and any attached
|
|
203
|
+
comment are kept — so user-authored keys ohbin doesn't generate (e.g.
|
|
204
|
+
`password_committed_ok`) are never touched here. `password` is only written when
|
|
205
|
+
the caller supplied one (omitting it lets a manually-set password survive).
|
|
206
|
+
"""
|
|
207
|
+
entry["repo"] = cfg["repo"]
|
|
208
|
+
entry["version"] = cfg["version"]
|
|
209
|
+
entry["binary"] = cfg["binary"]
|
|
210
|
+
if cfg.get("encrypted"):
|
|
211
|
+
entry["encrypted"] = True
|
|
212
|
+
if "password" in cfg:
|
|
213
|
+
entry["password"] = cfg["password"]
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _update_assets_in_place(entry: Table, cfg_assets: dict[str, AssetEntry]) -> None:
|
|
217
|
+
"""Refresh a tool's asset sub-tables without disturbing surrounding trivia.
|
|
218
|
+
|
|
219
|
+
Same platform set (the common re-publish): update each sub-table's values in
|
|
220
|
+
place — nothing structural moves, so the comment block that belongs to the next
|
|
221
|
+
section (which tomlkit parses into the last asset's body) stays put. If the set
|
|
222
|
+
or order changed, detach that trailing block, rebuild the sub-tables, then
|
|
223
|
+
re-attach it to the new last asset so it still precedes the next section.
|
|
224
|
+
"""
|
|
225
|
+
import tomlkit
|
|
226
|
+
from tomlkit.items import Table
|
|
227
|
+
|
|
228
|
+
existing = entry.get("assets")
|
|
229
|
+
if not isinstance(existing, Table):
|
|
230
|
+
entry["assets"] = _build_assets_table(cfg_assets)
|
|
231
|
+
return
|
|
232
|
+
if list(existing.keys()) == list(cfg_assets.keys()):
|
|
233
|
+
for plat, asset in cfg_assets.items():
|
|
234
|
+
_set_asset_fields(existing[plat], asset)
|
|
235
|
+
return
|
|
236
|
+
# Set/order changed. Reconcile rather than empty-and-rebuild: deleting the
|
|
237
|
+
# first sub-table perturbs the leading blank line before the assets block, so
|
|
238
|
+
# keep the platforms that survive (update in place), drop the gone ones, and
|
|
239
|
+
# append the new ones. The trailing comment block lives in the current last
|
|
240
|
+
# asset's body — detach it first so a removed-or-shifted last asset can't strand
|
|
241
|
+
# it, then re-attach it to whatever asset ends up last.
|
|
242
|
+
trailing = _detach_trailing_trivia(entry)
|
|
243
|
+
for plat in list(existing.keys()):
|
|
244
|
+
if plat not in cfg_assets:
|
|
245
|
+
del existing[plat]
|
|
246
|
+
for plat, asset in cfg_assets.items():
|
|
247
|
+
sub = existing.get(plat)
|
|
248
|
+
if isinstance(sub, Table):
|
|
249
|
+
_set_asset_fields(sub, asset)
|
|
250
|
+
else:
|
|
251
|
+
sub = tomlkit.table()
|
|
252
|
+
_set_asset_fields(sub, asset)
|
|
253
|
+
existing[plat] = sub
|
|
254
|
+
if trailing:
|
|
255
|
+
_deepest_last_table(entry).value.body.extend(trailing)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _append_new_tool(tools: Table, name: str, cfg: ToolConfig) -> None:
|
|
259
|
+
"""Append a brand-new tool entry, keeping any following section's comment block.
|
|
260
|
+
|
|
261
|
+
A comment block before the next top-level section is parsed into the innermost
|
|
262
|
+
body of whichever tool is physically last in `[tool.ohbin.tools]`. tomlkit would
|
|
263
|
+
place the new table *after* that trivia — stranding the comment above the new
|
|
264
|
+
entry and dropping the separator — so detach it and move it past the new entry.
|
|
265
|
+
"""
|
|
266
|
+
import tomlkit
|
|
267
|
+
|
|
268
|
+
next_section_trivia: list[BodyEntry] = []
|
|
269
|
+
if _deepest_last_table(tools) is not tools:
|
|
270
|
+
next_section_trivia = _detach_trailing_trivia(tools)
|
|
271
|
+
|
|
272
|
+
entry = tomlkit.table()
|
|
273
|
+
_set_generated_fields(entry, cfg)
|
|
274
|
+
if cfg.get("password_committed_ok"):
|
|
275
|
+
entry["password_committed_ok"] = True
|
|
276
|
+
entry["assets"] = _build_assets_table(cfg["assets"])
|
|
277
|
+
|
|
278
|
+
tools[name] = entry
|
|
279
|
+
if next_section_trivia:
|
|
280
|
+
_deepest_last_table(entry).value.body.extend(next_section_trivia)
|
|
281
|
+
|
|
282
|
+
|
|
177
283
|
def write_tool(pyproject: Path, name: str, cfg: ToolConfig) -> None:
|
|
178
|
-
"""Write/overwrite `[tool.ohbin.tools.<name>]`, preserving the rest of the file.
|
|
284
|
+
"""Write/overwrite `[tool.ohbin.tools.<name>]`, preserving the rest of the file.
|
|
285
|
+
|
|
286
|
+
Overwriting an existing tool mutates it *in place*: only the ohbin-generated
|
|
287
|
+
fields and asset hashes change, so interior comments and user-authored keys
|
|
288
|
+
(e.g. `password_committed_ok`) survive untouched. Appending a new tool builds a
|
|
289
|
+
fresh table and moves any following comment block (which tomlkit parses into the
|
|
290
|
+
last existing tool's body) past the new entry.
|
|
291
|
+
"""
|
|
179
292
|
import tomlkit
|
|
180
293
|
from tomlkit import TOMLDocument
|
|
181
294
|
from tomlkit.items import Table
|
|
@@ -196,46 +309,13 @@ def write_tool(pyproject: Path, name: str, cfg: ToolConfig) -> None:
|
|
|
196
309
|
super_table=True,
|
|
197
310
|
)
|
|
198
311
|
|
|
199
|
-
# A comment block before the next top-level section is parsed into the
|
|
200
|
-
# innermost body of whichever tool is physically last in `[tool.ohbin.tools]`.
|
|
201
|
-
# Two cases must both preserve it:
|
|
202
|
-
# - overwriting a tool: its own trailing trivia goes with the rebuilt entry;
|
|
203
|
-
# - appending a new tool: tomlkit puts the new table *after* that trivia,
|
|
204
|
-
# stranding the comment above the new entry and dropping the separator —
|
|
205
|
-
# so detach it from the current last tool and move it past the new one.
|
|
206
312
|
old_entry = tools.get(name)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
entry = tomlkit.table()
|
|
214
|
-
entry["repo"] = cfg["repo"]
|
|
215
|
-
entry["version"] = cfg["version"]
|
|
216
|
-
entry["binary"] = cfg["binary"]
|
|
217
|
-
if cfg.get("encrypted"):
|
|
218
|
-
entry["encrypted"] = True
|
|
219
|
-
if "password" in cfg:
|
|
220
|
-
entry["password"] = cfg["password"]
|
|
221
|
-
if cfg.get("password_committed_ok"):
|
|
222
|
-
entry["password_committed_ok"] = True
|
|
223
|
-
|
|
224
|
-
assets = tomlkit.table(is_super_table=True)
|
|
225
|
-
for plat_key, asset in cfg["assets"].items():
|
|
226
|
-
asset_table = tomlkit.table()
|
|
227
|
-
asset_table["url"] = asset["url"]
|
|
228
|
-
asset_table["sha256"] = asset["sha256"]
|
|
229
|
-
if "binary_sha256" in asset:
|
|
230
|
-
asset_table["binary_sha256"] = asset["binary_sha256"]
|
|
231
|
-
assets[plat_key] = asset_table
|
|
232
|
-
entry["assets"] = assets
|
|
313
|
+
if isinstance(old_entry, Table):
|
|
314
|
+
_set_generated_fields(old_entry, cfg)
|
|
315
|
+
_update_assets_in_place(old_entry, cfg["assets"])
|
|
316
|
+
else:
|
|
317
|
+
_append_new_tool(tools, name, cfg)
|
|
233
318
|
|
|
234
|
-
tools[name] = entry
|
|
235
|
-
if own_trailing:
|
|
236
|
-
_deepest_last_table(entry).value.body.extend(own_trailing)
|
|
237
|
-
if next_section_trivia:
|
|
238
|
-
_deepest_last_table(entry).value.body.extend(next_section_trivia)
|
|
239
319
|
pyproject.write_text(tomlkit.dumps(doc))
|
|
240
320
|
|
|
241
321
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|