···5656 see [`maintainer-list.nix`] for the fields' definition.
57575858[`maintainer-list.nix`]: ../maintainer-list.nix
5959+6060+6161+## Conventions
6262+6363+### `sha-to-sri.py`
6464+6565+`sha-to-sri.py path ...` (atomically) rewrites hash attributes (named `hash` or `sha(1|256|512)`)
6666+into the SRI format: `hash = "{hash name}-{base64 encoded value}"`.
6767+6868+`path` must point to either a nix file, or a directory which will be automatically traversed.
6969+7070+`sha-to-sri.py` automatically skips files whose first non-empty line contains `generated by` or `do not edit`.
7171+Moreover, when walking a directory tree, the script will skip files whose name is `yarn.nix` or contains `generated`.
+67-49
maintainers/scripts/sha-to-sri.py
···11#!/usr/bin/env nix-shell
22#! nix-shell -i "python3 -I" -p "python3.withPackages(p: with p; [ rich structlog ])"
3344-from abc import ABC, abstractclassmethod, abstractmethod
44+from abc import ABC, abstractmethod
55from contextlib import contextmanager
66from pathlib import Path
77from structlog.contextvars import bound_contextvars as log_context
88from typing import ClassVar, List, Tuple
991010-import hashlib, re, structlog
1010+import hashlib, logging, re, structlog
111112121313logger = structlog.getLogger("sha-to-SRI")
···2626 assert len(digest) == self.n
27272828 from base64 import b64encode
2929+2930 return f"{self.hashName}-{b64encode(digest).decode()}"
30313132 @classmethod
3232- def all(cls, h) -> 'List[Encoding]':
3333- return [ c(h) for c in cls.__subclasses__() ]
3333+ def all(cls, h) -> "List[Encoding]":
3434+ return [c(h) for c in cls.__subclasses__()]
34353536 def __init__(self, h):
3637 self.n = h.digest_size
···38393940 @property
4041 @abstractmethod
4141- def length(self) -> int:
4242- ...
4242+ def length(self) -> int: ...
43434444 @property
4545 def regex(self) -> str:
4646 return f"[{self.alphabet}]{{{self.length}}}"
47474848 @abstractmethod
4949- def decode(self, s: str) -> bytes:
5050- ...
4949+ def decode(self, s: str) -> bytes: ...
515052515352class Nix32(Encoding):
5453 alphabet = "0123456789abcdfghijklmnpqrsvwxyz"
5555- inverted = { c: i for i, c in enumerate(alphabet) }
5454+ inverted = {c: i for i, c in enumerate(alphabet)}
56555756 @property
5857 def length(self):
5958 return 1 + (8 * self.n) // 5
5959+6060 def decode(self, s: str):
6161 assert len(s) == self.length
6262- out = [ 0 for _ in range(self.n) ]
6363- # TODO: Do better than a list of byte-sized ints
6262+ out = bytearray(self.n)
64636564 for n, c in enumerate(reversed(s)):
6665 digit = self.inverted[c]
6766 i, j = divmod(5 * n, 8)
6868- out[i] = out[i] | (digit << j) & 0xff
6767+ out[i] = out[i] | (digit << j) & 0xFF
6968 rem = digit >> (8 - j)
7069 if rem == 0:
7170 continue
7271 elif i < self.n:
7373- out[i+1] = rem
7272+ out[i + 1] = rem
7473 else:
7574 raise ValueError(f"Invalid nix32 hash: '{s}'")
76757776 return bytes(out)
7777+78787979class Hex(Encoding):
8080 alphabet = "0-9A-Fa-f"
···8282 @property
8383 def length(self):
8484 return 2 * self.n
8585+8586 def decode(self, s: str):
8687 from binascii import unhexlify
8888+8789 return unhexlify(s)
9090+88918992class Base64(Encoding):
9093 alphabet = "A-Za-z0-9+/"
···9497 """Number of characters in data and padding."""
9598 i, k = divmod(self.n, 3)
9699 return 4 * i + (0 if k == 0 else k + 1), (3 - k) % 3
100100+97101 @property
98102 def length(self):
99103 return sum(self.format)
104104+100105 @property
101106 def regex(self):
102107 data, padding = self.format
103108 return f"[{self.alphabet}]{{{data}}}={{{padding}}}"
109109+104110 def decode(self, s):
105111 from base64 import b64decode
112112+106113 return b64decode(s, validate = True)
107114108115109109-_HASHES = (hashlib.new(n) for n in ('SHA-256', 'SHA-512'))
110110-ENCODINGS = {
111111- h.name: Encoding.all(h)
112112- for h in _HASHES
113113-}
116116+_HASHES = (hashlib.new(n) for n in ("SHA-256", "SHA-512"))
117117+ENCODINGS = {h.name: Encoding.all(h) for h in _HASHES}
114118115119RE = {
116120 h: "|".join(
117117- (f"({h}-)?" if e.name == 'base64' else '') +
118118- f"(?P<{h}_{e.name}>{e.regex})"
121121+ (f"({h}-)?" if e.name == "base64" else "") + f"(?P<{h}_{e.name}>{e.regex})"
119122 for e in encodings
120120- ) for h, encodings in ENCODINGS.items()
123123+ )
124124+ for h, encodings in ENCODINGS.items()
121125}
122126123123-_DEF_RE = re.compile("|".join(
124124- f"(?P<{h}>{h} = (?P<{h}_quote>['\"])({re})(?P={h}_quote);)"
125125- for h, re in RE.items()
126126-))
127127+_DEF_RE = re.compile(
128128+ "|".join(
129129+ f"(?P<{h}>{h} = (?P<{h}_quote>['\"])({re})(?P={h}_quote);)"
130130+ for h, re in RE.items()
131131+ )
132132+)
127133128134129135def defToSRI(s: str) -> str:
···153159154160@contextmanager
155161def atomicFileUpdate(target: Path):
156156- '''Atomically replace the contents of a file.
162162+ """Atomically replace the contents of a file.
157163158164 Guarantees that no temporary files are left behind, and `target` is either
159165 left untouched, or overwritten with new content if no exception was raised.
···164170165171 Upon exiting the context, the files are closed; if no exception was
166172 raised, `new` (atomically) replaces the `target`, otherwise it is deleted.
167167- '''
173173+ """
168174 # That's mostly copied from noto-emoji.py, should DRY it out
169169- from tempfile import mkstemp
170170- fd, _p = mkstemp(
171171- dir = target.parent,
172172- prefix = target.name,
173173- )
174174- tmpPath = Path(_p)
175175+ from tempfile import NamedTemporaryFile
175176176177 try:
177178 with target.open() as original:
178178- with tmpPath.open('w') as new:
179179+ with NamedTemporaryFile(
180180+ dir = target.parent,
181181+ prefix = target.stem,
182182+ suffix = target.suffix,
183183+ delete = False,
184184+ mode="w", # otherwise the file would be opened in binary mode by default
185185+ ) as new:
186186+ tmpPath = Path(new.name)
179187 yield (original, new)
180188181189 tmpPath.replace(target)
···188196def fileToSRI(p: Path):
189197 with atomicFileUpdate(p) as (og, new):
190198 for i, line in enumerate(og):
191191- with log_context(line=i):
199199+ with log_context(line = i):
192200 new.write(defToSRI(line))
193201194202195195-_SKIP_RE = re.compile(
196196- "(generated by)|(do not edit)",
197197- re.IGNORECASE
198198-)
203203+_SKIP_RE = re.compile("(generated by)|(do not edit)", re.IGNORECASE)
199204200205if __name__ == "__main__":
201201- from sys import argv, stderr
206206+ from sys import argv
207207+202208 logger.info("Starting!")
203209204204- for arg in argv[1:]:
205205- p = Path(arg)
206206- with log_context(path=str(p)):
210210+ def handleFile(p: Path, skipLevel = logging.INFO):
211211+ with log_context(file = str(p)):
207212 try:
208208- if p.name == "yarn.nix" or p.name.find("generated") != -1:
209209- logger.warning("File looks autogenerated, skipping!")
210210- continue
211211-212213 with p.open() as f:
213214 for line in f:
214215 if line.strip():
215216 break
216217217218 if _SKIP_RE.search(line):
218218- logger.warning("File looks autogenerated, skipping!")
219219- continue
219219+ logger.log(skipLevel, "File looks autogenerated, skipping!")
220220+ return
220221221222 fileToSRI(p)
223223+222224 except Exception as exn:
223225 logger.error(
224226 "Unhandled exception, skipping file!",
···226228 )
227229 else:
228230 logger.info("Finished processing file")
231231+232232+ for arg in argv[1:]:
233233+ p = Path(arg)
234234+ with log_context(arg = arg):
235235+ if p.is_file():
236236+ handleFile(p, skipLevel = logging.WARNING)
237237+238238+ elif p.is_dir():
239239+ logger.info("Recursing into directory")
240240+ for q in p.glob("**/*.nix"):
241241+ if q.is_file():
242242+ if q.name == "yarn.nix" or q.name.find("generated") != -1:
243243+ logger.info("File looks autogenerated, skipping!")
244244+ continue
245245+246246+ handleFile(q)
···29293030 meta = with lib; {
3131 description = "New symbolic model checker for the analysis of synchronous finite-state and infinite-state systems";
3232- homepage = "https://nuxmv.fbk.eu/pmwiki.php";
3232+ homepage = "https://nusmv.fbk.eu/";
3333 maintainers = with maintainers; [ mgttlinger ];
3434 sourceProvenance = with sourceTypes; [ binaryNativeCode ];
3535 platforms = platforms.linux;
···167167 };
168168169169 meta = {
170170- description = "Like neofetch, but much faster because written in C";
170170+ description = "An actively maintained, feature-rich and performance oriented, neofetch like system information tool";
171171 homepage = "https://github.com/fastfetch-cli/fastfetch";
172172 changelog = "https://github.com/fastfetch-cli/fastfetch/releases/tag/${finalAttrs.version}";
173173 license = lib.licenses.mit;