from os import environ from os import getenv from pathlib import Path from shutil import which from subprocess import CalledProcessError from subprocess import check_output from subprocess import PIPE from subprocess import Popen from time import time from typing import Any from typing import Callable from typing import List from typing import Optional from typing import Sequence from typing import Type from typing import Union def locate_cmus_base_path() -> Optional[Path]: for path in ( path.expanduser() for path in (Path("~/.config/cmus/"), Path("~/.cmus/")) ): if path.is_dir(): return path return None def locate_editor() -> Optional[Path]: for editor in (getenv("VISUAL", None), getenv("EDITOR", None), "nano", "vim", "vi"): if editor is not None: # Might be absolute path to editor editor_path = Path(editor).expanduser() # Might also be just the binary name editor_which = which(editor) if editor_path.is_file(): return editor_path elif editor_which is not None: return Path(editor_which) return None def safe_execute( default: Any, exception: Union[Type[BaseException], Sequence[Type[BaseException]]], function: Callable, *args: Any, **kwargs: Any, ): try: return function(*args, **kwargs) except exception: # type: ignore return default def remove_prefix(text: str, prefix: str): return text[len(prefix) :] if text.startswith(prefix) else text # https://stackoverflow.com/a/3505826 def source_env_file(env_file: Path): proc = Popen( ["/bin/sh", "-c", f"set -a && source {str(env_file)} && env"], stdout=PIPE ) for line in (line.decode() for line in proc.stdout): (key, _, value) = line.partition("=") environ[key] = value.rstrip() proc.communicate() def get_cmus_instances() -> Optional[List[int]]: try: return [ int(pid) for pid in check_output(["pgrep", "-x", "cmus"]).decode().split("\n") if pid != "" ] except CalledProcessError: return None # https://gist.github.com/walkermatt/2871026#gistcomment-2280711 def throttle(interval: Union[float, int]): """Decorator ensures function that can only be called once every `s` seconds. """ def decorate(fn: Callable) -> Callable: t = None def wrapped(*args, **kwargs): nonlocal t t_ = time() if t is None or t_ - t >= interval: result = fn(*args, **kwargs) t = time() return result return wrapped return decorate def unexpanduser(path: Path) -> Path: try: return Path("~") / path.relative_to(Path.home()) except ValueError: return path