| import os |
| import subprocess |
| import sys |
| from typing import Any, Callable, Generic, Optional, Text, TypeVar |
| T = TypeVar("T") |
| |
| |
| def rel_path_to_url(rel_path: Text, url_base: Text = "/") -> Text: |
| assert not os.path.isabs(rel_path), rel_path |
| if url_base[0] != "/": |
| url_base = "/" + url_base |
| if url_base[-1] != "/": |
| url_base += "/" |
| return url_base + rel_path.replace(os.sep, "/") |
| |
| |
| def from_os_path(path: Text) -> Text: |
| assert os.path.sep == "/" or sys.platform == "win32" |
| if "/" == os.path.sep: |
| rv = path |
| else: |
| rv = path.replace(os.path.sep, "/") |
| if "\\" in rv: |
| raise ValueError("path contains \\ when separator is %s" % os.path.sep) |
| return rv |
| |
| |
| def to_os_path(path: Text) -> Text: |
| assert os.path.sep == "/" or sys.platform == "win32" |
| if "\\" in path: |
| raise ValueError("normalised path contains \\") |
| if "/" == os.path.sep: |
| return path |
| return path.replace("/", os.path.sep) |
| |
| |
| def git(path: Text) -> Optional[Callable[..., Text]]: |
| def gitfunc(cmd: Text, *args: Text) -> Text: |
| full_cmd = ["git", cmd] + list(args) |
| try: |
| return subprocess.check_output(full_cmd, cwd=path, stderr=subprocess.STDOUT).decode('utf8') |
| except Exception as e: |
| if sys.platform == "win32" and isinstance(e, WindowsError): |
| full_cmd[0] = "git.bat" |
| return subprocess.check_output(full_cmd, cwd=path, stderr=subprocess.STDOUT).decode('utf8') |
| else: |
| raise |
| |
| try: |
| # this needs to be a command that fails if we aren't in a git repo |
| gitfunc("rev-parse", "--show-toplevel") |
| except (subprocess.CalledProcessError, OSError): |
| return None |
| else: |
| return gitfunc |
| |
| |
| class cached_property(Generic[T]): |
| def __init__(self, func: Callable[[Any], T]) -> None: |
| self.func = func |
| self.__doc__ = getattr(func, "__doc__") |
| self.name = func.__name__ |
| |
| def __get__(self, obj: Any, cls: Optional[type] = None) -> T: |
| if obj is None: |
| return self # type: ignore |
| |
| # we can unconditionally assign as next time this won't be called |
| assert self.name not in obj.__dict__ |
| rv = obj.__dict__[self.name] = self.func(obj) |
| obj.__dict__.setdefault("__cached_properties__", set()).add(self.name) |
| return rv |