2022-04-15 13:34:17 +02:00
|
|
|
"""Check for alias collisions within the codebase"""
|
|
|
|
|
2022-11-22 16:24:15 +01:00
|
|
|
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
|
2022-04-15 13:34:17 +02:00
|
|
|
from pathlib import Path
|
2024-02-19 15:19:49 +01:00
|
|
|
from dataclasses import dataclass
|
2024-02-02 13:48:57 +01:00
|
|
|
from typing import List, Dict
|
2022-04-15 13:34:17 +02:00
|
|
|
import itertools
|
|
|
|
import re
|
2024-02-02 19:54:53 +01:00
|
|
|
import json
|
2022-04-15 13:34:17 +02:00
|
|
|
|
|
|
|
|
2022-11-22 17:57:15 +01:00
|
|
|
ERROR_MESSAGE_TEMPLATE = (
|
|
|
|
"Alias `%s` defined in `%s` already exists as alias `%s` in `%s`."
|
|
|
|
)
|
2022-04-15 13:34:17 +02:00
|
|
|
|
2024-02-19 15:19:49 +01:00
|
|
|
KNOWN_COLLISIONS_PATH = Path(__file__).resolve().parent / "known_collisions.json"
|
2024-02-02 14:13:12 +01:00
|
|
|
|
2022-04-15 13:34:17 +02:00
|
|
|
|
|
|
|
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",
|
|
|
|
)
|
2024-02-19 15:19:49 +01:00
|
|
|
group = parser.add_mutually_exclusive_group(required=True)
|
|
|
|
group.add_argument(
|
2024-02-02 19:54:53 +01:00
|
|
|
"--known-collisions",
|
|
|
|
type=Path,
|
2024-02-19 15:19:49 +01:00
|
|
|
default=None,
|
|
|
|
help="Json-serialized list of known collision to compare to",
|
|
|
|
)
|
|
|
|
group.add_argument(
|
|
|
|
"--known-collisions-output-path",
|
|
|
|
type=Path,
|
|
|
|
default=KNOWN_COLLISIONS_PATH,
|
|
|
|
help="Output path for a json-serialized list of known collisions",
|
2024-02-02 19:54:53 +01:00
|
|
|
)
|
2022-04-15 13:34:17 +02:00
|
|
|
return parser.parse_args()
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
class Alias:
|
|
|
|
alias: str
|
|
|
|
value: str
|
|
|
|
module: Path
|
|
|
|
|
2024-02-19 15:19:49 +01:00
|
|
|
def to_dict(self) -> Dict:
|
|
|
|
return {
|
|
|
|
"alias": self.alias,
|
|
|
|
"value": self.value,
|
|
|
|
"module": str(self.module),
|
|
|
|
}
|
|
|
|
|
2022-04-15 13:34:17 +02:00
|
|
|
|
2022-11-22 17:57:15 +01:00
|
|
|
@dataclass(frozen=True)
|
|
|
|
class Collision:
|
|
|
|
existing_alias: Alias
|
|
|
|
new_alias: Alias
|
|
|
|
|
2024-02-02 19:54:53 +01:00
|
|
|
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"]),
|
|
|
|
)
|
2024-02-02 14:13:12 +01:00
|
|
|
|
2024-02-19 15:19:49 +01:00
|
|
|
def to_dict(self) -> Dict:
|
|
|
|
return {
|
|
|
|
"existing_alias": self.existing_alias.to_dict(),
|
|
|
|
"new_alias": self.new_alias.to_dict(),
|
|
|
|
}
|
|
|
|
|
2022-11-22 17:57:15 +01:00
|
|
|
|
2024-02-02 13:48:57 +01:00
|
|
|
def find_aliases_in_file(file: Path) -> List[Alias]:
|
2022-04-15 13:34:17 +02:00
|
|
|
matches = re.findall(r"^alias (.*)='(.*)'", file.read_text(), re.M)
|
|
|
|
return [Alias(match[0], match[1], file) for match in matches]
|
|
|
|
|
|
|
|
|
2024-02-02 19:54:53 +01:00
|
|
|
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]
|
|
|
|
|
|
|
|
|
2022-11-22 17:57:15 +01:00
|
|
|
def find_all_aliases(path: Path) -> list:
|
2024-02-02 14:13:12 +01:00
|
|
|
aliases = [find_aliases_in_file(file) for file in path.rglob("*.zsh")]
|
2022-04-15 13:34:17 +02:00
|
|
|
return list(itertools.chain(*aliases))
|
|
|
|
|
|
|
|
|
2024-02-02 13:48:57 +01:00
|
|
|
def check_for_duplicates(aliases: List[Alias]) -> List[Collision]:
|
2022-11-22 16:24:15 +01:00
|
|
|
elements = {}
|
2022-11-22 17:57:15 +01:00
|
|
|
collisions = []
|
2022-04-15 13:34:17 +02:00
|
|
|
for alias in aliases:
|
|
|
|
if alias.alias in elements:
|
|
|
|
existing = elements[alias.alias]
|
2022-11-22 17:57:15 +01:00
|
|
|
collisions.append(Collision(existing, alias))
|
|
|
|
else:
|
|
|
|
elements[alias.alias] = alias
|
|
|
|
return collisions
|
|
|
|
|
|
|
|
|
2024-02-02 13:48:57 +01:00
|
|
|
def print_collisions(collisions: Dict[Alias, Alias]) -> None:
|
2022-11-22 17:57:15 +01:00
|
|
|
if collisions:
|
|
|
|
print(f"Found {len(collisions)} alias collisions:\n")
|
|
|
|
for collision in collisions:
|
|
|
|
print(
|
2022-04-15 13:34:17 +02:00
|
|
|
ERROR_MESSAGE_TEMPLATE
|
|
|
|
% (
|
2022-11-22 17:57:15 +01:00
|
|
|
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,
|
2022-04-15 13:34:17 +02:00
|
|
|
)
|
|
|
|
)
|
2022-11-22 17:57:15 +01:00
|
|
|
print("\nConsider renaming your aliases.")
|
|
|
|
else:
|
|
|
|
print("Found no collisions")
|
2022-04-15 13:34:17 +02:00
|
|
|
|
|
|
|
|
2024-02-19 15:19:49 +01:00
|
|
|
def check_for_new_collisions(
|
|
|
|
known_collisions: Path, collisions: List[Collision]
|
|
|
|
) -> List[Collision]:
|
|
|
|
known_collisions = load_known_collisions(known_collisions)
|
2024-02-02 19:54:53 +01:00
|
|
|
known_collision_aliases = [
|
|
|
|
collision.new_alias.alias for collision in known_collisions
|
|
|
|
]
|
|
|
|
|
2024-02-19 15:19:49 +01:00
|
|
|
return [
|
2024-02-02 14:13:12 +01:00
|
|
|
collision
|
|
|
|
for collision in collisions
|
2024-02-02 19:54:53 +01:00
|
|
|
if collision.is_new_collision(known_collision_aliases)
|
2024-02-02 14:13:12 +01:00
|
|
|
]
|
2024-02-02 19:54:53 +01:00
|
|
|
|
2024-02-19 15:19:49 +01:00
|
|
|
|
|
|
|
def main() -> int:
|
|
|
|
"""main"""
|
|
|
|
args = parse_arguments()
|
|
|
|
aliases = find_all_aliases(args.folder)
|
|
|
|
collisions = check_for_duplicates(aliases)
|
|
|
|
|
|
|
|
if args.known_collisions is not None:
|
|
|
|
new_collisions = check_for_new_collisions(args.known_collisions, collisions)
|
|
|
|
print_collisions(new_collisions)
|
|
|
|
return -1 if new_collisions else 0
|
|
|
|
|
|
|
|
args.known_collisions_output_path.write_text(
|
|
|
|
json.dumps([collision.to_dict() for collision in collisions])
|
|
|
|
)
|
|
|
|
return 0
|
2022-04-15 13:34:17 +02:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2024-02-02 19:54:53 +01:00
|
|
|
exit(main())
|