"""Check for alias collisions within the codebase""" from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser from pathlib import Path from dataclasses import dataclass, asdict from typing import List, Dict import itertools import re import json ERROR_MESSAGE_TEMPLATE = ( "Alias `%s` defined in `%s` already exists as alias `%s` in `%s`." ) # TODO: We want that list to be empty and get rid of this file KNOWN_COLLISIONS = Path(__file__).resolve().parent / "known_collisions.json" def dir_path(path_string: str) -> Path: if Path(path_string).is_dir(): return Path(path_string) else: raise NotADirectoryError(path_string) def parse_arguments(): parser = ArgumentParser( description=__doc__, formatter_class=ArgumentDefaultsHelpFormatter, ) parser.add_argument( "folder", type=dir_path, help="Folder to check", ) parser.add_argument( "--known-collisions", type=Path, default=KNOWN_COLLISIONS, help="Json-serialized list of known collisions", ) return parser.parse_args() @dataclass(frozen=True) class Alias: alias: str value: str module: Path @dataclass(frozen=True) class Collision: existing_alias: Alias new_alias: Alias def is_new_collision(self, known_collision_aliases: List[str]) -> bool: return self.new_alias.alias not in known_collision_aliases @classmethod def from_dict(cls, collision_dict: Dict) -> "Collision": return cls( Alias(**collision_dict["existing_alias"]), Alias(**collision_dict["new_alias"]), ) def find_aliases_in_file(file: Path) -> List[Alias]: matches = re.findall(r"^alias (.*)='(.*)'", file.read_text(), re.M) return [Alias(match[0], match[1], file) for match in matches] def load_known_collisions(collision_file: Path) -> List[Collision]: collision_list = json.loads(collision_file.read_text()) return [Collision.from_dict(collision_dict) for collision_dict in collision_list] def find_all_aliases(path: Path) -> list: aliases = [find_aliases_in_file(file) for file in path.rglob("*.zsh")] return list(itertools.chain(*aliases)) def check_for_duplicates(aliases: List[Alias]) -> List[Collision]: elements = {} collisions = [] for alias in aliases: if alias.alias in elements: existing = elements[alias.alias] collisions.append(Collision(existing, alias)) else: elements[alias.alias] = alias return collisions def print_collisions(collisions: Dict[Alias, Alias]) -> None: if collisions: print(f"Found {len(collisions)} alias collisions:\n") for collision in collisions: print( ERROR_MESSAGE_TEMPLATE % ( f"{collision.new_alias.alias}={collision.new_alias.value}", collision.new_alias.module.name, f"{collision.existing_alias.alias}={collision.existing_alias.value}", collision.existing_alias.module.name, ) ) print("\nConsider renaming your aliases.") else: print("Found no collisions") def main() -> int: """main""" args = parse_arguments() aliases = find_all_aliases(args.folder) collisions = check_for_duplicates(aliases) known_collisions = load_known_collisions(args.known_collisions) known_collision_aliases = [ collision.new_alias.alias for collision in known_collisions ] new_collisions = [ collision for collision in collisions if collision.is_new_collision(known_collision_aliases) ] print_collisions(new_collisions) return -1 if new_collisions else 0 if __name__ == "__main__": exit(main())