|
#!/usr/bin/env -S uv run --script |
|
# /// script |
|
# dependencies = [ |
|
# "typer>=0.12.0", |
|
# "rich>=13.0.0", |
|
# "pydantic>=2.0.0", |
|
# "loguru>=0.7.0", |
|
# "jinja2>=3.1.0", |
|
# ] |
|
# /// |
|
""" |
|
IDE Settings Management - Multi-developer IDE settings management |
|
|
|
A tool for managing IDE settings across teams with per-developer profiles, |
|
git hooks automation via lefthook, and Taskfile integration. |
|
|
|
Usage: |
|
./ide-settings-mgmt.py [command] [options] |
|
""" |
|
|
|
import os |
|
import shutil |
|
import subprocess |
|
import json |
|
from pathlib import Path |
|
from typing import Optional, List |
|
import typer |
|
from rich.console import Console |
|
from rich.table import Table |
|
from rich.panel import Panel |
|
from rich import print as rprint |
|
from loguru import logger |
|
from jinja2 import Environment, FileSystemLoader |
|
|
|
console = Console() |
|
app = typer.Typer( |
|
help="IDE Settings Management - Multi-developer IDE settings management", |
|
add_completion=False |
|
) |
|
|
|
# Constants |
|
IDE_SETTINGS_MGMT_DIR = Path.home() / ".ide-settings-mgmt" |
|
REPO_IDE_DIR = Path(".ide-settings-mgmt") |
|
CHEZMOI_SOURCE_DIR = IDE_SETTINGS_MGMT_DIR / "chezmoi" |
|
SUPPORTED_IDES = { |
|
"vscode": { |
|
"config_dir": ".vscode", |
|
"settings_file": "settings.json", |
|
"extensions_file": "extensions.json", |
|
"gitignore_pattern": ".vscode/" |
|
}, |
|
"jetbrains": { |
|
"config_dir": ".idea", |
|
"settings_file": "misc.xml", |
|
"keymap_file": "keymap.xml", |
|
"gitignore_pattern": ".idea/" |
|
}, |
|
"neovim": { |
|
"config_dir": ".nvim", |
|
"init_file": "init.lua", |
|
"gitignore_pattern": ".nvim/" |
|
} |
|
} |
|
|
|
# Tool-specific ignore files that might need IDE-specific patterns |
|
TOOL_IGNORE_FILES = [ |
|
".prettierignore", |
|
".biomeignore", |
|
".dockerignore", |
|
".eslintignore", |
|
".stylelintignore", |
|
".gitattributes" |
|
] |
|
|
|
# Gist configuration |
|
IDE_SETTINGS_MGMT_GIST_ID = "5ff956a2cce1d59c1175d818837ecaa3" |
|
IDE_SETTINGS_MGMT_GITHUB_USER = "tobiashochguertel" |
|
|
|
# Jinja2 templates directory (templates are in root directory for Gist compatibility) |
|
# Resolve symlinks to get the actual clone directory |
|
SCRIPT_PATH = Path(__file__).resolve() |
|
TEMPLATES_DIR = SCRIPT_PATH.parent |
|
|
|
# Jinja2 environment for template rendering |
|
env = Environment(loader=FileSystemLoader(TEMPLATES_DIR)) |
|
|
|
|
|
def render_template(template_name: str, **context) -> str: |
|
"""Render a Jinja2 template with the given context. |
|
|
|
Args: |
|
template_name: Name of the template file |
|
**context: Variables to pass to the template |
|
|
|
Returns: |
|
Rendered template as string |
|
""" |
|
try: |
|
template = env.get_template(template_name) |
|
return template.render(**context) |
|
except Exception as e: |
|
logger.error(f"Failed to render template {template_name}: {e}") |
|
raise IDESetupError(f"Template rendering failed: {e}") from e |
|
|
|
|
|
class IDESetupError(Exception): |
|
"""Base exception for IDE setup operations.""" |
|
pass |
|
|
|
|
|
def run_command(cmd: List[str], check: bool = True) -> subprocess.CompletedProcess: |
|
"""Run a shell command with rich error handling.""" |
|
try: |
|
result = subprocess.run( |
|
cmd, |
|
capture_output=True, |
|
text=True, |
|
check=check |
|
) |
|
return result |
|
except subprocess.CalledProcessError as e: |
|
console.print(f"[red]Command failed: {' '.join(cmd)}[/red]") |
|
console.print(f"[red]Error: {e.stderr}[/red]") |
|
raise IDESetupError(f"Command failed: {e}") from e |
|
|
|
|
|
def adjust_ignore_files(repo_path: Path, ide: str, operation: str = "init") -> None: |
|
"""Adjust .gitignore and other tool-specific ignore files for IDE settings. |
|
|
|
Args: |
|
repo_path: Path to the repository |
|
ide: IDE type (vscode, jetbrains, neovim) |
|
operation: Either 'init' (new setup) or 'migrate' (existing settings) |
|
""" |
|
ide_config = SUPPORTED_IDES[ide] |
|
ide_pattern = ide_config["gitignore_pattern"] |
|
|
|
logger.debug(f"Adjusting ignore files for {operation} operation...") |
|
|
|
# Handle .gitignore |
|
gitignore_path = repo_path / ".gitignore" |
|
if gitignore_path.exists(): |
|
gitignore_content = gitignore_path.read_text() |
|
gitignore_lines = gitignore_content.split('\n') |
|
|
|
if operation == "migrate": |
|
# For migration, remove IDE directory from .gitignore if present |
|
# since we want to track the settings now |
|
if ide_pattern in gitignore_lines: |
|
console.print(f"[yellow]Found {ide_pattern} in .gitignore[/yellow]") |
|
console.print(f"[dim]Removing {ide_pattern} from .gitignore to track IDE settings[/dim]") |
|
|
|
# Remove the pattern |
|
gitignore_lines = [line for line in gitignore_lines if line.strip() != ide_pattern] |
|
gitignore_path.write_text('\n'.join(gitignore_lines)) |
|
console.print(f"[green]β Removed {ide_pattern} from .gitignore[/green]") |
|
else: |
|
console.print(f"[dim]β {ide_pattern} not in .gitignore (good for tracking)[/dim]") |
|
|
|
# Add .ide-settings-mgmt/ to .gitignore (we track structure, not user-specific content) |
|
ide_settings_pattern = ".ide-settings-mgmt/" |
|
if ide_settings_pattern not in gitignore_lines: |
|
console.print(f"[dim]Adding {ide_settings_pattern} to .gitignore[/dim]") |
|
with open(gitignore_path, 'a') as f: |
|
f.write(f"\n# IDE settings management\n{ide_settings_pattern}\n") |
|
console.print(f"[green]β Added {ide_settings_pattern} to .gitignore[/green]") |
|
|
|
# Handle other tool-specific ignore files |
|
for ignore_file in TOOL_IGNORE_FILES: |
|
ignore_path = repo_path / ignore_file |
|
if not ignore_path.exists(): |
|
continue |
|
|
|
console.print(f"[dim]Checking {ignore_file}...[/dim]") |
|
ignore_content = ignore_path.read_text() |
|
|
|
# Add IDE-specific comments if not present |
|
ide_comment = f"# IDE-specific patterns for {ide}" |
|
if ide_comment not in ignore_content: |
|
with open(ignore_path, 'a') as f: |
|
f.write(f"\n{ide_comment}\n") |
|
console.print(f"[green]β Added IDE comment to {ignore_file}[/green]") |
|
|
|
|
|
def detect_ide() -> Optional[str]: |
|
"""Detect which IDE is likely being used based on project files.""" |
|
cwd = Path.cwd() |
|
|
|
# Check for VS Code |
|
if (cwd / ".vscode").exists(): |
|
return "vscode" |
|
|
|
# Check for JetBrains |
|
if (cwd / ".idea").exists(): |
|
return "jetbrains" |
|
|
|
# Check for NeoVim |
|
if (cwd / ".nvim").exists() or (cwd / "init.lua").exists(): |
|
return "neovim" |
|
|
|
return None |
|
|
|
|
|
def check_chezmoi_installed() -> bool: |
|
"""Check if chezmoi is installed.""" |
|
try: |
|
run_command(["chezmoi", "--version"], check=False) |
|
return True |
|
except (IDESetupError, FileNotFoundError): |
|
return False |
|
|
|
|
|
def init_chezmoi() -> None: |
|
"""Initialize chezmoi for managing IDE profiles.""" |
|
logger.info("Initializing chezmoi for IDE profiles...") |
|
|
|
if not check_chezmoi_installed(): |
|
logger.warning("chezmoi is not installed. Installing...") |
|
console.print("[dim]Please install chezmoi first:[/dim]") |
|
console.print("[dim] brew install chezmoi # macOS[/dim]") |
|
console.print("[dim] curl -sfL https://git.io/chezmoi | sh # Linux[/dim]") |
|
raise IDESetupError("chezmoi is required but not installed") |
|
|
|
# Create chezmoi source directory if it doesn't exist |
|
CHEZMOI_SOURCE_DIR.mkdir(parents=True, exist_ok=True) |
|
|
|
# Initialize chezmoi if not already initialized |
|
chezmoi_config = Path.home() / ".local" / "share" / "chezmoi" |
|
if not chezmoi_config.exists(): |
|
logger.debug("Initializing chezmoi...") |
|
run_command(["chezmoi", "init", "--source", str(CHEZMOI_SOURCE_DIR)]) |
|
|
|
logger.success("chezmoi initialized for IDE profiles") |
|
|
|
|
|
def create_profile_template(ide_name: str, profile_name: str) -> Path: |
|
"""Create a template profile for an IDE.""" |
|
ide_config = SUPPORTED_IDES.get(ide_name) |
|
if not ide_config: |
|
raise IDESetupError(f"Unsupported IDE: {ide_name}") |
|
|
|
profile_dir = CHEZMOI_SOURCE_DIR / profile_name / ide_config["config_dir"] |
|
profile_dir.mkdir(parents=True, exist_ok=True) |
|
|
|
# Create template settings file |
|
settings_file = profile_dir / ide_config.get("settings_file", "settings.json") |
|
if not settings_file.exists(): |
|
if ide_name == "vscode": |
|
settings_file.write_text(json.dumps({ |
|
"editor.formatOnSave": True, |
|
"editor.tabSize": 2, |
|
"files.exclude": { |
|
"**/__pycache__": True, |
|
"**/.git": True |
|
} |
|
}, indent=2)) |
|
elif ide_name == "neovim": |
|
settings_file.write_text(""" |
|
-- NeoVim configuration |
|
vim.opt.tabstop = 2 |
|
vim.opt.shiftwidth = 2 |
|
vim.opt.expandtab = true |
|
""") |
|
|
|
console.print(f"[green]β Created profile template: {profile_name}/{ide_name}[/green]") |
|
logger.info(f"Created profile template: {profile_name}/{ide_name}") |
|
return profile_dir |
|
|
|
|
|
@app.command() |
|
def init( |
|
repo_path: Path = typer.Argument( |
|
Path.cwd(), |
|
help="Path to the repository to initialize" |
|
), |
|
ide: Optional[str] = typer.Option( |
|
None, |
|
"--ide", |
|
help="IDE to configure (auto-detected if not specified)" |
|
), |
|
force: bool = typer.Option( |
|
False, |
|
"--force", |
|
help="Overwrite existing .ide-settings-mgmt directory" |
|
) |
|
) -> None: |
|
"""Initialize IDE settings structure in a repository.""" |
|
console.print(Panel.fit("[bold blue]IDE Settings Management - Initialize Repository[/bold blue]")) |
|
|
|
# Detect IDE if not specified |
|
if not ide: |
|
ide = detect_ide() |
|
if ide: |
|
console.print(f"[dim]Detected IDE: {ide}[/dim]") |
|
else: |
|
console.print("[yellow]No IDE detected. Specify --ide or supported IDEs: vscode, jetbrains, neovim[/yellow]") |
|
raise typer.Exit(code=1) |
|
|
|
if ide not in SUPPORTED_IDES: |
|
console.print(f"[red]Unsupported IDE: {ide}[/red]") |
|
console.print(f"[dim]Supported IDEs: {', '.join(SUPPORTED_IDES.keys())}[/dim]") |
|
raise typer.Exit(code=1) |
|
|
|
# Check if repo is a git repository |
|
if not (repo_path / ".git").exists(): |
|
console.print("[yellow]Not a git repository. Skipping git hooks setup.[/yellow]") |
|
|
|
# Create .ide-settings-mgmt directory |
|
ide_settings_dir = repo_path / REPO_IDE_DIR |
|
if ide_settings_dir.exists() and not force: |
|
console.print(f"[yellow].ide-settings-mgmt already exists. Use --force to overwrite.[/yellow]") |
|
raise typer.Exit(code=1) |
|
|
|
ide_settings_dir.mkdir(parents=True, exist_ok=True) |
|
|
|
# Create directory structure |
|
(ide_settings_dir / "profiles").mkdir(exist_ok=True) |
|
(ide_settings_dir / "hooks").mkdir(exist_ok=True) |
|
|
|
# Create lefthook configuration |
|
lefthook_config = ide_settings_dir / "lefthook.yml" |
|
lefthook_config.write_text(render_template("lefthook.yml.j2")) |
|
|
|
# Create repository-specific Taskfile |
|
repo_taskfile = ide_settings_dir / "Taskfile.yml" |
|
if not repo_taskfile.exists(): |
|
repo_taskfile.write_text("""# Taskfile for IDE settings in this repository |
|
# Repository-specific IDE management tasks |
|
# Following Taskfile Guide patterns with task-help integration |
|
|
|
version: "3" |
|
|
|
vars: |
|
IDE_SETTINGS_MGMT_GIST_ID: '{{.IDE_SETTINGS_MGMT_GIST_ID | default "5ff956a2cce1d59c1175d818837ecaa3"}}' |
|
TASK_HELP_WARN: "true" # set to 'false' to suppress the install hint |
|
|
|
tasks: |
|
default: |
|
silent: true |
|
desc: "Show grouped task list (falls back to task --list if task-help not installed)" |
|
cmds: |
|
- | |
|
SCRIPT="$HOME/.taskfiles/taskscripts/task-help/task_help.py" |
|
if [ -x "$SCRIPT" ]; then |
|
"$SCRIPT" |
|
else |
|
WARN="{{.TASK_HELP_WARN}}" |
|
if [ "$WARN" != "false" ] && [ "$WARN" != "0" ]; then |
|
printf '\\033[33mβ task-help is not installed.\\033[0m\\n' |
|
printf '\\033[2m curl -fsSL https://gist.githubusercontent.com/tobiashochguertel/261c54d64fff6dc1493619e2924161b4/raw/install.sh | bash\\033[0m\\n' |
|
fi |
|
task --list |
|
fi |
|
|
|
ensure-global: |
|
desc: "Ensure global ide-settings-mgmt gist is installed" |
|
silent: true |
|
cmds: |
|
- | |
|
if [ ! -f ~/.taskfiles/Taskfile.ide-settings-mgmt.yml ]; then |
|
echo "Installing global ide-settings-mgmt..." |
|
curl -fsSL https://gist.github.com/tobiashochguertel/{{.IDE_SETTINGS_MGMT_GIST_ID}}/raw/install.sh | bash |
|
else |
|
echo "β Global ide-settings-mgmt already installed" |
|
fi |
|
|
|
status: |
|
desc: "Show IDE settings status for this repository" |
|
cmds: |
|
- task: ensure-global |
|
- | |
|
echo "=== Repository IDE Settings Status ===" |
|
if [ -d .ide-settings-mgmt ]; then |
|
echo "β .ide-settings-mgmt directory exists" |
|
if [ -d .vscode ]; then |
|
echo "β VS Code settings present" |
|
fi |
|
if [ -d .idea ]; then |
|
echo "β JetBrains settings present" |
|
fi |
|
else |
|
echo "β .ide-settings-mgmt directory not found" |
|
fi |
|
|
|
apply: |
|
desc: "Apply your IDE profile to this repository" |
|
cmds: |
|
- task: ensure-global |
|
- ide-settings-mgmt apply |
|
|
|
sync: |
|
desc: "Sync current IDE settings with your profile" |
|
cmds: |
|
- task: ensure-global |
|
- ide-settings-mgmt sync |
|
|
|
check: |
|
desc: "Check IDE settings consistency" |
|
cmds: |
|
- task: ensure-global |
|
- | |
|
if [ -f .vscode/settings.json ]; then |
|
echo "Checking VS Code settings..." |
|
# Add validation logic here |
|
fi |
|
|
|
validate: |
|
desc: "Validate IDE settings format" |
|
cmds: |
|
- task: ensure-global |
|
- | |
|
echo "Validating IDE settings..." |
|
# Add validation logic here |
|
""") |
|
console.print("[green]β Created repository Taskfile[/green]") |
|
|
|
# Include in root Taskfile.yml if it exists |
|
root_taskfile = repo_path / "Taskfile.yml" |
|
if root_taskfile.exists(): |
|
console.print("[dim]Checking root Taskfile.yml for include...[/dim]") |
|
root_taskfile_content = root_taskfile.read_text() |
|
|
|
# Check if already included |
|
if "ide-settings-mgmt" in root_taskfile_content: |
|
console.print("[dim]β Already included in root Taskfile.yml[/dim]") |
|
else: |
|
console.print("[dim]Adding include to root Taskfile.yml...[/dim]") |
|
# Add include at the end of includes section |
|
with open(root_taskfile, 'a') as f: |
|
f.write("\n # ββ ide-settings-mgmt β Repository IDE settings management βββββββββββββββββ\n") |
|
f.write(" ide-settings-mgmt:\n") |
|
f.write(" taskfile: .ide-settings-mgmt/Taskfile.yml\n") |
|
f.write(" optional: true\n") |
|
console.print("[green]β Added include to root Taskfile.yml[/green]") |
|
|
|
# Create README for .ide-settings-mgmt |
|
readme_file = ide_settings_dir / "README.md" |
|
readme_file.write_text(render_template( |
|
"readme.md.j2", |
|
gist_id=IDE_SETTINGS_MGMT_GIST_ID, |
|
github_user=IDE_SETTINGS_MGMT_GITHUB_USER, |
|
profile_name="default", |
|
ide_name=ide |
|
)) |
|
|
|
# Adjust .gitignore and other ignore files |
|
adjust_ignore_files(repo_path, ide, operation="init") |
|
|
|
console.print(f"[green]β Initialized IDE settings in {repo_path}[/green]") |
|
console.print(f"[dim]Created .ide-settings-mgmt/ with lefthook and Taskfile integration[/dim]") |
|
console.print("[dim]Next steps:[/dim]") |
|
console.print("[dim] 1. Create your profile: ide-settings-mgmt profile create --ide vscode my-default[/dim]") |
|
console.print("[dim] 2. Apply profile to repo: ide-settings-mgmt apply[/dim]") |
|
|
|
|
|
@app.command() |
|
def migrate( |
|
repo_path: Path = typer.Argument( |
|
Path.cwd(), |
|
help="Path to the repository to migrate" |
|
), |
|
profile_name: Optional[str] = typer.Option( |
|
None, |
|
"--profile", |
|
help="Profile name to create (default: repo-name)" |
|
), |
|
ide: Optional[str] = typer.Option( |
|
None, |
|
"--ide", |
|
help="IDE type (auto-detected if not specified)" |
|
), |
|
force: bool = typer.Option( |
|
False, |
|
"--force", |
|
help="Overwrite existing .ide-settings-mgmt directory" |
|
) |
|
) -> None: |
|
"""Migrate existing IDE settings to multi-developer workflow.""" |
|
console.print(Panel.fit("[bold blue]IDE Settings Management - Migrate Existing Settings[/bold blue]")) |
|
|
|
# Detect IDE if not specified |
|
if not ide: |
|
ide = detect_ide() |
|
if ide: |
|
console.print(f"[dim]Detected IDE: {ide}[/dim]") |
|
else: |
|
console.print("[yellow]No IDE detected. Specify --ide or supported IDEs: vscode, jetbrains, neovim[/yellow]") |
|
raise typer.Exit(code=1) |
|
|
|
if ide not in SUPPORTED_IDES: |
|
console.print(f"[red]Unsupported IDE: {ide}[/red]") |
|
console.print(f"[dim]Supported IDEs: {', '.join(SUPPORTED_IDES.keys())}[/dim]") |
|
raise typer.Exit(code=1) |
|
|
|
# Check if repo is a git repository |
|
if not (repo_path / ".git").exists(): |
|
console.print("[yellow]Not a git repository. Migration requires git.[/yellow]") |
|
raise typer.Exit(code=1) |
|
|
|
# Check for existing IDE settings |
|
ide_config = SUPPORTED_IDES[ide] |
|
existing_settings = repo_path / ide_config["config_dir"] |
|
|
|
if not existing_settings.exists(): |
|
console.print(f"[yellow]No existing {ide} settings found in {repo_path}[/yellow]") |
|
console.print(f"[dim]Expected: {ide_config['config_dir']}[/dim]") |
|
console.print("[dim]Use 'ide-settings-mgmt init' instead for new setup[/dim]") |
|
raise typer.Exit(code=1) |
|
|
|
console.print(f"[green]β Found existing {ide} settings[/green]") |
|
logger.info(f"Found existing {ide} settings in {repo_path}") |
|
|
|
# Generate profile name if not provided |
|
if not profile_name: |
|
profile_name = repo_path.name.replace("-", "_").replace(" ", "_") |
|
console.print(f"[dim]Using profile name: {profile_name}[/dim]") |
|
|
|
# Initialize chezmoi if needed |
|
init_chezmoi() |
|
|
|
# Create profile from existing settings |
|
profile_dir = CHEZMOI_SOURCE_DIR / profile_name / ide_config["config_dir"] |
|
profile_dir.mkdir(parents=True, exist_ok=True) |
|
|
|
console.print(f"[dim]Migrating settings to profile: {profile_name}[/dim]") |
|
|
|
# Copy existing settings to profile |
|
for item in existing_settings.iterdir(): |
|
if item.is_file(): |
|
shutil.copy2(item, profile_dir / item.name) |
|
console.print(f"[green]β Migrated {item.name}[/green]") |
|
|
|
# Initialize .ide-settings-mgmt structure |
|
ide_settings_dir = repo_path / REPO_IDE_DIR |
|
if ide_settings_dir.exists() and not force: |
|
console.print(f"[yellow].ide-settings-mgmt already exists. Use --force to overwrite.[/yellow]") |
|
if not typer.confirm("Continue with existing .ide-settings-mgmt?"): |
|
raise typer.Exit(code=1) |
|
else: |
|
ide_settings_dir.mkdir(parents=True, exist_ok=True) |
|
|
|
# Create directory structure |
|
(ide_settings_dir / "profiles").mkdir(exist_ok=True) |
|
(ide_settings_dir / "hooks").mkdir(exist_ok=True) |
|
|
|
# Create lefthook configuration |
|
lefthook_config = ide_settings_dir / "lefthook.yml" |
|
if not lefthook_config.exists(): |
|
lefthook_config.write_text(render_template("lefthook.yml.j2")) |
|
console.print("[green]β Created lefthook.yml[/green]") |
|
|
|
# Create repository-specific Taskfile |
|
repo_taskfile = ide_settings_dir / "Taskfile.yml" |
|
if not repo_taskfile.exists(): |
|
repo_taskfile.write_text(render_template( |
|
"repo_taskfile.yml.j2", |
|
gist_id=IDE_SETTINGS_MGMT_GIST_ID, |
|
github_user=IDE_SETTINGS_MGMT_GITHUB_USER, |
|
profile_name=profile_name |
|
)) |
|
console.print("[green]β Created repository Taskfile[/green]") |
|
|
|
# Include in root Taskfile.yml if it exists |
|
root_taskfile = repo_path / "Taskfile.yml" |
|
if root_taskfile.exists(): |
|
console.print("[dim]Checking root Taskfile.yml for include...[/dim]") |
|
root_taskfile_content = root_taskfile.read_text() |
|
|
|
# Check if already included |
|
if "ide-settings-mgmt" in root_taskfile_content: |
|
console.print("[dim]β Already included in root Taskfile.yml[/dim]") |
|
else: |
|
console.print("[dim]Adding include to root Taskfile.yml...[/dim]") |
|
# Add include at the end of includes section |
|
with open(root_taskfile, 'a') as f: |
|
f.write("\n # ββ ide-settings-mgmt β Repository IDE settings management βββββββββββββββββ\n") |
|
f.write(" ide-settings-mgmt:\n") |
|
f.write(" taskfile: .ide-settings-mgmt/Taskfile.yml\n") |
|
f.write(" optional: true\n") |
|
console.print("[green]β Added include to root Taskfile.yml[/green]") |
|
|
|
# Create README for .ide-settings-mgmt |
|
readme_file = ide_settings_dir / "README.md" |
|
if not readme_file.exists(): |
|
readme_file.write_text(render_template( |
|
"readme.md.j2", |
|
gist_id=IDE_SETTINGS_MGMT_GIST_ID, |
|
github_user=IDE_SETTINGS_MGMT_GITHUB_USER, |
|
profile_name=profile_name, |
|
ide_name=ide |
|
)) |
|
console.print("[green]β Created README.md[/green]") |
|
|
|
# Adjust .gitignore and other ignore files |
|
adjust_ignore_files(repo_path, ide, operation="migrate") |
|
|
|
console.print(f"[green]β Migration complete![/green]") |
|
console.print(f"[dim]Profile created: {profile_name}[/dim]") |
|
console.print(f"[dim]Settings migrated from: {existing_settings}[/dim]") |
|
console.print(f"[dim]Profile location: {profile_dir}[/dim]") |
|
console.print("[dim]Next steps:[/dim]") |
|
console.print(f"[dim] 1. Review your profile: ide-settings-mgmt profile edit --name {profile_name}[/dim]") |
|
console.print(f"[dim] 2. Apply profile to repo: ide-settings-mgmt apply --profile {profile_name}[/dim]") |
|
console.print("[dim] 3. Commit the changes: git add .ide-settings-mgmt/[/dim]") |
|
|
|
|
|
@app.command() |
|
def profile( |
|
action: str = typer.Argument(..., help="Action: create, list, delete, edit"), |
|
name: Optional[str] = typer.Option(None, "--name", help="Profile name"), |
|
ide: Optional[str] = typer.Option(None, "--ide", help="IDE type (vscode, jetbrains, neovim)") |
|
) -> None: |
|
"""Manage IDE profiles.""" |
|
console.print(Panel.fit(f"[bold blue]IDE Settings Management - Profile Management ({action})[/bold blue]")) |
|
|
|
if action == "list": |
|
"""List all profiles.""" |
|
if not CHEZMOI_SOURCE_DIR.exists(): |
|
console.print("[yellow]No profiles found. Run 'ide-settings-mgmt profile create' first.[/yellow]") |
|
return |
|
|
|
profiles = [d for d in CHEZMOI_SOURCE_DIR.iterdir() if d.is_dir()] |
|
|
|
if not profiles: |
|
console.print("[yellow]No profiles found.[/yellow]") |
|
return |
|
|
|
table = Table(title="IDE Profiles") |
|
table.add_column("Profile", style="cyan") |
|
table.add_column("IDEs", style="green") |
|
|
|
for profile in profiles: |
|
ides = [] |
|
for ide_name in SUPPORTED_IDES.keys(): |
|
ide_config = SUPPORTED_IDES[ide_name] |
|
if (profile / ide_config["config_dir"]).exists(): |
|
ides.append(ide_name) |
|
table.add_row(profile.name, ", ".join(ides) if ides else "empty") |
|
|
|
console.print(table) |
|
|
|
elif action == "create": |
|
"""Create a new profile.""" |
|
if not name: |
|
console.print("[red]--name is required for create action[/red]") |
|
raise typer.Exit(code=1) |
|
|
|
if not ide: |
|
console.print("[red]--ide is required for create action[/red]") |
|
raise typer.Exit(code=1) |
|
|
|
# Initialize chezmoi if needed |
|
init_chezmoi() |
|
|
|
# Create profile template |
|
create_profile_template(ide, name) |
|
|
|
console.print(f"[green]β Created profile '{name}' for {ide}[/green]") |
|
console.print(f"[dim]Edit profile at: {CHEZMOI_SOURCE_DIR / name}[/dim]") |
|
console.print("[dim]Apply with: ide-settings-mgmt apply --profile {name}[/dim]") |
|
|
|
elif action == "delete": |
|
"""Delete a profile.""" |
|
if not name: |
|
console.print("[red]--name is required for delete action[/red]") |
|
raise typer.Exit(code=1) |
|
|
|
profile_path = CHEZMOI_SOURCE_DIR / name |
|
if not profile_path.exists(): |
|
console.print(f"[red]Profile '{name}' not found[/red]") |
|
raise typer.Exit(code=1) |
|
|
|
if typer.confirm(f"Delete profile '{name}'?"): |
|
shutil.rmtree(profile_path) |
|
console.print(f"[green]β Deleted profile '{name}'[/green]") |
|
|
|
elif action == "edit": |
|
"""Edit a profile.""" |
|
if not name: |
|
console.print("[red]--name is required for edit action[/red]") |
|
raise typer.Exit(code=1) |
|
|
|
profile_path = CHEZMOI_SOURCE_DIR / name |
|
if not profile_path.exists(): |
|
console.print(f"[red]Profile '{name}' not found[/red]") |
|
raise typer.Exit(code=1) |
|
|
|
# Open with default editor |
|
editor = os.environ.get("EDITOR", "vim") |
|
run_command([editor, str(profile_path)]) |
|
|
|
else: |
|
console.print(f"[red]Unknown action: {action}[/red]") |
|
console.print("[dim]Available actions: create, list, delete, edit[/dim]") |
|
raise typer.Exit(code=1) |
|
|
|
|
|
@app.command() |
|
def apply( |
|
profile: Optional[str] = typer.Option(None, "--profile", help="Profile name to apply"), |
|
ide: Optional[str] = typer.Option(None, "--ide", help="IDE type (auto-detected if not specified)") |
|
) -> None: |
|
"""Apply IDE profile to current repository.""" |
|
console.print(Panel.fit("[bold blue]IDE Settings Management - Apply Profile[/bold blue]")) |
|
|
|
# Detect IDE if not specified |
|
if not ide: |
|
ide = detect_ide() |
|
if not ide: |
|
console.print("[yellow]No IDE detected. Specify --ide[/yellow]") |
|
raise typer.Exit(code=1) |
|
|
|
# Get profile name |
|
if not profile: |
|
# Use default or ask |
|
profiles = [d.name for d in CHEZMOI_SOURCE_DIR.iterdir() if d.is_dir()] if CHEZMOI_SOURCE_DIR.exists() else [] |
|
if not profiles: |
|
console.print("[yellow]No profiles found. Create one first with 'ide-settings-mgmt profile create'[/yellow]") |
|
raise typer.Exit(code=1) |
|
if len(profiles) == 1: |
|
profile = profiles[0] |
|
else: |
|
console.print("[dim]Available profiles:[/dim]") |
|
for p in profiles: |
|
console.print(f" - {p}") |
|
console.print("[yellow]Specify --profile to choose which profile to apply[/yellow]") |
|
raise typer.Exit(code=1) |
|
|
|
profile_path = CHEZMOI_SOURCE_DIR / profile |
|
if not profile_path.exists(): |
|
console.print(f"[red]Profile '{profile}' not found[/red]") |
|
raise typer.Exit(code=1) |
|
|
|
# Apply profile using chezmoi |
|
ide_config = SUPPORTED_IDES[ide] |
|
source_config = profile_path / ide_config["config_dir"] |
|
|
|
if not source_config.exists(): |
|
console.print(f"[yellow]No {ide} configuration in profile '{profile}'[/yellow]") |
|
console.print(f"[dim]Run 'ide-settings-mgmt profile create --ide {ide} {profile}' first[/dim]") |
|
raise typer.Exit(code=1) |
|
|
|
# Copy IDE settings to repository |
|
target_config = Path.cwd() / ide_config["config_dir"] |
|
target_config.mkdir(parents=True, exist_ok=True) |
|
|
|
# Copy all files from profile to repo |
|
for item in source_config.iterdir(): |
|
if item.is_file(): |
|
shutil.copy2(item, target_config / item.name) |
|
console.print(f"[green]β Copied {item.name}[/green]") |
|
|
|
console.print(f"[green]β Applied profile '{profile}' to {ide}[/green]") |
|
|
|
|
|
@app.command() |
|
def status() -> None: |
|
"""Show IDE settings management status.""" |
|
console.print(Panel.fit("[bold blue]IDE Settings Management - Status[/bold blue]")) |
|
|
|
# Check chezmoi |
|
chezmoi_status = "[green]β Installed[/green]" if check_chezmoi_installed() else "[red]β Not installed[/red]" |
|
console.print(f"chezmoi: {chezmoi_status}") |
|
|
|
# Check profiles |
|
if CHEZMOI_SOURCE_DIR.exists(): |
|
profiles = [d.name for d in CHEZMOI_SOURCE_DIR.iterdir() if d.is_dir()] |
|
console.print(f"Profiles: {len(profiles)} ({', '.join(profiles[:3])}{'...' if len(profiles) > 3 else ''})") |
|
else: |
|
console.print("Profiles: [red]None[/red]") |
|
|
|
# Check current repo |
|
cwd = Path.cwd() |
|
if (cwd / REPO_IDE_DIR).exists(): |
|
console.print(f"Repo: [green]Initialized ({REPO_IDE_DIR})[/green]") |
|
else: |
|
console.print(f"Repo: [yellow]Not initialized[/yellow]") |
|
|
|
# Detect IDE |
|
detected_ide = detect_ide() |
|
if detected_ide: |
|
console.print(f"Detected IDE: [green]{detected_ide}[/green]") |
|
else: |
|
console.print("Detected IDE: [dim]None[/dim]") |
|
|
|
|
|
@app.command() |
|
def sync() -> None: |
|
"""Sync current IDE settings with your profile.""" |
|
console.print(Panel.fit("[bold blue]IDE Settings Management - Sync Settings[/bold blue]")) |
|
|
|
ide = detect_ide() |
|
if not ide: |
|
console.print("[yellow]No IDE detected. Specify --ide[/yellow]") |
|
raise typer.Exit(code=1) |
|
|
|
profile = typer.prompt("Enter profile name to sync to") |
|
profile_path = CHEZMOI_SOURCE_DIR / profile |
|
|
|
if not profile_path.exists(): |
|
console.print(f"[red]Profile '{profile}' not found[/red]") |
|
raise typer.Exit(code=1) |
|
|
|
# Copy current settings to profile |
|
ide_config = SUPPORTED_IDES[ide] |
|
source_config = Path.cwd() / ide_config["config_dir"] |
|
target_config = profile_path / ide_config["config_dir"] |
|
|
|
if not source_config.exists(): |
|
console.print(f"[yellow]No {ide} settings found in current directory[/yellow]") |
|
raise typer.Exit(code=1) |
|
|
|
target_config.mkdir(parents=True, exist_ok=True) |
|
|
|
for item in source_config.iterdir(): |
|
if item.is_file(): |
|
shutil.copy2(item, target_config / item.name) |
|
console.print(f"[green]β Synced {item.name}[/green]") |
|
|
|
console.print(f"[green]β Synced {ide} settings to profile '{profile}'[/green]") |
|
|
|
|
|
if __name__ == "__main__": |
|
app() |