44
55Provides a Click Group subclass that resolves short aliases to
66full command names, plus a registry of built-in aliases.
7+ User-defined aliases can be loaded from ``~/.bernstein/aliases.yaml``.
78"""
89
910from __future__ import annotations
1011
12+ import logging
13+ from pathlib import Path
14+
1115import click
16+ import yaml
17+
18+ logger = logging .getLogger (__name__ )
1219
1320# ---------------------------------------------------------------------------
1421# Alias registry
2532 "i" : "overture" , # init (hidden name: overture)
2633}
2734
35+ # Track which aliases are user-defined (populated at load time)
36+ _USER_ALIASES : dict [str , str ] = {}
37+
38+ _USER_ALIASES_PATH = Path .home () / ".bernstein" / "aliases.yaml"
39+
2840
2941def get_alias (name : str ) -> str | None :
3042 """Return the full command name for an alias, or None.
@@ -43,6 +55,32 @@ def get_all_aliases() -> dict[str, str]:
4355 return dict (ALIASES )
4456
4557
58+ def _load_user_aliases () -> dict [str , str ]:
59+ """Load user-defined aliases from ~/.bernstein/aliases.yaml."""
60+ if not _USER_ALIASES_PATH .is_file ():
61+ return {}
62+ try :
63+ with open (_USER_ALIASES_PATH ) as f :
64+ data = yaml .safe_load (f ) or {}
65+ if not isinstance (data , dict ):
66+ return {}
67+ return {str (k ): str (v ) for k , v in data .items () if isinstance (k , str ) and isinstance (v , str )}
68+ except Exception :
69+ logger .debug ("Failed to load user aliases from %s" , _USER_ALIASES_PATH , exc_info = True )
70+ return {}
71+
72+
73+ def _merge_aliases () -> None :
74+ """Merge user aliases into the global registry (user overrides built-in)."""
75+ global _USER_ALIASES
76+ _USER_ALIASES = _load_user_aliases ()
77+ ALIASES .update (_USER_ALIASES )
78+
79+
80+ # Call at module load time
81+ _merge_aliases ()
82+
83+
4684class AliasGroup (click .Group ):
4785 """Click Group that resolves short aliases to full command names.
4886
@@ -92,6 +130,7 @@ def aliases_cmd() -> None:
92130 table = Table (title = "Command Aliases" , show_header = True , header_style = "bold cyan" )
93131 table .add_column ("Alias" , style = "green" , width = 10 )
94132 table .add_column ("Command" , style = "white" , width = 20 )
133+ table .add_column ("Source" , style = "dim" , width = 10 )
95134 table .add_column ("Description" , style = "dim" )
96135
97136 _descriptions : dict [str , str ] = {
@@ -107,7 +146,8 @@ def aliases_cmd() -> None:
107146
108147 for alias , command in sorted (ALIASES .items ()):
109148 desc = _descriptions .get (alias , "" )
110- table .add_row (alias , command , desc )
149+ source = "[cyan]user[/cyan]" if alias in _USER_ALIASES else "[dim]built-in[/dim]"
150+ table .add_row (alias , command , source , desc )
111151
112152 console .print (table )
113153 console .print ("\n [dim]Usage: bernstein <alias> [options][/dim]" )
0 commit comments