Skip to content

Instantly share code, notes, and snippets.

@philiptaron
Last active March 20, 2024 04:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save philiptaron/87b5889efb90d0a35809669d1d3d8816 to your computer and use it in GitHub Desktop.
Save philiptaron/87b5889efb90d0a35809669d1d3d8816 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
set -e
NIX_ID_RE="[a-zA-Z_][0-9A-Za-z_'-]*"
LIB_RE="$(sed -z 's/\n/|/g; s/|$/)/; s/^/(/' /tmp/names/lib)"
doWork() {
f=$1
# Find the top-level `with` statements
for top in $(rg '^(in )?with ' $f | sed 's/in with //; s/with //; s/;//;'); do
# We ought to have previously found all the names exported from that attrset.
names="/tmp/names/$top"
if [ ! -e "$names" ]; then
echo Cannot find $names
exit 1
fi
# Have Nix parse the file (no comments) then search through the identifiers for the names
# that were being accessed through the top-level `with`. The pipeline does this:
#
# 1. Remove comments by parsing the nix file
# 2. Select out just the identifiers (plus keywords)
# 3. Deduplicate them with `sort` and `uniq`
# 4. Have `nixfmt` format the expression
# 5. Replace the top-level `with` with `let ... inherit expr ... in`
replace="$(nix-instantiate --parse $f | \
rg -o "$NIX_ID_RE" | \
rg -xf $names | \
sort | \
uniq | \
perl -0777 -p -e "s/^/let inherit ($top) /; s/\$/; in EOF/" | \
nixfmt | \
sed 's/^EOF//')"
perl -i -p -e "s/^with $top*;/$replace/g" $f
# Since we replaced the uses at the top level, remove excess with.
perl -i -p -e "s/with $top;\s*//g" $f
done
# Access names from `lib` in just one way.
perl -0777 -i -p -e "s/lib\.${LIB_RE}/\$1/g" $f
# Combine adjacent `in ... let` scopes.
perl -0777 -i -p -e 's/in\s*let\s*/\n /g' $f
# Make sure that the file still parses.
while ! nix-instantiate --parse $f > /dev/null; do
read -p "Press <Return> to re-parse $f "
done
# Show the changes
git diff $f
echo "Look through $f above"
# Commit it with a standard commit message.
read -p "Press <Return> to commit it! "
# Make sure that the file still parses.
while ! nix-instantiate --parse $f > /dev/null; do
read -p "Press <Return> to re-parse $f "
done
git commit -m "$(printf 'Avoid top-level `with ...;` in %s' $f)" $f
}
ROOT="."
# If called with an argument, it's a either a file or a directory
if [ "$#" -ne 0 ]; then
if [ -f "$1" ]; then
doWork "$1"
exit 0
elif [ -d "$1" ]; then
ROOT="$1"
else
echo "$1 is neither file nor directory"
exit 1
fi
fi
# Loop through each file, working on it.
for f in $(rg '^(in )?with ' -l --sort=path -tnix "$ROOT"); do
doWork $f
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment