diff options
| author | Dylan <boss@tehbox.org> | 2026-10-17 22:02:09 +1300 |
|---|---|---|
| committer | Dylan <boss@tehbox.org> | 2026-10-17 22:04:40 +1300 |
| commit | 008b591a0a5942ae42f4c07a5abbee4dbd2d0743 (patch) | |
| tree | 0ae88a36bc00ede3b43b31489adae633dc54e8cd /tabify-nix.py | |
| parent | 280e1bbe5e733128a451260685cc8ecd10a042a3 (diff) | |
| download | nixos-configuration-008b591a0a5942ae42f4c07a5abbee4dbd2d0743.tar.gz nixos-configuration-008b591a0a5942ae42f4c07a5abbee4dbd2d0743.zip | |
feat: Formatting changes
Diffstat (limited to 'tabify-nix.py')
| -rwxr-xr-x | tabify-nix.py | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/tabify-nix.py b/tabify-nix.py new file mode 100755 index 0000000..0220ab9 --- /dev/null +++ b/tabify-nix.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +"""Convert nixfmt's 2-space indentation to tabs. + +Usage: ./tabify-nix.py [FILE...] (default: every git-tracked .nix file) + +Only lines that start in code are touched. Lines that start inside a string +('' or ") are left exactly as they are, because Nix only strips leading +*spaces* from '' strings, so tabs there would change the string's value. +""" +import subprocess +import sys + +INDENT = 2 + + +def lex_line(line, stack): + """Advance the lexer state `stack` over one line (mutates it in place). + + Stack entries: ["code", brace_depth], ["ind"] (''), ["dq"] ("), ["block"]. + A "code" entry above a string entry is a ${ } interpolation. + """ + i, n = 0, len(line) + while i < n: + mode = stack[-1][0] + rest = line[i:] + if mode == "code": + c = line[i] + if c == "#": + return + if rest.startswith("/*"): + stack.append(["block"]) + i += 2 + elif c == '"': + stack.append(["dq"]) + i += 1 + elif rest.startswith("''"): + stack.append(["ind"]) + i += 2 + elif c == "{": + stack[-1][1] += 1 + i += 1 + elif c == "}": + if stack[-1][1] == 0 and len(stack) > 1: + stack.pop() # end of ${ } interpolation + else: + stack[-1][1] -= 1 + i += 1 + else: + i += 1 + elif mode == "block": + if rest.startswith("*/"): + stack.pop() + i += 2 + else: + i += 1 + elif mode == "dq": + if rest.startswith("\\"): + i += 2 + elif rest.startswith("$${"): + i += 3 + elif rest.startswith("${"): + stack.append(["code", 0]) + i += 2 + elif rest.startswith('"'): + stack.pop() + i += 1 + else: + i += 1 + elif mode == "ind": + if rest.startswith("'''") or rest.startswith("''$"): + i += 3 + elif rest.startswith("''\\"): + i += 4 + elif rest.startswith("''"): + stack.pop() + i += 2 + elif rest.startswith("$${"): + i += 3 + elif rest.startswith("${"): + stack.append(["code", 0]) + i += 2 + else: + i += 1 + + +def tabify(text): + stack = [["code", 0]] + out = [] + for line in text.split("\n"): + if stack[-1][0] in ("code", "block"): + stripped = line.lstrip(" \t") + ws = line[: len(line) - len(stripped)] + if stripped: + tabs = ws.count("\t") + spaces = ws.count(" ") + line = "\t" * (tabs + spaces // INDENT) + " " * (spaces % INDENT) + stripped + else: + line = "" + out.append(line) + lex_line(line, stack) + if stack != [["code", 0]]: + raise ValueError(f"unbalanced lexer state at EOF: {stack}") + return "\n".join(out) + + +def main(): + files = sys.argv[1:] or subprocess.run( + ["git", "ls-files", "*.nix"], capture_output=True, text=True, check=True + ).stdout.split() + failed = False + for path in files: + with open(path) as f: + src = f.read() + try: + new = tabify(src) + except ValueError as e: + print(f"{path}: {e}, skipped", file=sys.stderr) + failed = True + continue + if new != src: + with open(path, "w") as f: + f.write(new) + print(f"tabified {path}") + sys.exit(1 if failed else 0) + + +if __name__ == "__main__": + main() |
