Skip to content

flake: add diff-plugins command #3510

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions flake/dev/devshell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@
command = ''${pkgs.python3.interpreter} ${./new-plugin.py} "$@"'';
help = "Create a new plugin";
}
{
name = "diff-plugins";
command = ''${pkgs.python3.interpreter} ${./diff-plugins.py} "$@"'';
help = "Compare available plugins with another nixvim commit";
}
];
};
};
Expand Down
108 changes: 108 additions & 0 deletions flake/dev/diff-plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/usr/bin/env python3

import json
import re
import subprocess
from argparse import ArgumentParser, ArgumentTypeError


def main():
"""
Main function to compare nixvim plugins with another revision.
"""

parser = ArgumentParser(description="Compare nixvim plugins with another revision")
parser.add_argument(
"flakeref",
metavar="old",
help="the commit or flakeref to compare against",
type=flakeref,
)
parser.add_argument(
"--compact",
"-c",
help="produce compact json instead of prettifying",
action="store_true",
)
args = parser.parse_args()

after_plugins = list_plugins(".")
before_plugins = list_plugins(args.flakeref)
Comment on lines +29 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could make it more robust and allow passing in 2 flakerefs if we wanted to compare between different locations, even. Then default to . when its not provided? Just a thought for future possibility

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had this in the previous attempts. I left it out of this one to keep things as simple as possible.

Follow-up PRs welcome 😉

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ended up actually needing this in #3513 🙈

print(
json.dumps(
diff(before_plugins, after_plugins),
separators=((",", ":") if args.compact else None),
indent=(None if args.compact else 4),
sort_keys=(not args.compact),
default=list,
)
)


def flakeref(arg):
"""
An argparse type that represents a flakeref, or a partial flakeref that we can
normalise using sane defaults.
"""
default_protocol = "github:"
default_repo = "nix-community/nixvim"
sha_rxp = re.compile(r"^[A-Fa-f0-9]{6,40}$")
repo_rxp = re.compile(
r"^(?P<protocol>[^:/]+:)?(?P<repo>(:?[^/]+)/(:?[^/]+))(?P<sha>/[A-Fa-f0-9]{6,40})?$"
)
if sha_rxp.match(arg):
return f"{default_protocol}{default_repo}/{arg}"
elif m := repo_rxp.match(arg):
protocol = m.group("protocol") or default_protocol
repo = m.group("repo")
sha = m.group("sha") or ""
return protocol + repo + sha
else:
raise ArgumentTypeError(f"Unsupported commit or flakeref format: {arg}")


def diff(before: list[str], after: list[str]):
"""
Compare the before and after plugin sets.
"""
# TODO: also guess at "renamed" plugins heuristically
return {
"added": {n: after[n] - before[n] for n in ["plugins", "colorschemes"]},
"removed": {n: before[n] - after[n] for n in ["plugins", "colorschemes"]},
}


def list_plugins(flake: str) -> list[str]:
"""
Gets a list of plugins that exist in the flake.
Grouped as "plugins" and "colorschemes"
"""
expr = """
options:
builtins.listToAttrs (
map
(name: {
inherit name;
value = builtins.attrNames options.${name};
})
[
"plugins"
"colorschemes"
]
)
"""
cmd = [
"nix",
"eval",
f"{flake}#nixvimConfiguration.options",
"--apply",
expr,
"--json",
]
out = subprocess.check_output(cmd)
# Parse as json, converting the lists to sets
return {k: set(v) for k, v in json.loads(out).items()}
Comment on lines +75 to +104
Copy link
Contributor

@khaneliman khaneliman Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to try just leveraging our list-plugins package/script?

Copy link
Member Author

@MattSturgeon MattSturgeon Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about it briefly, but I think it's simpler to keep them separate?

The list-plugins script is meant to print a user-readable summary of the plugins, right? While this one is meant to print a JSON diff.

Unless you have ideas for how we could do it without adding too much complexity to the list-plugins script?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this looks to be a pretty simple implementation. We can always refactor later since it might require some more rework for list-plugins



if __name__ == "__main__":
main()