import glob import os import re from argparse import ArgumentParser, RawTextHelpFormatter from dataclasses import dataclass from enum import Enum from typing import Optional # Ignore files that are not plugin definitions EXCLUDES: list[str] = [ # Patterns "TEMPLATE.nix", "deprecations.nix", "helpers.nix", "renamed-options", "settings-options.nix", # Specific files "colorschemes/base16/theme-list.nix", "plugins/by-name/blink-cmp/provider-config.nix", "plugins/by-name/dap/dapHelpers.nix", "plugins/by-name/efmls-configs/packages.nix", "plugins/by-name/hydra/hydras-option.nix", "plugins/by-name/hydra/settings-options.nix", "plugins/by-name/neotest/adapters-list.nix", "plugins/by-name/neotest/adapters.nix", "plugins/by-name/none-ls/_mk-source-plugin.nix", "plugins/by-name/none-ls/packages.nix", "plugins/by-name/none-ls/prettier.nix", "plugins/by-name/none-ls/prettierd.nix", "plugins/by-name/none-ls/settings.nix", "plugins/by-name/none-ls/sources.nix", "plugins/by-name/openscad/fuzzy-finder-plugin-option.nix", "plugins/by-name/rustaceanvim/renamed-options.nix", "plugins/by-name/telescope/extensions/_mk-extension.nix", "plugins/by-name/telescope/extensions/default.nix", "plugins/cmp/auto-enable.nix", "plugins/cmp/options/", "plugins/cmp/sources/cmp-fish.nix", "plugins/cmp/sources/default.nix", "plugins/default.nix", "plugins/deprecation.nix", "plugins/lsp/language-servers/", "plugins/lsp/lsp-packages.nix", ] class Kind(Enum): NEOVIM = 1 VIM = 2 MISC = 3 class State(Enum): UNKNOWN = "❔" NEW = "✅" OLD = "❌" KNOWN_PATHS: dict[ str, tuple[ State, # If the implem is "legacy" or up to date Kind, # Vim / Neovim / misc bool, # Has deprecation warnings ], ] = { "plugins/by-name/chadtree/default.nix": (State.OLD, Kind.NEOVIM, False), "plugins/by-name/coq-thirdparty/default.nix": (State.OLD, Kind.NEOVIM, False), "plugins/by-name/leap/default.nix": (State.OLD, Kind.NEOVIM, False), "plugins/by-name/lspkind/default.nix": (State.OLD, Kind.NEOVIM, False), "plugins/by-name/rainbow-delimiters/default.nix": (State.OLD, Kind.NEOVIM, False), "plugins/by-name/treesitter-refactor/default.nix": (State.OLD, Kind.MISC, True), "plugins/by-name/treesitter-textobjects/default.nix": ( State.OLD, Kind.NEOVIM, True, ), } for telescope_extension_name, has_depr_warnings in { "file-browser": True, "frecency": True, "fzf-native": True, "fzy-native": True, "live-greps-args": False, "manix": False, "media-files": True, "project": False, "ui-select": False, "undo": True, "zoxide": False, }.items(): KNOWN_PATHS[ f"plugins/by-name/telescope/extensions/{telescope_extension_name}.nix" ] = ( State.NEW, Kind.MISC, has_depr_warnings, ) DEPRECATION_REGEX: list[re.Pattern] = [ re.compile(rf".*{pattern}", re.DOTALL) for pattern in [ "deprecateExtra", "mkRemovedOptionModule", "mkRenamedOptionModule", "optionsRenamedToSettings", ] ] @dataclass class Plugin: path: str state: State kind: Kind dep_warnings: bool def __str__(self) -> str: state_icon: str = self.state.value kind_icon: str match self.kind: case Kind.NEOVIM: kind_icon = "\033[94m" + " " case Kind.VIM: kind_icon = "\033[92m" + " " case Kind.MISC: kind_icon = "\033[92m" + "🟢" case _: assert False deprecation_icon: str = "⚠️ " if self.dep_warnings else " " return ( f"| {kind_icon}\033[0m | {state_icon} | {deprecation_icon} | {self.path}" ) def print_markdown(self) -> None: print(f"- [ ] {self.path} ({self.kind.name.lower()})") def has_deprecation_warnings(string: str) -> bool: for regex in DEPRECATION_REGEX: if re.match(regex, string): return True return False def parse_file(path: str) -> Optional[Plugin]: file_content: str = "" with open(path, "r") as f: file_content = f.read() known_path: str props: tuple[State, Kind, bool] for known_path, props in KNOWN_PATHS.items(): if known_path in path: return Plugin( path=path, state=props[0], kind=props[1], dep_warnings=props[2], ) state: State = State.UNKNOWN kind: Kind if re.match( re.compile(r".*mkNeovimPlugin", re.DOTALL), file_content, ): kind = Kind.NEOVIM state = State.NEW elif re.match( re.compile(r".*require.+setup", re.DOTALL), file_content, ): kind = Kind.NEOVIM state = State.OLD elif re.match( re.compile(r".*mkVimPlugin", re.DOTALL), file_content, ): kind = Kind.VIM state = State.NEW else: raise ValueError( f"I was not able to categorize `{path}`. Consider adding it to `EXCLUDES` or `KNOWN_PATHS`." ) return Plugin( path=path, state=state, kind=kind, dep_warnings=has_deprecation_warnings(string=file_content), ) def _is_excluded(path: str) -> bool: for exclude_pattern in EXCLUDES: if exclude_pattern in path: return False return True def main(args) -> None: pathname: str = os.path.join(args.root_path, "plugins/**/*.nix") paths: list[str] = glob.glob(pathname=pathname, recursive=True) filtered_paths: list[str] = list(filter(_is_excluded, paths)) filtered_paths.sort() if not args.markdown: print("| Typ | Sty | DW | path") print( "|-----|-----|----|--------------------------------------------------------" ) for plugin_path in filtered_paths: plugin: Optional[Plugin] = parse_file(path=plugin_path) if plugin is not None: if ( (args.kind is None or plugin.kind.name.lower() == args.kind) and (args.state is None or plugin.state.name.lower() == args.state) and (not args.deprecation_warnings or plugin.dep_warnings) ): if args.markdown: plugin.print_markdown() else: print(plugin) if __name__ == "__main__": parser: ArgumentParser = ArgumentParser( description=""" Analyze Nixvim plugin files Output formats a table showing: If a plugin is written for Neovim or Vim. If the plugin has been updated to latest style standards. If a plugin contains any deprecation warnings. """, formatter_class=RawTextHelpFormatter, ) # TODO: consider automatically localizing the flake's root. parser.add_argument( "--root-path", type=str, default="./", help="The path to the root of the nixvim repo", ) parser.add_argument( "-k", "--kind", choices=[k.name.lower() for k in Kind], help="Filter plugins by kind (neovim, vim, misc)", ) parser.add_argument( "-s", "--state", choices=[s.name.lower() for s in State], help="Filter plugins by state (new, old, unknown)", ) parser.add_argument( "-d", "--deprecation-warnings", action="store_true", help="Show only plugins with deprecation warnings", ) parser.add_argument( "-m", "--markdown", action="store_true", help="Markdown output", ) main(parser.parse_args())