Skip to content

Commit d913b44

Browse files
committed
plain-dev prompts to add p alias
1 parent b75a38d commit d913b44

File tree

2 files changed

+154
-0
lines changed

2 files changed

+154
-0
lines changed

plain-dev/plain/dev/alias.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import os
2+
import subprocess
3+
import sys
4+
from functools import cached_property
5+
from pathlib import Path
6+
7+
import click
8+
9+
10+
class AliasManager:
11+
"""Manages the 'p' alias for 'uv run plain'."""
12+
13+
MARKER_FILE = Path.home() / ".plain" / "dev" / ".alias_prompted"
14+
ALIAS_COMMAND = "uv run plain"
15+
ALIAS_NAME = "p"
16+
17+
@cached_property
18+
def shell(self):
19+
"""Detect the current shell."""
20+
shell = os.environ.get("SHELL", "")
21+
if "zsh" in shell:
22+
return "zsh"
23+
elif "bash" in shell:
24+
return "bash"
25+
elif "fish" in shell:
26+
return "fish"
27+
return None
28+
29+
@cached_property
30+
def shell_config_file(self):
31+
"""Get the appropriate shell configuration file."""
32+
home = Path.home()
33+
34+
if self.shell == "zsh":
35+
return home / ".zshrc"
36+
elif self.shell == "bash":
37+
# Check for .bash_aliases first (Ubuntu/Debian convention)
38+
if (home / ".bash_aliases").exists():
39+
return home / ".bash_aliases"
40+
return home / ".bashrc"
41+
elif self.shell == "fish":
42+
return home / ".config" / "fish" / "config.fish"
43+
44+
return None
45+
46+
def _command_exists(self, command):
47+
"""Check if a command exists in the system."""
48+
try:
49+
result = subprocess.run(
50+
["which", command], capture_output=True, text=True, check=False
51+
)
52+
return result.returncode == 0
53+
except Exception:
54+
return False
55+
56+
def _alias_exists(self):
57+
"""Check if the 'p' alias already exists."""
58+
# First check if 'p' is already a command
59+
if self._command_exists(self.ALIAS_NAME):
60+
return True
61+
62+
# Check if alias is defined in shell
63+
try:
64+
# Try to run the alias to see if it exists
65+
result = subprocess.run(
66+
[self.shell, "-i", "-c", f"alias {self.ALIAS_NAME}"],
67+
capture_output=True,
68+
text=True,
69+
check=False,
70+
timeout=2,
71+
)
72+
return result.returncode == 0
73+
except (subprocess.TimeoutExpired, Exception):
74+
return False
75+
76+
def _add_alias_to_shell(self):
77+
"""Add the alias to the shell configuration file."""
78+
if not self.shell_config_file or not self.shell_config_file.exists():
79+
return False
80+
81+
alias_line = f'alias {self.ALIAS_NAME}="{self.ALIAS_COMMAND}"'
82+
comment = "# Added by Plain"
83+
84+
# Check if alias already in file
85+
try:
86+
with open(self.shell_config_file) as f:
87+
content = f.read()
88+
if alias_line in content:
89+
return True
90+
except Exception:
91+
return False
92+
93+
# Add alias to file
94+
try:
95+
with open(self.shell_config_file, "a") as f:
96+
f.write(f"\n{comment}\n{alias_line}\n")
97+
98+
click.secho(
99+
f"✓ Added '{self.ALIAS_NAME}' alias to {self.shell_config_file.name}. Restart your shell!",
100+
fg="green",
101+
)
102+
return True
103+
except Exception as e:
104+
click.secho(
105+
f"Failed to add alias to {self.shell_config_file.name}: {e}", fg="red"
106+
)
107+
return False
108+
109+
def check_and_prompt(self):
110+
"""Check if alias exists and prompt user to set it up if needed."""
111+
# Only suggest if project uses uv (has uv.lock file)
112+
if not Path("uv.lock").exists():
113+
return
114+
115+
# Don't prompt if already configured
116+
if self._alias_exists():
117+
return
118+
119+
# Don't prompt if we've asked before
120+
if self.MARKER_FILE.exists():
121+
return
122+
123+
# Don't prompt for certain commands
124+
if "--help" in sys.argv or "-h" in sys.argv:
125+
return
126+
127+
# Mark that we've asked (do this first so we don't ask again even if they Ctrl+C)
128+
self.MARKER_FILE.parent.mkdir(parents=True, exist_ok=True)
129+
self.MARKER_FILE.touch()
130+
131+
click.echo()
132+
click.secho("💡 Tip: ", fg="yellow", bold=True, nl=False)
133+
click.echo(
134+
f"Set up `{self.ALIAS_NAME}` as an alias to run commands faster (e.g., `{self.ALIAS_NAME} dev` instead of `uv run plain dev`)."
135+
)
136+
click.echo()
137+
138+
# Check if shell is supported
139+
if not self.shell or not self.shell_config_file:
140+
click.echo("To set this up manually, add to your shell config:")
141+
click.echo(f' alias {self.ALIAS_NAME}="{self.ALIAS_COMMAND}"')
142+
click.echo()
143+
return
144+
145+
# Offer to set it up
146+
prompt_text = f"Would you like to add this to {self.shell_config_file.name}?"
147+
if click.confirm(prompt_text, default=False):
148+
click.echo()
149+
if self._add_alias_to_shell():
150+
sys.exit(0) # Completely exit
151+
152+
click.echo()

plain-dev/plain/dev/cli.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from plain.cli import register_cli
1010
from plain.runtime import APP_PATH, PLAIN_TEMP_PATH
1111

12+
from .alias import AliasManager
1213
from .core import ENTRYPOINT_GROUP, DevProcess
1314
from .services import ServicesProcess
1415

@@ -18,6 +19,7 @@ class DevGroup(click.Group):
1819

1920
def __init__(self, *args, **kwargs):
2021
super().__init__(*args, **kwargs)
22+
AliasManager().check_and_prompt()
2123
self._auto_start_services()
2224

2325
@staticmethod

0 commit comments

Comments
 (0)