diff --git a/README.md b/README.md index 7c48d9d..2bae130 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,35 @@ source .venv/bin/activate pip install -e . ``` +## Установка через pipx (глобальная команда `pathi`) + +Если нужно, чтобы `pathi` была доступна глобально (вне конкретного venv), используйте `pipx`: + +```bash +python3 -m pip install --user pipx +python3 -m pipx ensurepath +# перезапустите терминал +pipx install /полный/путь/к/EnvPathShortcutTool +``` + +Пример для вашего случая: + +```bash +pipx install ~/Sources/EnvPathShortcutTool-main +``` + +Обновить установленную версию после `git pull`: + +```bash +pipx upgrade pathindex +``` + +Удалить: + +```bash +pipx uninstall pathindex +``` + ## Использование ```bash @@ -28,8 +57,27 @@ pathi search conf pathi pseudo /home/user/.config/nvim pathi alias add projects /home/user/projects pathi open nvim +pathi env sync --source alias --normalize --file ~/.config/pathi/environment --rebuild ``` +### Экспорт индекса в переменные окружения + +Чтобы превращать записи индекса в переменные среды (например, для быстрого доступа к `$STEAM`), +используйте: + +```bash +pathi env sync --source alias --normalize --print-only +``` + +Для записи в файл окружения: + +```bash +pathi env sync --source alias --normalize --file /etc/environment +``` + +> Для `/etc/environment` обычно требуются права root (запуск через `sudo`). +> В файл добавляется/обновляется только блок, помеченный как `pathi managed`. + ## Хранилище По умолчанию: diff --git a/pathindex/cli.py b/pathindex/cli.py index 37f1c7d..7d262d0 100644 --- a/pathindex/cli.py +++ b/pathindex/cli.py @@ -1,6 +1,7 @@ from __future__ import annotations import argparse +import re from pathlib import Path from .actions import copy_to_clipboard, open_path, open_terminal @@ -8,6 +9,33 @@ from .search import to_pseudo_path from .storage import ensure_storage, insert_entries, load_aliases, reset_index, search_entries, save_aliases +MANAGED_ENV_BEGIN = "# >>> pathi managed variables >>>" +MANAGED_ENV_END = "# <<< pathi managed variables <<<" + + +def normalize_env_name(name: str) -> str: + normalized = re.sub(r"[^A-Za-z0-9_]", "_", name.upper()) + if not normalized: + return "PI_PATH" + if normalized[0].isdigit(): + return f"PI_{normalized}" + return normalized + + +def is_env_name(name: str) -> bool: + return bool(re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", name)) + + +def replace_managed_block(content: str, block: str) -> str: + pattern = re.compile( + rf"\n?{re.escape(MANAGED_ENV_BEGIN)}\n.*?\n{re.escape(MANAGED_ENV_END)}\n?", + re.DOTALL, + ) + cleaned = re.sub(pattern, "\n", content).rstrip("\n") + if cleaned: + return f"{cleaned}\n\n{block}\n" + return f"{block}\n" + def build_index() -> int: root = ensure_storage() @@ -114,6 +142,45 @@ def cmd_alias_list(_: argparse.Namespace) -> int: return 0 +def cmd_env_sync(args: argparse.Namespace) -> int: + root = ensure_storage() + db = root / "index.sqlite" + if args.rebuild: + build_index() + + entries = search_entries(db, "", 1_000_000) + selected = [e for e in entries if args.source == "all" or e.source == args.source] + + out: dict[str, str] = {} + skipped: list[str] = [] + for entry in selected: + key = normalize_env_name(entry.name) if args.normalize else entry.name + if not is_env_name(key): + skipped.append(entry.name) + continue + out.setdefault(key, entry.path) + + lines = [MANAGED_ENV_BEGIN] + for key in sorted(out): + lines.append(f'{key}="{out[key]}"') + lines.append(MANAGED_ENV_END) + block = "\n".join(lines) + + if args.print_only: + print(block) + else: + target = Path(args.file).expanduser() + existing = target.read_text(encoding="utf-8") if target.exists() else "" + updated = replace_managed_block(existing, block) + target.write_text(updated, encoding="utf-8") + print(f"Updated {target}") + + print(f"Exported {len(out)} variables") + if skipped: + print(f"Skipped invalid names: {len(skipped)}") + return 0 + + def build_parser() -> argparse.ArgumentParser: p = argparse.ArgumentParser(prog="pathi", description="Path Index CLI") sub = p.add_subparsers(dest="command", required=True) @@ -155,6 +222,17 @@ def build_parser() -> argparse.ArgumentParser: a = alias_sub.add_parser("list", help="List aliases") a.set_defaults(func=cmd_alias_list) + env = sub.add_parser("env", help="Export index entries as environment variables") + env_sub = env.add_subparsers(dest="env_cmd", required=True) + + e = env_sub.add_parser("sync", help="Sync variables to an env file (e.g. /etc/environment)") + e.add_argument("--file", default="/etc/environment") + e.add_argument("--source", choices=["all", "alias", "env", "path"], default="all") + e.add_argument("--normalize", action="store_true", help="Convert names to valid ENV keys") + e.add_argument("--rebuild", action="store_true", help="Rebuild index before export") + e.add_argument("--print-only", action="store_true", help="Print managed block instead of writing file") + e.set_defaults(func=cmd_env_sync) + return p