Skip to main content

xdg_user/
lib.rs

1#![cfg(any(unix, target_os = "redox"))]
2
3//! This simple crate allows you to get paths to well known user directories,
4//! using [`xdg-user-dirs`][1]s `user-dirs.dirs` file.
5//!
6//! There are two ways of using this crate - with functions in the root of the
7//! crate, or with the [`UserDirs`] struct. [`UserDirs`] will read and parse the
8//! config file only once - when you call the [`UserDirs::new`] function.
9//! Functions in the root will read and parse the config file EVERY TIME you
10//! call them - so use them ONLY if you need to get one or two folders one or
11//! two times.
12//!
13//! # Example
14//!
15//! ```rust
16//! println!("Pictures folder: {:?}", xdg_user::pictures()?);
17//! println!("Music folder:    {:?}", xdg_user::music()?);
18//!
19//! let dirs = xdg_user::UserDirs::new()?;
20//! println!("Documents folder: {:?}", dirs.documents());
21//! println!("Downloads folder: {:?}", dirs.downloads());
22//! ```
23//!
24//! [1]: https://www.freedesktop.org/wiki/Software/xdg-user-dirs/
25
26use std::path::{Path, PathBuf};
27
28mod parser;
29mod utils;
30
31/// This crates main and only error type
32#[derive(Debug)]
33pub enum Error {
34    /// Something went wrong while accessing the config file
35    Io(std::io::Error),
36    /// Unable to find the home directory
37    NoHome,
38    /// Error while parsing the config file
39    Parse,
40}
41
42impl From<std::io::Error> for Error {
43    fn from(e: std::io::Error) -> Self {
44        Self::Io(e)
45    }
46}
47
48impl From<std::str::Utf8Error> for Error {
49    fn from(_: std::str::Utf8Error) -> Self {
50        Self::Parse
51    }
52}
53
54impl std::fmt::Display for Error {
55    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
56        match self {
57            Self::Io(e) => e.fmt(f),
58            Self::NoHome => write!(f, "unable to find the home directory"),
59            Self::Parse => write!(f, "error while parsing"),
60        }
61    }
62}
63
64impl std::error::Error for Error {}
65
66/// This crates main and only struct, allows you to access the paths to all the
67/// user directories
68pub struct UserDirs {
69    desktop: Option<PathBuf>,
70    documents: Option<PathBuf>,
71    downloads: Option<PathBuf>,
72    music: Option<PathBuf>,
73    pictures: Option<PathBuf>,
74    public: Option<PathBuf>,
75    templates: Option<PathBuf>,
76    videos: Option<PathBuf>,
77    projects: Option<PathBuf>,
78}
79
80impl std::fmt::Debug for UserDirs {
81    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
82        f.debug_struct("UserDirs").finish()
83    }
84}
85
86impl UserDirs {
87    /// Attempts to read and parse the `${XDG_COFNIG_HOME}/user-dirs.dirs` file
88    pub fn new() -> Result<Self, Error> {
89        let mut this = Self {
90            desktop: None,
91            documents: None,
92            downloads: None,
93            music: None,
94            pictures: None,
95            public: None,
96            templates: None,
97            videos: None,
98            projects: None,
99        };
100
101        utils::parse_file(|key, val| {
102            match key {
103                utils::DESKTOP => this.desktop = val,
104                utils::DOCUMENTS => this.documents = val,
105                utils::DOWNLOADS => this.downloads = val,
106                utils::MUSIC => this.music = val,
107                utils::PICTURES => this.pictures = val,
108                utils::PUBLIC => this.public = val,
109                utils::TEMPLATES => this.templates = val,
110                utils::VIDEOS => this.videos = val,
111                utils::PROJECTS => this.projects = val,
112                _ => {}
113            }
114            true
115        })?;
116
117        Ok(this)
118    }
119
120    /// Returns an absolute path to users desktop directory (`XDG_DESKTOP_DIR`),
121    /// if found
122    pub fn desktop(&self) -> Option<&Path> {
123        self.desktop.as_deref()
124    }
125
126    /// Returns an absolute path to users documents directory
127    /// (`XDG_DOCUMENTS_DIR`), if found
128    pub fn documents(&self) -> Option<&Path> {
129        self.documents.as_deref()
130    }
131
132    /// Returns an absolute path to users downloads directory
133    /// (`XDG_DOWNLOAD_DIR`), if found
134    pub fn downloads(&self) -> Option<&Path> {
135        self.downloads.as_deref()
136    }
137
138    /// Returns an absolute path to users music directory (`XDG_MUSIC_DIR`),
139    /// if found
140    pub fn music(&self) -> Option<&Path> {
141        self.music.as_deref()
142    }
143
144    /// Returns an absolute path to users pictures directory
145    /// (`XDG_PICTURES_DIR`), if found
146    pub fn pictures(&self) -> Option<&Path> {
147        self.pictures.as_deref()
148    }
149
150    /// Returns an absolute path to users public share directory
151    /// (`XDG_PUBLICSHARE_DIR`), if found
152    pub fn public(&self) -> Option<&Path> {
153        self.public.as_deref()
154    }
155
156    /// Returns an absolute path to users templates directory
157    /// (`XDG_TEMPLATES_DIR`), if found
158    pub fn templates(&self) -> Option<&Path> {
159        self.templates.as_deref()
160    }
161
162    /// Returns an absolute path to users videos directory (`XDG_VIDEOS_DIR`),
163    /// if found
164    pub fn videos(&self) -> Option<&Path> {
165        self.videos.as_deref()
166    }
167
168    /// Returns an absolute path to users projects directory
169    /// (`XDG_PROJECTS_DIR`), if found
170    pub fn projects(&self) -> Option<&Path> {
171        self.projects.as_deref()
172    }
173}
174
175fn read_single(env: &[u8]) -> Result<Option<PathBuf>, Error> {
176    let mut ret = None;
177    utils::parse_file(|key, val| {
178        if key == env {
179            ret = val;
180            false
181        } else {
182            true
183        }
184    })?;
185
186    Ok(ret)
187}
188
189/// Returns an absolute path to users desktop directory (`XDG_DESKTOP_DIR`), if
190/// found
191///
192/// <div class="warning">
193///
194/// This function will parse the `user-dirs.dirs` file every time it's called,
195/// so if you need paths to multiple different directories - consider using
196/// [`UserDirs`] instead
197///
198/// </div>
199pub fn desktop() -> Result<Option<PathBuf>, Error> {
200    read_single(utils::DESKTOP)
201}
202
203/// Returns an absolute path to users documents directory (`XDG_DOCUMENTS_DIR`),
204/// if found
205///
206/// <div class="warning">
207///
208/// This function will parse the `user-dirs.dirs` file every time it's called,
209/// so if you need paths to multiple different directories - consider using
210/// [`UserDirs`] instead
211///
212/// </div>
213pub fn documents() -> Result<Option<PathBuf>, Error> {
214    read_single(utils::DOCUMENTS)
215}
216
217/// Returns an absolute path to users downloads directory (`XDG_DOWNLOAD_DIR`),
218/// if found
219///
220/// <div class="warning">
221///
222/// This function will parse the `user-dirs.dirs` file every time it's called,
223/// so if you need paths to multiple different directories - consider using
224/// [`UserDirs`] instead
225///
226/// </div>
227pub fn downloads() -> Result<Option<PathBuf>, Error> {
228    read_single(utils::DOWNLOADS)
229}
230
231/// Returns an absolute path to users music directory (`XDG_MUSIC_DIR`),  if
232/// found
233///
234/// <div class="warning">
235///
236/// This function will parse the `user-dirs.dirs` file every time it's called,
237/// so if you need paths to multiple different directories - consider using
238/// [`UserDirs`] instead
239///
240/// </div>
241pub fn music() -> Result<Option<PathBuf>, Error> {
242    read_single(utils::MUSIC)
243}
244
245/// Returns an absolute path to users pictures directory (`XDG_PICTURES_DIR`),
246/// if found
247///
248/// <div class="warning">
249///
250/// This function will parse the `user-dirs.dirs` file every time it's called,
251/// so if you need paths to multiple different directories - consider using
252/// [`UserDirs`] instead
253///
254/// </div>
255pub fn pictures() -> Result<Option<PathBuf>, Error> {
256    read_single(utils::PICTURES)
257}
258
259/// Returns an absolute path to users public share directory
260/// (`XDG_PUBLICSHARE_DIR`), if found
261///
262/// <div class="warning">
263///
264/// This function will parse the `user-dirs.dirs` file every time it's called,
265/// so if you need paths to multiple different directories - consider using
266/// [`UserDirs`] instead
267///
268/// </div>
269pub fn public() -> Result<Option<PathBuf>, Error> {
270    read_single(utils::PUBLIC)
271}
272
273/// Returns an absolute path to users templates directory (`XDG_TEMPLATES_DIR`),
274/// if found
275///
276/// <div class="warning">
277///
278/// This function will parse the `user-dirs.dirs` file every time it's called,
279/// so if you need paths to multiple different directories - consider using
280/// [`UserDirs`] instead
281///
282/// </div>
283pub fn templates() -> Result<Option<PathBuf>, Error> {
284    read_single(utils::TEMPLATES)
285}
286
287/// Returns an absolute path to users videos directory (`XDG_VIDEOS_DIR`), if
288/// found
289///
290/// <div class="warning">
291///
292/// This function will parse the `user-dirs.dirs` file every time it's called,
293/// so if you need paths to multiple different directories - consider using
294/// [`UserDirs`] instead
295///
296/// </div>
297pub fn videos() -> Result<Option<PathBuf>, Error> {
298    read_single(utils::VIDEOS)
299}
300
301/// Returns an absolute path to users projects directory (`XDG_PROJECTS_DIR`),
302/// if found
303///
304/// <div class="warning">
305///
306/// This function will parse the `user-dirs.dirs` file every time it's called,
307/// so if you need paths to multiple different directories - consider using
308/// [`UserDirs`] instead
309///
310/// </div>
311pub fn projects() -> Result<Option<PathBuf>, Error> {
312    read_single(utils::PROJECTS)
313}