#!/usr/bin/env python3 import glob import re from argparse import ArgumentParser, RawTextHelpFormatter from dataclasses import dataclass from enum import Enum from typing import Optional QUESTION_MARK = "❔" EXCLUDES: list[str] = [ # Not plugin files "colorschemes/base16/theme-list.nix", "helpers.nix", "Helpers.nix", "TEMPLATE.nix", ] class Kind(Enum): UNKNOWN = 1 NEOVIM = 2 VIM = 3 MISC = 4 class State(Enum): UNKNOWN = QUESTION_MARK NEW = "✅" OLD = "❌" KNOWN_PATHS: dict[str, tuple[State, Kind, bool]] = { # "plugins/utils/mkdnflow.nix": (True, Kind.NEOVIM), "plugins/colorschemes/base16/default.nix": (State.NEW, Kind.VIM, True), } 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.UNKNOWN: kind_icon = "\033[93m" + QUESTION_MARK case Kind.NEOVIM: kind_icon = "\033[94m" + " " case Kind.VIM: 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 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() if path in KNOWN_PATHS: props: tuple[State, Kind, bool] = KNOWN_PATHS[path] return Plugin( path=path, state=props[0], kind=props[1], dep_warnings=props[2], ) state: State = State.UNKNOWN kind: Kind = Kind.UNKNOWN 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 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): paths: list[str] = glob.glob(pathname="plugins/**/*.nix", recursive=True) filtered_paths: list[str] = list(filter(_is_excluded, paths)) filtered_paths.sort() 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) ): 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, ) parser.add_argument( "-k", "--kind", choices=[k.name.lower() for k in Kind], help="Filter plugins by kind (neovim, vim, misc, unknown)", ) 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", ) main(parser.parse_args())