9aca5a030545d3ba26fa96fbfd7cae1c31dcdd15
[SubU] /
1 from __future__ import annotations
2
3 import os
4 import sys
5 from configparser import ConfigParser
6 from pathlib import Path
7
8 from .api import PlatformDirsABC
9
10 if sys.platform.startswith("linux"):  # pragma: no branch # no op check, only to please the type checker
11     from os import getuid
12 else:
13
14     def getuid() -> int:
15         raise RuntimeError("should only be used on Linux")
16
17
18 class Unix(PlatformDirsABC):
19     """
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>`.
28     """
29
30     @property
31     def user_data_dir(self) -> str:
32         """
33         :return: data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or
34          ``$XDG_DATA_HOME/$appname/$version``
35         """
36         path = os.environ.get("XDG_DATA_HOME", "")
37         if not path.strip():
38             path = os.path.expanduser("~/.local/share")
39         return self._append_app_name_and_version(path)
40
41     @property
42     def site_data_dir(self) -> str:
43         """
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``
47         """
48         # XDG default for $XDG_DATA_DIRS; only first, if multipath is False
49         path = os.environ.get("XDG_DATA_DIRS", "")
50         if not path.strip():
51             path = f"/usr/local/share{os.pathsep}/usr/share"
52         return self._with_multi_path(path)
53
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)
60
61     @property
62     def user_config_dir(self) -> str:
63         """
64         :return: config directory tied to the user, e.g. ``~/.config/$appname/$version`` or
65          ``$XDG_CONFIG_HOME/$appname/$version``
66         """
67         path = os.environ.get("XDG_CONFIG_HOME", "")
68         if not path.strip():
69             path = os.path.expanduser("~/.config")
70         return self._append_app_name_and_version(path)
71
72     @property
73     def site_config_dir(self) -> str:
74         """
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``
78         """
79         # XDG default for $XDG_CONFIG_DIRS only first, if multipath is False
80         path = os.environ.get("XDG_CONFIG_DIRS", "")
81         if not path.strip():
82             path = "/etc/xdg"
83         return self._with_multi_path(path)
84
85     @property
86     def user_cache_dir(self) -> str:
87         """
88         :return: cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or
89          ``~/$XDG_CACHE_HOME/$appname/$version``
90         """
91         path = os.environ.get("XDG_CACHE_HOME", "")
92         if not path.strip():
93             path = os.path.expanduser("~/.cache")
94         return self._append_app_name_and_version(path)
95
96     @property
97     def user_state_dir(self) -> str:
98         """
99         :return: state directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or
100          ``$XDG_STATE_HOME/$appname/$version``
101         """
102         path = os.environ.get("XDG_STATE_HOME", "")
103         if not path.strip():
104             path = os.path.expanduser("~/.local/state")
105         return self._append_app_name_and_version(path)
106
107     @property
108     def user_log_dir(self) -> str:
109         """
110         :return: log directory tied to the user, same as `user_state_dir` if not opinionated else ``log`` in it
111         """
112         path = self.user_state_dir
113         if self.opinion:
114             path = os.path.join(path, "log")
115         return path
116
117     @property
118     def user_documents_dir(self) -> str:
119         """
120         :return: documents directory tied to the user, e.g. ``~/Documents``
121         """
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")
127
128         return documents_dir
129
130     @property
131     def user_runtime_dir(self) -> str:
132         """
133         :return: runtime directory tied to the user, e.g. ``/run/user/$(id -u)/$appname/$version`` or
134          ``$XDG_RUNTIME_DIR/$appname/$version``
135         """
136         path = os.environ.get("XDG_RUNTIME_DIR", "")
137         if not path.strip():
138             path = f"/run/user/{getuid()}"
139         return self._append_app_name_and_version(path)
140
141     @property
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)
145
146     @property
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)
150
151     def _first_item_as_path_if_multipath(self, directory: str) -> Path:
152         if self.multipath:
153             # If multipath is True, the first path is returned.
154             directory = directory.split(os.pathsep)[0]
155         return Path(directory)
156
157
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()
163
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()}")
167
168         if key not in parser["top"]:
169             return None
170
171         path = parser["top"][key].strip('"')
172         # Handle relative home paths
173         path = path.replace("$HOME", os.path.expanduser("~"))
174         return path
175
176     return None
177
178
179 __all__ = [
180     "Unix",
181 ]