1 from __future__ import annotations
5 from configparser import ConfigParser
6 from pathlib import Path
8 from .api import PlatformDirsABC
10 if sys.platform.startswith("linux"): # pragma: no branch # no op check, only to please the type checker
15 raise RuntimeError("should only be used on Linux")
18 class Unix(PlatformDirsABC):
20 On Unix/Linux, we follow the
21 `XDG Basedir Spec <https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html>`_. The spec allows
22 overriding directories with environment variables. The examples show are the default values, alongside the name of
23 the environment variable that overrides them. Makes use of the
24 `appname <platformdirs.api.PlatformDirsABC.appname>`,
25 `version <platformdirs.api.PlatformDirsABC.version>`,
26 `multipath <platformdirs.api.PlatformDirsABC.multipath>`,
27 `opinion <platformdirs.api.PlatformDirsABC.opinion>`.
31 def user_data_dir(self) -> str:
33 :return: data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or
34 ``$XDG_DATA_HOME/$appname/$version``
36 path = os.environ.get("XDG_DATA_HOME", "")
38 path = os.path.expanduser("~/.local/share")
39 return self._append_app_name_and_version(path)
42 def site_data_dir(self) -> str:
44 :return: data directories shared by users (if `multipath <platformdirs.api.PlatformDirsABC.multipath>` is
45 enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS
46 path separator), e.g. ``/usr/local/share/$appname/$version`` or ``/usr/share/$appname/$version``
48 # XDG default for $XDG_DATA_DIRS; only first, if multipath is False
49 path = os.environ.get("XDG_DATA_DIRS", "")
51 path = f"/usr/local/share{os.pathsep}/usr/share"
52 return self._with_multi_path(path)
54 def _with_multi_path(self, path: str) -> str:
55 path_list = path.split(os.pathsep)
56 if not self.multipath:
57 path_list = path_list[0:1]
58 path_list = [self._append_app_name_and_version(os.path.expanduser(p)) for p in path_list]
59 return os.pathsep.join(path_list)
62 def user_config_dir(self) -> str:
64 :return: config directory tied to the user, e.g. ``~/.config/$appname/$version`` or
65 ``$XDG_CONFIG_HOME/$appname/$version``
67 path = os.environ.get("XDG_CONFIG_HOME", "")
69 path = os.path.expanduser("~/.config")
70 return self._append_app_name_and_version(path)
73 def site_config_dir(self) -> str:
75 :return: config directories shared by users (if `multipath <platformdirs.api.PlatformDirsABC.multipath>`
76 is enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS
77 path separator), e.g. ``/etc/xdg/$appname/$version``
79 # XDG default for $XDG_CONFIG_DIRS only first, if multipath is False
80 path = os.environ.get("XDG_CONFIG_DIRS", "")
83 return self._with_multi_path(path)
86 def user_cache_dir(self) -> str:
88 :return: cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or
89 ``~/$XDG_CACHE_HOME/$appname/$version``
91 path = os.environ.get("XDG_CACHE_HOME", "")
93 path = os.path.expanduser("~/.cache")
94 return self._append_app_name_and_version(path)
97 def user_state_dir(self) -> str:
99 :return: state directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or
100 ``$XDG_STATE_HOME/$appname/$version``
102 path = os.environ.get("XDG_STATE_HOME", "")
104 path = os.path.expanduser("~/.local/state")
105 return self._append_app_name_and_version(path)
108 def user_log_dir(self) -> str:
110 :return: log directory tied to the user, same as `user_state_dir` if not opinionated else ``log`` in it
112 path = self.user_state_dir
114 path = os.path.join(path, "log")
118 def user_documents_dir(self) -> str:
120 :return: documents directory tied to the user, e.g. ``~/Documents``
122 documents_dir = _get_user_dirs_folder("XDG_DOCUMENTS_DIR")
123 if documents_dir is None:
124 documents_dir = os.environ.get("XDG_DOCUMENTS_DIR", "").strip()
125 if not documents_dir:
126 documents_dir = os.path.expanduser("~/Documents")
131 def user_runtime_dir(self) -> str:
133 :return: runtime directory tied to the user, e.g. ``/run/user/$(id -u)/$appname/$version`` or
134 ``$XDG_RUNTIME_DIR/$appname/$version``
136 path = os.environ.get("XDG_RUNTIME_DIR", "")
138 path = f"/run/user/{getuid()}"
139 return self._append_app_name_and_version(path)
142 def site_data_path(self) -> Path:
143 """:return: data path shared by users. Only return first item, even if ``multipath`` is set to ``True``"""
144 return self._first_item_as_path_if_multipath(self.site_data_dir)
147 def site_config_path(self) -> Path:
148 """:return: config path shared by the users. Only return first item, even if ``multipath`` is set to ``True``"""
149 return self._first_item_as_path_if_multipath(self.site_config_dir)
151 def _first_item_as_path_if_multipath(self, directory: str) -> Path:
153 # If multipath is True, the first path is returned.
154 directory = directory.split(os.pathsep)[0]
155 return Path(directory)
158 def _get_user_dirs_folder(key: str) -> str | None:
159 """Return directory from user-dirs.dirs config file. See https://freedesktop.org/wiki/Software/xdg-user-dirs/"""
160 user_dirs_config_path = os.path.join(Unix().user_config_dir, "user-dirs.dirs")
161 if os.path.exists(user_dirs_config_path):
162 parser = ConfigParser()
164 with open(user_dirs_config_path) as stream:
165 # Add fake section header, so ConfigParser doesn't complain
166 parser.read_string(f"[top]\n{stream.read()}")
168 if key not in parser["top"]:
171 path = parser["top"][key].strip('"')
172 # Handle relative home paths
173 path = path.replace("$HOME", os.path.expanduser("~"))