unix.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. from __future__ import annotations
  2. import os
  3. import sys
  4. from configparser import ConfigParser
  5. from pathlib import Path
  6. from .api import PlatformDirsABC
  7. if sys.platform.startswith("linux"): # pragma: no branch # no op check, only to please the type checker
  8. from os import getuid
  9. else:
  10. def getuid() -> int:
  11. raise RuntimeError("should only be used on Linux")
  12. class Unix(PlatformDirsABC):
  13. """
  14. On Unix/Linux, we follow the
  15. `XDG Basedir Spec <https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html>`_. The spec allows
  16. overriding directories with environment variables. The examples show are the default values, alongside the name of
  17. the environment variable that overrides them. Makes use of the
  18. `appname <platformdirs.api.PlatformDirsABC.appname>`,
  19. `version <platformdirs.api.PlatformDirsABC.version>`,
  20. `multipath <platformdirs.api.PlatformDirsABC.multipath>`,
  21. `opinion <platformdirs.api.PlatformDirsABC.opinion>`.
  22. """
  23. @property
  24. def user_data_dir(self) -> str:
  25. """
  26. :return: data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or
  27. ``$XDG_DATA_HOME/$appname/$version``
  28. """
  29. path = os.environ.get("XDG_DATA_HOME", "")
  30. if not path.strip():
  31. path = os.path.expanduser("~/.local/share")
  32. return self._append_app_name_and_version(path)
  33. @property
  34. def site_data_dir(self) -> str:
  35. """
  36. :return: data directories shared by users (if `multipath <platformdirs.api.PlatformDirsABC.multipath>` is
  37. enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS
  38. path separator), e.g. ``/usr/local/share/$appname/$version`` or ``/usr/share/$appname/$version``
  39. """
  40. # XDG default for $XDG_DATA_DIRS; only first, if multipath is False
  41. path = os.environ.get("XDG_DATA_DIRS", "")
  42. if not path.strip():
  43. path = f"/usr/local/share{os.pathsep}/usr/share"
  44. return self._with_multi_path(path)
  45. def _with_multi_path(self, path: str) -> str:
  46. path_list = path.split(os.pathsep)
  47. if not self.multipath:
  48. path_list = path_list[0:1]
  49. path_list = [self._append_app_name_and_version(os.path.expanduser(p)) for p in path_list]
  50. return os.pathsep.join(path_list)
  51. @property
  52. def user_config_dir(self) -> str:
  53. """
  54. :return: config directory tied to the user, e.g. ``~/.config/$appname/$version`` or
  55. ``$XDG_CONFIG_HOME/$appname/$version``
  56. """
  57. path = os.environ.get("XDG_CONFIG_HOME", "")
  58. if not path.strip():
  59. path = os.path.expanduser("~/.config")
  60. return self._append_app_name_and_version(path)
  61. @property
  62. def site_config_dir(self) -> str:
  63. """
  64. :return: config directories shared by users (if `multipath <platformdirs.api.PlatformDirsABC.multipath>`
  65. is enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS
  66. path separator), e.g. ``/etc/xdg/$appname/$version``
  67. """
  68. # XDG default for $XDG_CONFIG_DIRS only first, if multipath is False
  69. path = os.environ.get("XDG_CONFIG_DIRS", "")
  70. if not path.strip():
  71. path = "/etc/xdg"
  72. return self._with_multi_path(path)
  73. @property
  74. def user_cache_dir(self) -> str:
  75. """
  76. :return: cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or
  77. ``~/$XDG_CACHE_HOME/$appname/$version``
  78. """
  79. path = os.environ.get("XDG_CACHE_HOME", "")
  80. if not path.strip():
  81. path = os.path.expanduser("~/.cache")
  82. return self._append_app_name_and_version(path)
  83. @property
  84. def user_state_dir(self) -> str:
  85. """
  86. :return: state directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or
  87. ``$XDG_STATE_HOME/$appname/$version``
  88. """
  89. path = os.environ.get("XDG_STATE_HOME", "")
  90. if not path.strip():
  91. path = os.path.expanduser("~/.local/state")
  92. return self._append_app_name_and_version(path)
  93. @property
  94. def user_log_dir(self) -> str:
  95. """
  96. :return: log directory tied to the user, same as `user_data_dir` if not opinionated else ``log`` in it
  97. """
  98. path = self.user_cache_dir
  99. if self.opinion:
  100. path = os.path.join(path, "log")
  101. return path
  102. @property
  103. def user_documents_dir(self) -> str:
  104. """
  105. :return: documents directory tied to the user, e.g. ``~/Documents``
  106. """
  107. documents_dir = _get_user_dirs_folder("XDG_DOCUMENTS_DIR")
  108. if documents_dir is None:
  109. documents_dir = os.environ.get("XDG_DOCUMENTS_DIR", "").strip()
  110. if not documents_dir:
  111. documents_dir = os.path.expanduser("~/Documents")
  112. return documents_dir
  113. @property
  114. def user_runtime_dir(self) -> str:
  115. """
  116. :return: runtime directory tied to the user, e.g. ``/run/user/$(id -u)/$appname/$version`` or
  117. ``$XDG_RUNTIME_DIR/$appname/$version``
  118. """
  119. path = os.environ.get("XDG_RUNTIME_DIR", "")
  120. if not path.strip():
  121. path = f"/run/user/{getuid()}"
  122. return self._append_app_name_and_version(path)
  123. @property
  124. def site_data_path(self) -> Path:
  125. """:return: data path shared by users. Only return first item, even if ``multipath`` is set to ``True``"""
  126. return self._first_item_as_path_if_multipath(self.site_data_dir)
  127. @property
  128. def site_config_path(self) -> Path:
  129. """:return: config path shared by the users. Only return first item, even if ``multipath`` is set to ``True``"""
  130. return self._first_item_as_path_if_multipath(self.site_config_dir)
  131. def _first_item_as_path_if_multipath(self, directory: str) -> Path:
  132. if self.multipath:
  133. # If multipath is True, the first path is returned.
  134. directory = directory.split(os.pathsep)[0]
  135. return Path(directory)
  136. def _get_user_dirs_folder(key: str) -> str | None:
  137. """Return directory from user-dirs.dirs config file. See https://freedesktop.org/wiki/Software/xdg-user-dirs/"""
  138. user_dirs_config_path = os.path.join(Unix().user_config_dir, "user-dirs.dirs")
  139. if os.path.exists(user_dirs_config_path):
  140. parser = ConfigParser()
  141. with open(user_dirs_config_path) as stream:
  142. # Add fake section header, so ConfigParser doesn't complain
  143. parser.read_string(f"[top]\n{stream.read()}")
  144. if key not in parser["top"]:
  145. return None
  146. path = parser["top"][key].strip('"')
  147. # Handle relative home paths
  148. path = path.replace("$HOME", os.path.expanduser("~"))
  149. return path
  150. return None
  151. __all__ = [
  152. "Unix",
  153. ]