2 from __future__
import annotations
6 from configparser
import ConfigParser
7 from pathlib
import Path
9 from .api
import PlatformDirsABC
11 if sys
.platform
== "win32":
14 msg
= "should only be used on Unix"
15 raise RuntimeError(msg
)
21 class Unix(PlatformDirsABC
):
23 On Unix/Linux, we follow the
24 `XDG Basedir Spec <https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html>`_. The spec allows
25 overriding directories with environment variables. The examples show are the default values, alongside the name of
26 the environment variable that overrides them. Makes use of the
27 `appname <platformdirs.api.PlatformDirsABC.appname>`,
28 `version <platformdirs.api.PlatformDirsABC.version>`,
29 `multipath <platformdirs.api.PlatformDirsABC.multipath>`,
30 `opinion <platformdirs.api.PlatformDirsABC.opinion>`,
31 `ensure_exists <platformdirs.api.PlatformDirsABC.ensure_exists>`.
35 def user_data_dir(self
) -> str:
37 :return: data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or
38 ``$XDG_DATA_HOME/$appname/$version``
40 path
= os
.environ
.get("XDG_DATA_HOME", "")
42 path
= os
.path
.expanduser("~/.local/share") # noqa: PTH111
43 return self
._append
_app
_name
_and
_version
(path
)
46 def site_data_dir(self
) -> str:
48 :return: data directories shared by users (if `multipath <platformdirs.api.PlatformDirsABC.multipath>` is
49 enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS
50 path separator), e.g. ``/usr/local/share/$appname/$version`` or ``/usr/share/$appname/$version``
52 # XDG default for $XDG_DATA_DIRS; only first, if multipath is False
53 path
= os
.environ
.get("XDG_DATA_DIRS", "")
55 path
= f
"/usr/local/share{os.pathsep}/usr/share"
56 return self
._with
_multi
_path
(path
)
58 def _with_multi_path(self
, path
: str) -> str:
59 path_list
= path
.split(os
.pathsep
)
60 if not self
.multipath
:
61 path_list
= path_list
[0:1]
62 path_list
= [self
._append
_app
_name
_and
_version
(os
.path
.expanduser(p
)) for p
in path_list
] # noqa: PTH111
63 return os
.pathsep
.join(path_list
)
66 def user_config_dir(self
) -> str:
68 :return: config directory tied to the user, e.g. ``~/.config/$appname/$version`` or
69 ``$XDG_CONFIG_HOME/$appname/$version``
71 path
= os
.environ
.get("XDG_CONFIG_HOME", "")
73 path
= os
.path
.expanduser("~/.config") # noqa: PTH111
74 return self
._append
_app
_name
_and
_version
(path
)
77 def site_config_dir(self
) -> str:
79 :return: config directories shared by users (if `multipath <platformdirs.api.PlatformDirsABC.multipath>`
80 is enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS
81 path separator), e.g. ``/etc/xdg/$appname/$version``
83 # XDG default for $XDG_CONFIG_DIRS only first, if multipath is False
84 path
= os
.environ
.get("XDG_CONFIG_DIRS", "")
87 return self
._with
_multi
_path
(path
)
90 def user_cache_dir(self
) -> str:
92 :return: cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or
93 ``~/$XDG_CACHE_HOME/$appname/$version``
95 path
= os
.environ
.get("XDG_CACHE_HOME", "")
97 path
= os
.path
.expanduser("~/.cache") # noqa: PTH111
98 return self
._append
_app
_name
_and
_version
(path
)
101 def site_cache_dir(self
) -> str:
102 """:return: cache directory shared by users, e.g. ``/var/tmp/$appname/$version``"""
103 return self
._append
_app
_name
_and
_version
("/var/tmp") # noqa: S108
106 def user_state_dir(self
) -> str:
108 :return: state directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or
109 ``$XDG_STATE_HOME/$appname/$version``
111 path
= os
.environ
.get("XDG_STATE_HOME", "")
113 path
= os
.path
.expanduser("~/.local/state") # noqa: PTH111
114 return self
._append
_app
_name
_and
_version
(path
)
117 def user_log_dir(self
) -> str:
118 """:return: log directory tied to the user, same as `user_state_dir` if not opinionated else ``log`` in it"""
119 path
= self
.user_state_dir
121 path
= os
.path
.join(path
, "log") # noqa: PTH118
122 self
._optionally
_create
_directory
(path
)
126 def user_documents_dir(self
) -> str:
127 """:return: documents directory tied to the user, e.g. ``~/Documents``"""
128 return _get_user_media_dir("XDG_DOCUMENTS_DIR", "~/Documents")
131 def user_downloads_dir(self
) -> str:
132 """:return: downloads directory tied to the user, e.g. ``~/Downloads``"""
133 return _get_user_media_dir("XDG_DOWNLOAD_DIR", "~/Downloads")
136 def user_pictures_dir(self
) -> str:
137 """:return: pictures directory tied to the user, e.g. ``~/Pictures``"""
138 return _get_user_media_dir("XDG_PICTURES_DIR", "~/Pictures")
141 def user_videos_dir(self
) -> str:
142 """:return: videos directory tied to the user, e.g. ``~/Videos``"""
143 return _get_user_media_dir("XDG_VIDEOS_DIR", "~/Videos")
146 def user_music_dir(self
) -> str:
147 """:return: music directory tied to the user, e.g. ``~/Music``"""
148 return _get_user_media_dir("XDG_MUSIC_DIR", "~/Music")
151 def user_desktop_dir(self
) -> str:
152 """:return: desktop directory tied to the user, e.g. ``~/Desktop``"""
153 return _get_user_media_dir("XDG_DESKTOP_DIR", "~/Desktop")
156 def user_runtime_dir(self
) -> str:
158 :return: runtime directory tied to the user, e.g. ``/run/user/$(id -u)/$appname/$version`` or
159 ``$XDG_RUNTIME_DIR/$appname/$version``.
161 For FreeBSD/OpenBSD/NetBSD, it would return ``/var/run/user/$(id -u)/$appname/$version`` if
162 exists, otherwise ``/tmp/runtime-$(id -u)/$appname/$version``, if``$XDG_RUNTIME_DIR``
165 path
= os
.environ
.get("XDG_RUNTIME_DIR", "")
167 if sys
.platform
.startswith(("freebsd", "openbsd", "netbsd")):
168 path
= f
"/var/run/user/{getuid()}"
169 if not Path(path
).exists():
170 path
= f
"/tmp/runtime-{getuid()}" # noqa: S108
172 path
= f
"/run/user/{getuid()}"
173 return self
._append
_app
_name
_and
_version
(path
)
176 def site_runtime_dir(self
) -> str:
178 :return: runtime directory shared by users, e.g. ``/run/$appname/$version`` or \
179 ``$XDG_RUNTIME_DIR/$appname/$version``.
181 Note that this behaves almost exactly like `user_runtime_dir` if ``$XDG_RUNTIME_DIR`` is set, but will
182 fall back to paths associated to the root user instead of a regular logged-in user if it's not set.
184 If you wish to ensure that a logged-in root user path is returned e.g. ``/run/user/0``, use `user_runtime_dir`
187 For FreeBSD/OpenBSD/NetBSD, it would return ``/var/run/$appname/$version`` if ``$XDG_RUNTIME_DIR`` is not set.
189 path
= os
.environ
.get("XDG_RUNTIME_DIR", "")
191 if sys
.platform
.startswith(("freebsd", "openbsd", "netbsd")):
195 return self
._append
_app
_name
_and
_version
(path
)
198 def site_data_path(self
) -> Path
:
199 """:return: data path shared by users. Only return first item, even if ``multipath`` is set to ``True``"""
200 return self
._first
_item
_as
_path
_if
_multipath
(self
.site_data_dir
)
203 def site_config_path(self
) -> Path
:
204 """:return: config path shared by the users. Only return first item, even if ``multipath`` is set to ``True``"""
205 return self
._first
_item
_as
_path
_if
_multipath
(self
.site_config_dir
)
208 def site_cache_path(self
) -> Path
:
209 """:return: cache path shared by users. Only return first item, even if ``multipath`` is set to ``True``"""
210 return self
._first
_item
_as
_path
_if
_multipath
(self
.site_cache_dir
)
212 def _first_item_as_path_if_multipath(self
, directory
: str) -> Path
:
214 # If multipath is True, the first path is returned.
215 directory
= directory
.split(os
.pathsep
)[0]
216 return Path(directory
)
219 def _get_user_media_dir(env_var
: str, fallback_tilde_path
: str) -> str:
220 media_dir
= _get_user_dirs_folder(env_var
)
221 if media_dir
is None:
222 media_dir
= os
.environ
.get(env_var
, "").strip()
224 media_dir
= os
.path
.expanduser(fallback_tilde_path
) # noqa: PTH111
229 def _get_user_dirs_folder(key
: str) -> str |
None:
230 """Return directory from user-dirs.dirs config file. See https://freedesktop.org/wiki/Software/xdg-user-dirs/."""
231 user_dirs_config_path
= Path(Unix().user_config_dir
) / "user-dirs.dirs"
232 if user_dirs_config_path
.exists():
233 parser
= ConfigParser()
235 with user_dirs_config_path
.open() as stream
:
236 # Add fake section header, so ConfigParser doesn't complain
237 parser
.read_string(f
"[top]\n{stream.read()}")
239 if key
not in parser
["top"]:
242 path
= parser
["top"][key
].strip('"')
243 # Handle relative home paths
244 return path
.replace("$HOME", os
.path
.expanduser("~")) # noqa: PTH111