mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-07-17 23:50:17 +03:00
104 lines
2.7 KiB
Python
104 lines
2.7 KiB
Python
![]() |
#!/usr/bin/env nix-shell
|
||
|
#! nix-shell -i "python3 -I" -p python3
|
||
|
|
||
|
from contextlib import contextmanager
|
||
|
from pathlib import Path
|
||
|
|
||
|
import re
|
||
|
|
||
|
|
||
|
alphabet = "0123456789abcdfghijklmnpqrsvwxyz"
|
||
|
inverted_alphabet = { c: i for i, c in enumerate(alphabet) }
|
||
|
|
||
|
|
||
|
def decode(s: str) -> bytes:
|
||
|
# only support sha256 hashes for now
|
||
|
assert len(s) == 52
|
||
|
out = [ 0 for _ in range(32) ]
|
||
|
# TODO: Do better than a list of byte-sized ints
|
||
|
|
||
|
for n, c in enumerate(reversed(s)):
|
||
|
digit = inverted_alphabet[c]
|
||
|
i, j = divmod(5 * n, 8)
|
||
|
out[i] = out[i] | (digit << j) & 0xff
|
||
|
rem = digit >> (8 - j)
|
||
|
if rem == 0:
|
||
|
continue
|
||
|
elif i < 31:
|
||
|
out[i+1] = rem
|
||
|
else:
|
||
|
raise ValueError(f"Invalid nix32 hash: '{s}'")
|
||
|
|
||
|
return bytes(out)
|
||
|
|
||
|
|
||
|
def toSRI(s: str) -> str:
|
||
|
from base64 import b64encode
|
||
|
|
||
|
digest = decode(s)
|
||
|
assert(len(digest) == 32)
|
||
|
return f"sha256-{b64encode(digest).decode()}"
|
||
|
|
||
|
|
||
|
RE = f"[{alphabet}]" "{52}";
|
||
|
# Ohno I used evil, irregular backrefs ^^'
|
||
|
_sha256_re = re.compile(f'sha256 = (?P<quote>["\'])(?P<nix32>{RE})(?P=quote);')
|
||
|
|
||
|
def defToSRI(s: str) -> str:
|
||
|
return _sha256_re.sub(
|
||
|
lambda m: f'hash = "{toSRI(m["nix32"])}";',
|
||
|
s,
|
||
|
)
|
||
|
|
||
|
|
||
|
@contextmanager
|
||
|
def atomicFileUpdate(target: Path):
|
||
|
'''Atomically replace the contents of a file.
|
||
|
|
||
|
Guarantees that no temporary files are left behind, and `target` is either
|
||
|
left untouched, or overwritten with new content if no exception was raised.
|
||
|
|
||
|
Yields a pair `(original, new)` of open files.
|
||
|
`original` is the pre-existing file at `target`, open for reading;
|
||
|
`new` is an empty, temporary file in the same filder, open for writing.
|
||
|
|
||
|
Upon exiting the context, the files are closed; if no exception was
|
||
|
raised, `new` (atomically) replaces the `target`, otherwise it is deleted.
|
||
|
'''
|
||
|
# That's mostly copied from noto-emoji.py, should DRY it out
|
||
|
from tempfile import mkstemp
|
||
|
fd, _p = mkstemp(
|
||
|
dir = target.parent,
|
||
|
prefix = target.name,
|
||
|
)
|
||
|
tmpPath = Path(_p)
|
||
|
|
||
|
try:
|
||
|
with target.open() as original:
|
||
|
with tmpPath.open('w') as new:
|
||
|
yield (original, new)
|
||
|
|
||
|
tmpPath.replace(target)
|
||
|
|
||
|
except Exception:
|
||
|
tmpPath.unlink(missing_ok = True)
|
||
|
raise
|
||
|
|
||
|
|
||
|
def fileToSRI(p: Path):
|
||
|
with atomicFileUpdate(p) as (og, new):
|
||
|
for line in og:
|
||
|
new.write(defToSRI(line))
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
from sys import argv, stderr
|
||
|
|
||
|
for arg in argv[1:]:
|
||
|
p = Path(arg)
|
||
|
if not p.is_file():
|
||
|
print(f"Argument '{arg}' is not a regular file's path", file=stderr)
|
||
|
else:
|
||
|
print(f"Processing '{arg}'")
|
||
|
fileToSRI(p)
|