Skip to content

Commit d279747

Browse files
authored
✨ feat: add user_projects_dir for $XDG_PROJECTS_DIR (#490)
The XDG user-dirs spec added `XDG_PROJECTS_DIR` in xdg-user-dirs 0.0.4 and recently wired it up fully (see [commit 217cae71](https://gitlab.freedesktop.org/xdg/xdg-user-dirs/-/commit/217cae71c620ed2b3ed2936256ece68defccc6ab) and [issue xdg-user-dirs#3](https://gitlab.freedesktop.org/xdg/xdg-user-dirs/-/work_items/3)). Other ecosystems (Rust `dirs`, Java `directories-jvm`) already expose this. Closes #485. The implementation follows the same pattern as `user_documents_dir` and other XDG user-dirs: on Linux, `unix.py` reads from `user-dirs.dirs` via `_get_user_media_dir("XDG_PROJECTS_DIR", "~/Projects")`, `XDGMixin` in `_xdg.py` handles the env var override, and all other platforms (macOS, Windows, Android) fall back to `~/Projects` since no native shell folder exists for projects on those platforms. Both `user_projects_dir` (str) and `user_projects_path` (Path) are exposed as properties, module-level convenience functions, and documented in `api.rst` and `platforms.rst`.
1 parent 4116391 commit d279747

15 files changed

Lines changed: 96 additions & 0 deletions

File tree

docs/api.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,15 @@ See also: :ref:`platforms:``user_desktop_dir```
141141

142142
.. autofunction:: platformdirs.user_desktop_path
143143

144+
User projects directory
145+
=======================
146+
147+
See also: :ref:`platforms:``user_projects_dir```
148+
149+
.. autofunction:: platformdirs.user_projects_dir
150+
151+
.. autofunction:: platformdirs.user_projects_path
152+
144153
********************
145154
Shared directories
146155
********************

docs/platforms.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,34 @@ See also: :ref:`api:User desktop directory`
401401

402402
``/storage/emulated/0/Desktop``
403403

404+
``user_projects_dir``
405+
=====================
406+
407+
See also: :ref:`api:User projects directory`
408+
409+
.. tab-set::
410+
411+
.. tab-item:: Linux
412+
:sync: linux
413+
414+
``~/Projects`` (from ``$XDG_PROJECTS_DIR`` if set, else ``$XDG_PROJECTS_DIR`` entry in
415+
``user-dirs.dirs``, else ``~/Projects``)
416+
417+
.. tab-item:: macOS
418+
:sync: macos
419+
420+
``~/Projects``
421+
422+
.. tab-item:: Windows
423+
:sync: windows
424+
425+
``C:\Users\<User>\Projects``
426+
427+
.. tab-item:: Android
428+
:sync: android
429+
430+
``/storage/emulated/0/Projects``
431+
404432
********************
405433
Shared directories
406434
********************

src/platformdirs/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,11 @@ def user_desktop_dir() -> str:
344344
return PlatformDirs().user_desktop_dir
345345

346346

347+
def user_projects_dir() -> str:
348+
""":returns: projects directory tied to the user"""
349+
return PlatformDirs().user_projects_dir
350+
351+
347352
def user_bin_dir() -> str:
348353
""":returns: bin directory tied to the user"""
349354
return PlatformDirs().user_bin_dir
@@ -720,6 +725,11 @@ def user_desktop_path() -> Path:
720725
return PlatformDirs().user_desktop_path
721726

722727

728+
def user_projects_path() -> Path:
729+
""":returns: projects path tied to the user"""
730+
return PlatformDirs().user_projects_path
731+
732+
723733
def user_bin_path() -> Path:
724734
""":returns: bin path tied to the user"""
725735
return PlatformDirs().user_bin_path
@@ -848,6 +858,8 @@ def site_runtime_path(
848858
"user_music_path",
849859
"user_pictures_dir",
850860
"user_pictures_path",
861+
"user_projects_dir",
862+
"user_projects_path",
851863
"user_runtime_dir",
852864
"user_runtime_path",
853865
"user_state_dir",

src/platformdirs/__main__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"user_pictures_dir",
1616
"user_videos_dir",
1717
"user_music_dir",
18+
"user_projects_dir",
1819
"user_bin_dir",
1920
"site_bin_dir",
2021
"user_applications_dir",

src/platformdirs/_xdg.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,13 @@ def user_desktop_dir(self) -> str:
118118
return os.path.expanduser(path) # noqa: PTH111
119119
return super().user_desktop_dir
120120

121+
@property
122+
def user_projects_dir(self) -> str:
123+
""":returns: projects directory tied to the user, from ``$XDG_PROJECTS_DIR`` if set, else platform default"""
124+
if path := os.environ.get("XDG_PROJECTS_DIR", "").strip():
125+
return os.path.expanduser(path) # noqa: PTH111
126+
return super().user_projects_dir
127+
121128
@property
122129
def user_applications_dir(self) -> str:
123130
""":returns: applications directory tied to the user, from ``$XDG_DATA_HOME`` if set, else platform default"""

src/platformdirs/android.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ def user_desktop_dir(self) -> str:
107107
""":returns: desktop directory tied to the user e.g. ``/storage/emulated/0/Desktop``"""
108108
return "/storage/emulated/0/Desktop"
109109

110+
@property
111+
def user_projects_dir(self) -> str:
112+
""":returns: projects directory tied to the user e.g. ``/storage/emulated/0/Projects``"""
113+
return "/storage/emulated/0/Projects"
114+
110115
@property
111116
def user_bin_dir(self) -> str:
112117
""":returns: bin directory tied to the user, e.g. ``/data/user/<userid>/<packagename>/files/bin``"""

src/platformdirs/api.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,11 @@ def user_music_dir(self) -> str:
208208
def user_desktop_dir(self) -> str:
209209
""":returns: desktop directory tied to the user"""
210210

211+
@property
212+
@abstractmethod
213+
def user_projects_dir(self) -> str:
214+
""":returns: projects directory tied to the user"""
215+
211216
@property
212217
@abstractmethod
213218
def user_bin_dir(self) -> str:
@@ -322,6 +327,11 @@ def user_desktop_path(self) -> Path:
322327
""":returns: desktop path tied to the user"""
323328
return Path(self.user_desktop_dir)
324329

330+
@property
331+
def user_projects_path(self) -> Path:
332+
""":returns: projects path tied to the user"""
333+
return Path(self.user_projects_dir)
334+
325335
@property
326336
def user_bin_path(self) -> Path:
327337
""":returns: bin path tied to the user"""

src/platformdirs/macos.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ def user_desktop_dir(self) -> str:
134134
""":returns: desktop directory tied to the user, e.g. ``~/Desktop``"""
135135
return os.path.expanduser("~/Desktop") # noqa: PTH111
136136

137+
@property
138+
def user_projects_dir(self) -> str:
139+
""":returns: projects directory tied to the user, e.g. ``~/Projects``"""
140+
return os.path.expanduser("~/Projects") # noqa: PTH111
141+
137142
@property
138143
def user_bin_dir(self) -> str:
139144
""":returns: bin directory tied to the user, e.g. ``~/.local/bin``"""

src/platformdirs/unix.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ def user_desktop_dir(self) -> str:
123123
""":returns: desktop directory tied to the user, e.g. ``~/Desktop``"""
124124
return _get_user_media_dir("XDG_DESKTOP_DIR", "~/Desktop")
125125

126+
@property
127+
def user_projects_dir(self) -> str:
128+
""":returns: projects directory tied to the user, e.g. ``~/Projects``"""
129+
return _get_user_media_dir("XDG_PROJECTS_DIR", "~/Projects")
130+
126131
@property
127132
def user_bin_dir(self) -> str:
128133
""":returns: bin directory tied to the user, e.g. ``~/.local/bin``"""

src/platformdirs/windows.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ def user_desktop_dir(self) -> str:
133133
r""":returns: desktop directory tied to the user, e.g. ``%USERPROFILE%\Desktop``"""
134134
return os.path.normpath(get_win_folder("CSIDL_DESKTOPDIRECTORY"))
135135

136+
@property
137+
def user_projects_dir(self) -> str:
138+
r""":returns: projects directory tied to the user, e.g. ``%USERPROFILE%\Projects``"""
139+
return os.path.normpath(os.path.expanduser("~/Projects")) # noqa: PTH111
140+
136141
@property
137142
def user_bin_dir(self) -> str:
138143
r""":returns: bin directory tied to the user, e.g. ``%LOCALAPPDATA%\Programs``"""

0 commit comments

Comments
 (0)