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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ohbin
3
- Version: 0.2.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 git+https://github.com/prostomarkeloff/ohbin.git
45
+ uv add --dev ohbin
46
46
  ```
47
47
 
48
48
  ## How to?
@@ -30,7 +30,7 @@ What it gives you:
30
30
  It's a dev dependency, so with uv:
31
31
 
32
32
  ```sh
33
- uv add --dev git+https://github.com/prostomarkeloff/ohbin.git
33
+ uv add --dev ohbin
34
34
  ```
35
35
 
36
36
  ## How to?
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ohbin"
3
- version = "0.2.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
- own_trailing = _detach_trailing_trivia(old_entry) if isinstance(old_entry, Table) else []
208
- appending = not isinstance(old_entry, Table)
209
- next_section_trivia: list[BodyEntry] = []
210
- if appending and _deepest_last_table(tools) is not tools:
211
- next_section_trivia = _detach_trailing_trivia(tools)
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