Created
June 28, 2024 01:30
-
-
Save tgran2028/904679aff55355702a9a7f7112cf8c5a to your computer and use it in GitHub Desktop.
Shell command line tool for managing path
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import enum | |
import os | |
import sys | |
from pathlib import Path | |
from typing import List, Literal, Optional | |
import click | |
import rich | |
import rich.table | |
import typer | |
app = typer.Typer( | |
name="path-util", | |
add_completion=True, | |
add_help_option=True, | |
help="Utility to manage PATH environment variable.", | |
no_args_is_help=True, | |
) | |
def get_path() -> List[str]: | |
"""Get PATH environment variable. | |
Returns: | |
List[str]: PATH environment variable split into a list of directories. | |
""" | |
return os.environ["PATH"].split(os.pathsep) | |
def deduplicate_path(path: Optional[List[str] | str] = None) -> List[str]: | |
"""Deduplicate PATH environment variable while preserving order. | |
Args: | |
path (Optional[List[str] | str], optional): List of directories or PATH environment variable. Defaults to None. | |
Returns: | |
List[str]: Deduplicated PATH environment variable. | |
""" | |
if path is None: | |
path = get_path() | |
if isinstance(path, str): | |
path = path.split(os.pathsep) | |
new_path = [] | |
for p in path: | |
if p not in new_path: | |
new_path.append(p) | |
return new_path | |
def abs_path(path: str | Path) -> Path: | |
"""Get absolute path with user expansion. | |
Args: | |
path (str | Path): Path to file or directory. | |
Returns: | |
Path: Absolute path. | |
""" | |
if isinstance(path, str): | |
path = Path(path) | |
return path.expanduser().absolute() | |
def _remove_from_path(path: str | Path, | |
path_list: Optional[List[str]] = None) -> List[str]: | |
"""Remove a path from PATH. | |
Args: | |
path (str | Path): Path to remove. | |
path_list (Optional[List[str]], optional): Path list. If None, get PATH environment variable. Defaults to None. | |
Returns: | |
List[str]: List of items in PATH with path removed. | |
""" | |
if path_list is None: | |
path_list = get_path() | |
if isinstance(path, Path): | |
path = str(path) | |
new_path = [] | |
for p in path_list: | |
if p != path: | |
new_path.append(p) | |
return new_path | |
def _add_to_path( | |
path: str | Path, | |
position: Literal["append", "prepend"] = "append") -> List[str]: | |
path = abs_path(path) | |
path_list = get_path() | |
path_list = deduplicate_path(path_list) | |
if path in path_list: | |
path_list = remove_from_path(path, path_list) | |
if position == "prepend": | |
path_list.insert(0, str(path)) | |
else: | |
path_list.append(str(path)) | |
return path_list | |
def filepath_completer(ctx: click.Context, args: List[str], incomplete: str): | |
return [ | |
str(p) for p in Path(".").iterdir() | |
if p.is_dir() and p.name.startswith(incomplete) | |
] | |
class Position(enum.Enum): | |
APPEND = "append" | |
PREPEND = "prepend" | |
@app.command(name="add") | |
def add_to_path( | |
path: str = typer.Argument( | |
..., | |
help="Path to add to PATH", | |
exists=True, | |
metavar="<DIRECTORY>", | |
show_default=False, | |
autocompletion=filepath_completer, | |
), | |
position: Position = typer.Option( | |
"append", | |
"-p", | |
"--position", | |
help="Position to add path to PATH", | |
show_default=True, | |
case_sensitive=False, | |
show_choices=True, | |
), | |
deduplicate: bool = typer.Option( | |
False, | |
"-d", | |
"--deduplicate", | |
help="Deduplicate PATH", | |
show_default=True, | |
show_choices=False, | |
is_flag=True, | |
), | |
): | |
""" | |
Add a path to PATH. | |
""" | |
pos = Position(position) | |
path = abs_path(path) | |
path_list = _add_to_path(path, position=str(pos)) | |
if pos == Position.PREPEND: | |
path_list.insert(0, str(path)) | |
else: | |
path_list.append(str(path)) | |
if deduplicate: | |
path_list = deduplicate_path(path_list) | |
typer.echo(os.pathsep.join(path_list), nl=False) | |
# @app.command(name="search") | |
# def search_path( | |
# text: str = typer.Argument( | |
# ..., | |
# help="Text to search in PATH", | |
# metavar="<TEXT>", | |
# show_default=False, | |
# autocompletion=None, | |
# show_choices=False, | |
# ) | |
# ): | |
# """ | |
# Search for a text in PATH. | |
# """ | |
# path_list = get_path() | |
# if text.startswith("~"): | |
# text = abs_path(text) | |
# path_str = os.pathsep.join(path_list) | |
# path_str = typer.style(path_str, fg=typer.colors.WHITE, dim=True) | |
# path_str.replace( | |
# text, typer.style(text, fg=typer.colors.MAGENTA, bold=True, dim=False) | |
# ) | |
# typer.echo(path_str, nl=False, color=True) | |
@app.command(name="remove") | |
def remove_from_path(path: str = typer.Argument( | |
..., | |
help="Path to add remove from PATH", | |
metavar="<DIRECTORY>", | |
show_default=False, | |
autocompletion=filepath_completer, | |
)): | |
""" | |
Remove a path from PATH. | |
""" | |
path = abs_path(path) | |
path_list = _remove_from_path(path) | |
path_list = [p for p in path_list if not Path(path).samefile(p)] | |
typer.echo(os.pathsep.join(path_list), nl=False) | |
@app.command(name="contains") | |
def is_in_path(path: str = typer.Argument( | |
..., | |
help="Path to check if in PATH", | |
metavar="<DIRECTORY>", | |
show_default=False, | |
autocompletion=filepath_completer, | |
)): | |
""" | |
Check if a path is in PATH. | |
""" | |
path = abs_path(path) | |
path_list = get_path() | |
if str(path) in path_list: | |
typer.Exit(0) | |
else: | |
typer.Exit(1) | |
@app.command(name="deduplicate") | |
def deduplicate_path_cmd(): | |
""" | |
Deduplicate PATH. | |
""" | |
path_list = deduplicate_path() | |
typer.echo(os.pathsep.join(path_list), nl=False) | |
@app.command(name="show") | |
def show_path(): | |
""" | |
Show PATH. | |
""" | |
path_list = get_path() | |
tbl = rich.table.Table( | |
show_header=True, | |
header_style="bold", | |
title="PATH", | |
title_style="bold", | |
show_lines=True, | |
show_edge=False, | |
) | |
tbl.add_column("Path", justify="left") | |
for p in path_list: | |
tbl.add_row(p) | |
console = rich.console.Console() | |
console.print(tbl) | |
if __name__ == "__main__": | |
app() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment