etcetera-0.8.0/.cargo_vcs_info.json0000644000000001360000000000100126350ustar { "git": { "sha1": "1124ed215f5378ee9072ea92da143e8e1a9ca445" }, "path_in_vcs": "" }etcetera-0.8.0/.github/workflows/ci.yaml000064400000000000000000000016201046102023000163000ustar 00000000000000name: CI on: [push, pull_request] jobs: check_fmt: name: fmt ${{ github.sha }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: toolchain: stable override: true - uses: actions-rs/cargo@v1 with: command: fmt args: -- --check ci: strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest] task: [['test', ''], ['clippy', '-- --deny warnings']] name: ${{ matrix.task[0] }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: toolchain: stable override: true components: clippy - uses: actions-rs/cargo@v1 with: command: ${{ matrix.task[0] }} args: ${{ matrix.task[1] }} etcetera-0.8.0/.gitignore000064400000000000000000000000231046102023000134100ustar 00000000000000/target Cargo.lock etcetera-0.8.0/Cargo.toml0000644000000021710000000000100106340ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "etcetera" version = "0.8.0" description = "An unopinionated library for obtaining configuration, data, cache, & other directories" homepage = "https://github.com/lunacookies/etcetera" documentation = "https://docs.rs/etcetera" readme = "README.md" keywords = [ "xdg", "dirs", "directories", "basedir", "path", ] categories = ["config"] license = "MIT OR Apache-2.0" repository = "https://github.com/lunacookies/etcetera" [dependencies.cfg-if] version = "1" [dependencies.home] version = "0.5" [target."cfg(windows)".dependencies.windows-sys] version = "0.48" features = [ "Win32_Foundation", "Win32_UI_Shell", ] etcetera-0.8.0/Cargo.toml.orig000064400000000000000000000012401046102023000143110ustar 00000000000000[package] categories = ["config"] description = "An unopinionated library for obtaining configuration, data, cache, & other directories" documentation = "https://docs.rs/etcetera" edition = "2018" homepage = "https://github.com/lunacookies/etcetera" keywords = ["xdg", "dirs", "directories", "basedir", "path"] license = "MIT OR Apache-2.0" name = "etcetera" readme = "README.md" repository = "https://github.com/lunacookies/etcetera" version = "0.8.0" [dependencies] cfg-if = "1" home = "0.5" # We should keep this in sync with the `home` crate. [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.48", features = ["Win32_Foundation", "Win32_UI_Shell"] } etcetera-0.8.0/LICENSE-APACHE000064400000000000000000000227731046102023000133640ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS etcetera-0.8.0/LICENSE-MIT000064400000000000000000000017771046102023000130750ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. etcetera-0.8.0/README.md000064400000000000000000000052341046102023000127100ustar 00000000000000[![crates.io version](https://img.shields.io/crates/v/etcetera?style=for-the-badge)](https://crates.io/crates/etcetera) [![crates.io revdeps](https://img.shields.io/crates/d/etcetera?style=for-the-badge)](https://crates.io/crates/etcetera/reverse_dependencies) [![documentation](https://img.shields.io/docsrs/etcetera?style=for-the-badge)](https://docs.rs/etcetera) ![license](https://img.shields.io/crates/l/etcetera?style=for-the-badge) # Etcetera This is a Rust library that allows you to determine the locations of configuration, data, cache & other files for your application. Existing Rust libraries generally do not give you a choice in terms of which standards/conventions they follow. Etcetera, on the other hand, gives you the choice. ## Conventions Etcetera supports the following conventions: - the [XDG base directory](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) - Apple's [Standard Directories](https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html) - Window's [Known Folder Locations](https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid) - the "Unix Single-folder Convention" i.e. everything in `~/.myapp` ## Strategies Etcetera has 2 modes of operation: `BaseStrategy` & `AppStrategy`: - With `BaseStrategy`, you just get the location of the respective directory. For eg. for `config_dir()`: - XDG: `~/.config` - Apple: `~/Library/Preferences` - Windows: `~\AppData\Roaming` - With `AppStrategy`, you provide additional information to get the location of your app directory. For eg. if you provide the following details: `{ top_level_domain: "org", author: "Acme Corp", app_name: "Frobnicator Plus" }`, you'll get: - XDG: `~/.config/frobnicator-plus` - Unix: `~/.frobnicator-plus` - Apple: `~/Library/Preferences/org.acmecorp.FrobnicatorPlus` - Windows: `~\AppData\Roaming\Acme Corp\Frobnicator Plus` Note: the location of the home (~) is determined by the [`home`](https://docs.rs/home/0.5.4/home/fn.home_dir.html) crate. ### Convenience functions Etcetera also provides convenience functions for selecting the appropriate strategy on each platform: - `base_strategy::choose_base_strategy` & `app_strategy::choose_app_strategy`: Uses `Windows` on Windows & `XDG` everywhere else. This is used by most CLI tools & some GUI tools on each platform. - `base_strategy::choose_native_strategy` & `app_strategy::choose_native_strategy`: Uses `Windows` on Windows, `Apple` on macOS/iOS, & `XDG` everywhere else. This is used by most GUI applications on each platform. ## See the ![documentation](https://docs.rs/etcetera) for examples. etcetera-0.8.0/src/app_strategy/apple.rs000064400000000000000000000052101046102023000163630ustar 00000000000000use crate::base_strategy; use crate::base_strategy::BaseStrategy; use std::path::{Path, PathBuf}; /// This is the strategy created by Apple for use on macOS and iOS devices. It is always used by GUI apps on macOS, and is sometimes used by command-line applications there too. iOS only has GUIs, so all iOS applications follow this strategy. The specification is available [here](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW1). /// /// ``` /// use etcetera::app_strategy::AppStrategy; /// use etcetera::app_strategy::AppStrategyArgs; /// use etcetera::app_strategy::Apple; /// use std::path::Path; /// /// let app_strategy = Apple::new(AppStrategyArgs { /// top_level_domain: "org".to_string(), /// author: "Acme Corp".to_string(), /// app_name: "Frobnicator Plus".to_string(), /// }).unwrap(); /// /// let home_dir = etcetera::home_dir().unwrap(); /// /// assert_eq!( /// app_strategy.home_dir(), /// &home_dir /// ); /// assert_eq!( /// app_strategy.config_dir().strip_prefix(&home_dir), /// Ok(Path::new("Library/Preferences/org.acmecorp.FrobnicatorPlus/")) /// ); /// assert_eq!( /// app_strategy.data_dir().strip_prefix(&home_dir), /// Ok(Path::new("Library/Application Support/org.acmecorp.FrobnicatorPlus/")) /// ); /// assert_eq!( /// app_strategy.cache_dir().strip_prefix(&home_dir), /// Ok(Path::new("Library/Caches/org.acmecorp.FrobnicatorPlus/")) /// ); /// assert_eq!( /// app_strategy.state_dir(), /// None /// ); /// assert_eq!( /// app_strategy.runtime_dir(), /// None /// ); /// ``` #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Apple { base_strategy: base_strategy::Apple, bundle_id: String, } impl super::AppStrategy for Apple { type CreationError = crate::HomeDirError; fn new(args: super::AppStrategyArgs) -> Result { Ok(Self { base_strategy: base_strategy::Apple::new()?, bundle_id: args.bundle_id().replace(' ', ""), }) } fn home_dir(&self) -> &Path { self.base_strategy.home_dir() } fn config_dir(&self) -> PathBuf { self.base_strategy.config_dir().join(&self.bundle_id) } fn data_dir(&self) -> PathBuf { self.base_strategy.data_dir().join(&self.bundle_id) } fn cache_dir(&self) -> PathBuf { self.base_strategy.cache_dir().join(&self.bundle_id) } fn state_dir(&self) -> Option { None } fn runtime_dir(&self) -> Option { None } } etcetera-0.8.0/src/app_strategy/unix.rs000064400000000000000000000047741046102023000162630ustar 00000000000000use std::path::{Path, PathBuf}; /// This strategy has no standard or official specification. It has arisen over time through hundreds of Unixy tools. Vim and Cargo are notable examples whose configuration/data/cache directory layouts are similar to those created by this strategy. /// /// ``` /// use etcetera::app_strategy::AppStrategy; /// use etcetera::app_strategy::AppStrategyArgs; /// use etcetera::app_strategy::Unix; /// use std::path::Path; /// /// let app_strategy = Unix::new(AppStrategyArgs { /// top_level_domain: "org".to_string(), /// author: "Acme Corp".to_string(), /// app_name: "Frobnicator Plus".to_string(), /// }).unwrap(); /// /// let home_dir = etcetera::home_dir().unwrap(); /// /// assert_eq!( /// app_strategy.home_dir(), /// &home_dir /// ); /// assert_eq!( /// app_strategy.config_dir().strip_prefix(&home_dir), /// Ok(Path::new(".frobnicator-plus/")) /// ); /// assert_eq!( /// app_strategy.data_dir().strip_prefix(&home_dir), /// Ok(Path::new(".frobnicator-plus/data/")) /// ); /// assert_eq!( /// app_strategy.cache_dir().strip_prefix(&home_dir), /// Ok(Path::new(".frobnicator-plus/cache/")) /// ); /// assert_eq!( /// app_strategy.state_dir().unwrap().strip_prefix(&home_dir), /// Ok(Path::new(".frobnicator-plus/state/")) /// ); /// assert_eq!( /// app_strategy.runtime_dir().unwrap().strip_prefix(&home_dir), /// Ok(Path::new(".frobnicator-plus/runtime/")) /// ); /// ``` #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Unix { // This is `.frobnicator-plus` in the above example. home_dir: PathBuf, unixy_name: String, } impl super::AppStrategy for Unix { type CreationError = crate::HomeDirError; fn new(args: super::AppStrategyArgs) -> Result { Ok(Self { home_dir: crate::home_dir()?, unixy_name: format!(".{}", args.unixy_name()), }) } fn home_dir(&self) -> &Path { &self.home_dir } fn config_dir(&self) -> PathBuf { self.home_dir.join(&self.unixy_name) } fn data_dir(&self) -> PathBuf { self.home_dir.join(&self.unixy_name).join("data/") } fn cache_dir(&self) -> PathBuf { self.home_dir.join(&self.unixy_name).join("cache/") } fn state_dir(&self) -> Option { Some(self.home_dir.join(&self.unixy_name).join("state/")) } fn runtime_dir(&self) -> Option { Some(self.home_dir.join(&self.unixy_name).join("runtime/")) } } etcetera-0.8.0/src/app_strategy/windows.rs000064400000000000000000000113041046102023000167550ustar 00000000000000use crate::base_strategy; use crate::base_strategy::BaseStrategy; use std::path::{Path, PathBuf}; /// This strategy follows Windows’ conventions. It seems that all Windows GUI apps, and some command-line ones follow this pattern. The specification is available [here](https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid). /// /// This initial example removes all the relevant environment variables to show the strategy’s use of the: /// - (on Windows) SHGetFolderPathW API. /// - (on non-Windows) Windows default directories. /// /// ``` /// use etcetera::app_strategy::AppStrategy; /// use etcetera::app_strategy::AppStrategyArgs; /// use etcetera::app_strategy::Windows; /// use std::path::Path; /// /// // Remove the environment variables that the strategy reads from. /// std::env::remove_var("USERPROFILE"); /// std::env::remove_var("APPDATA"); /// std::env::remove_var("LOCALAPPDATA"); /// /// let app_strategy = Windows::new(AppStrategyArgs { /// top_level_domain: "org".to_string(), /// author: "Acme Corp".to_string(), /// app_name: "Frobnicator Plus".to_string(), /// }).unwrap(); /// /// let home_dir = etcetera::home_dir().unwrap(); /// /// assert_eq!( /// app_strategy.home_dir(), /// &home_dir /// ); /// assert_eq!( /// app_strategy.config_dir().strip_prefix(&home_dir), /// Ok(Path::new("AppData/Roaming/Acme Corp/Frobnicator Plus/config")) /// ); /// assert_eq!( /// app_strategy.data_dir().strip_prefix(&home_dir), /// Ok(Path::new("AppData/Roaming/Acme Corp/Frobnicator Plus/data")) /// ); /// assert_eq!( /// app_strategy.cache_dir().strip_prefix(&home_dir), /// Ok(Path::new("AppData/Local/Acme Corp/Frobnicator Plus/cache")) /// ); /// assert_eq!( /// app_strategy.state_dir(), /// None /// ); /// assert_eq!( /// app_strategy.runtime_dir(), /// None /// ); /// ``` /// /// This next example gives the environment variables values: /// /// ``` /// use etcetera::app_strategy::AppStrategy; /// use etcetera::app_strategy::AppStrategyArgs; /// use etcetera::app_strategy::Windows; /// use std::path::Path; /// /// let home_path = if cfg!(windows) { /// "C:\\my_home_location\\".to_string() /// } else { /// etcetera::home_dir().unwrap().to_string_lossy().to_string() /// }; /// let data_path = if cfg!(windows) { /// "C:\\my_data_location\\" /// } else { /// "/my_data_location/" /// }; /// let cache_path = if cfg!(windows) { /// "C:\\my_cache_location\\" /// } else { /// "/my_cache_location/" /// }; /// /// std::env::set_var("USERPROFILE", &home_path); /// std::env::set_var("APPDATA", data_path); /// std::env::set_var("LOCALAPPDATA", cache_path); /// /// let app_strategy = Windows::new(AppStrategyArgs { /// top_level_domain: "org".to_string(), /// author: "Acme Corp".to_string(), /// app_name: "Frobnicator Plus".to_string(), /// }).unwrap(); /// /// assert_eq!( /// app_strategy.home_dir(), /// Path::new(&home_path) /// ); /// assert_eq!( /// app_strategy.config_dir(), /// Path::new(&format!("{}/Acme Corp/Frobnicator Plus/config", data_path)) /// ); /// assert_eq!( /// app_strategy.data_dir(), /// Path::new(&format!("{}/Acme Corp/Frobnicator Plus/data", data_path)) /// ); /// assert_eq!( /// app_strategy.cache_dir(), /// Path::new(&format!("{}/Acme Corp/Frobnicator Plus/cache", cache_path)) /// ); /// assert_eq!( /// app_strategy.state_dir(), /// None /// ); /// assert_eq!( /// app_strategy.runtime_dir(), /// None /// ); /// ``` #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Windows { base_strategy: base_strategy::Windows, author_app_name_path: PathBuf, } macro_rules! dir_method { ($self: ident, $base_strategy_method: ident, $subfolder_name: expr) => {{ let mut path = $self.base_strategy.$base_strategy_method(); path.push(&$self.author_app_name_path); path.push($subfolder_name); path }}; } impl super::AppStrategy for Windows { type CreationError = crate::HomeDirError; fn new(args: super::AppStrategyArgs) -> Result { Ok(Self { base_strategy: base_strategy::Windows::new()?, author_app_name_path: PathBuf::from(args.author).join(args.app_name), }) } fn home_dir(&self) -> &Path { self.base_strategy.home_dir() } fn config_dir(&self) -> PathBuf { dir_method!(self, config_dir, "config") } fn data_dir(&self) -> PathBuf { dir_method!(self, data_dir, "data") } fn cache_dir(&self) -> PathBuf { dir_method!(self, cache_dir, "cache") } fn state_dir(&self) -> Option { None } fn runtime_dir(&self) -> Option { None } } etcetera-0.8.0/src/app_strategy/xdg.rs000064400000000000000000000152661046102023000160600ustar 00000000000000use crate::base_strategy; use crate::base_strategy::BaseStrategy; use std::path::{Path, PathBuf}; /// This strategy implements the [XDG Base Directories Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html). It is the most common on Linux, but is increasingly being adopted elsewhere. /// /// This initial example removes all the XDG environment variables to show the strategy’s use of the XDG default directories. /// /// ``` /// use etcetera::app_strategy::AppStrategy; /// use etcetera::app_strategy::AppStrategyArgs; /// use etcetera::app_strategy::Xdg; /// use std::path::Path; /// /// // Remove the environment variables that the strategy reads from. /// std::env::remove_var("XDG_CONFIG_HOME"); /// std::env::remove_var("XDG_DATA_HOME"); /// std::env::remove_var("XDG_CACHE_HOME"); /// std::env::remove_var("XDG_STATE_HOME"); /// std::env::remove_var("XDG_RUNTIME_DIR"); /// /// let app_strategy = Xdg::new(AppStrategyArgs { /// top_level_domain: "org".to_string(), /// author: "Acme Corp".to_string(), /// app_name: "Frobnicator Plus".to_string(), /// }).unwrap(); /// /// let home_dir = etcetera::home_dir().unwrap(); /// /// assert_eq!( /// app_strategy.home_dir(), /// &home_dir /// ); /// assert_eq!( /// app_strategy.config_dir().strip_prefix(&home_dir), /// Ok(Path::new(".config/frobnicator-plus/")) /// ); /// assert_eq!( /// app_strategy.data_dir().strip_prefix(&home_dir), /// Ok(Path::new(".local/share/frobnicator-plus/")) /// ); /// assert_eq!( /// app_strategy.cache_dir().strip_prefix(&home_dir), /// Ok(Path::new(".cache/frobnicator-plus/")) /// ); /// assert_eq!( /// app_strategy.state_dir().unwrap().strip_prefix(&home_dir), /// Ok(Path::new(".local/state/frobnicator-plus/")) /// ); /// assert_eq!( /// app_strategy.runtime_dir(), /// None /// ); /// ``` /// /// This next example gives the environment variables values: /// /// ``` /// use etcetera::app_strategy::AppStrategy; /// use etcetera::app_strategy::AppStrategyArgs; /// use etcetera::app_strategy::Xdg; /// use std::path::Path; /// /// // We need to conditionally set these to ensure that they are absolute paths both on Windows and other systems. /// let config_path = if cfg!(windows) { /// "C:\\my_config_location\\" /// } else { /// "/my_config_location/" /// }; /// let data_path = if cfg!(windows) { /// "C:\\my_data_location\\" /// } else { /// "/my_data_location/" /// }; /// let cache_path = if cfg!(windows) { /// "C:\\my_cache_location\\" /// } else { /// "/my_cache_location/" /// }; /// let state_path = if cfg!(windows) { /// "C:\\my_state_location\\" /// } else { /// "/my_state_location/" /// }; /// let runtime_path = if cfg!(windows) { /// "C:\\my_runtime_location\\" /// } else { /// "/my_runtime_location/" /// }; /// /// std::env::set_var("XDG_CONFIG_HOME", config_path); /// std::env::set_var("XDG_DATA_HOME", data_path); /// std::env::set_var("XDG_CACHE_HOME", cache_path); /// std::env::set_var("XDG_STATE_HOME", state_path); /// std::env::set_var("XDG_RUNTIME_DIR", runtime_path); /// /// let app_strategy = Xdg::new(AppStrategyArgs { /// top_level_domain: "org".to_string(), /// author: "Acme Corp".to_string(), /// app_name: "Frobnicator Plus".to_string(), /// }).unwrap(); /// /// assert_eq!( /// app_strategy.config_dir(), /// Path::new(&format!("{}/frobnicator-plus/", config_path)) /// ); /// assert_eq!( /// app_strategy.data_dir(), /// Path::new(&format!("{}/frobnicator-plus/", data_path)) /// ); /// assert_eq!( /// app_strategy.cache_dir(), /// Path::new(&format!("{}/frobnicator-plus/", cache_path)) /// ); /// assert_eq!( /// app_strategy.state_dir().unwrap(), /// Path::new(&format!("{}/frobnicator-plus/", state_path)) /// ); /// assert_eq!( /// app_strategy.runtime_dir().unwrap(), /// Path::new(&format!("{}/frobnicator-plus/", runtime_path)) /// ); /// ``` /// /// The XDG spec requires that when the environment variables’ values are not absolute paths, their values should be ignored. This example exemplifies this behaviour: /// /// ``` /// use etcetera::app_strategy::AppStrategy; /// use etcetera::app_strategy::AppStrategyArgs; /// use etcetera::app_strategy::Xdg; /// use std::path::Path; /// /// // Remove the environment variables that the strategy reads from. /// std::env::set_var("XDG_CONFIG_HOME", "relative_path/"); /// std::env::set_var("XDG_DATA_HOME", "./another_one/"); /// std::env::set_var("XDG_CACHE_HOME", "yet_another/"); /// std::env::set_var("XDG_STATE_HOME", "./and_another"); /// std::env::set_var("XDG_RUNTIME_DIR", "relative_path/"); /// /// let app_strategy = Xdg::new(AppStrategyArgs { /// top_level_domain: "org".to_string(), /// author: "Acme Corp".to_string(), /// app_name: "Frobnicator Plus".to_string(), /// }).unwrap(); /// /// let home_dir = etcetera::home_dir().unwrap(); /// /// // We still get the default values. /// assert_eq!( /// app_strategy.config_dir().strip_prefix(&home_dir), /// Ok(Path::new(".config/frobnicator-plus/")) /// ); /// assert_eq!( /// app_strategy.data_dir().strip_prefix(&home_dir), /// Ok(Path::new(".local/share/frobnicator-plus/")) /// ); /// assert_eq!( /// app_strategy.cache_dir().strip_prefix(&home_dir), /// Ok(Path::new(".cache/frobnicator-plus/")) /// ); /// assert_eq!( /// app_strategy.state_dir().unwrap().strip_prefix(&home_dir), /// Ok(Path::new(".local/state/frobnicator-plus/")) /// ); /// assert_eq!( /// app_strategy.runtime_dir(), /// None /// ); /// ``` #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Xdg { base_strategy: base_strategy::Xdg, unixy_name: String, } impl super::AppStrategy for Xdg { type CreationError = crate::HomeDirError; fn new(args: super::AppStrategyArgs) -> Result { Ok(Self { base_strategy: base_strategy::Xdg::new()?, unixy_name: args.unixy_name(), }) } fn home_dir(&self) -> &Path { self.base_strategy.home_dir() } fn config_dir(&self) -> PathBuf { self.base_strategy.config_dir().join(&self.unixy_name) } fn data_dir(&self) -> PathBuf { self.base_strategy.data_dir().join(&self.unixy_name) } fn cache_dir(&self) -> PathBuf { self.base_strategy.cache_dir().join(&self.unixy_name) } fn state_dir(&self) -> Option { Some( self.base_strategy .state_dir() .unwrap() .join(&self.unixy_name), ) } fn runtime_dir(&self) -> Option { self.base_strategy .runtime_dir() .map(|runtime_dir| runtime_dir.join(&self.unixy_name)) } } etcetera-0.8.0/src/app_strategy.rs000064400000000000000000000141401046102023000152640ustar 00000000000000//! These strategies require you to provide some information on your application, and they will in turn locate the configuration/data/cache directory specifically for your application. use std::ffi::OsStr; use std::path::Path; use std::path::PathBuf; /// The arguments to the creator method of an [`AppStrategy`](trait.AppStrategy.html). #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct AppStrategyArgs { /// The top level domain of the application, e.g. `com`, `org`, or `io.github`. pub top_level_domain: String, /// The name of the author of the application. pub author: String, /// The application’s name. This should be capitalised if appropriate. pub app_name: String, } impl AppStrategyArgs { /// Constructs a bunde identifier from an `AppStrategyArgs`. /// /// ``` /// use etcetera::app_strategy::AppStrategyArgs; /// /// let strategy_args = AppStrategyArgs { /// top_level_domain: "org".to_string(), /// author: "Acme Corp".to_string(), /// app_name: "Frobnicator Plus".to_string(), /// }; /// /// assert_eq!(strategy_args.bundle_id().replace(' ', ""), "org.acmecorp.FrobnicatorPlus".to_string()); /// ``` pub fn bundle_id(&self) -> String { format!( "{}.{}.{}", self.top_level_domain, self.author.to_lowercase(), self.app_name ) } /// Returns a ‘unixy’ version of the application’s name, akin to what would usually be used as a binary name. /// /// ``` /// use etcetera::app_strategy::AppStrategyArgs; /// /// let strategy_args = AppStrategyArgs { /// top_level_domain: "org".to_string(), /// author: "Acme Corp".to_string(), /// app_name: "Frobnicator Plus".to_string(), /// }; /// /// assert_eq!(strategy_args.unixy_name(), "frobnicator-plus".to_string()); /// ``` pub fn unixy_name(&self) -> String { self.app_name.to_lowercase().replace(' ', "-") } } macro_rules! in_dir_method { ($self: ident, $path_extra: expr, $dir_method_name: ident) => {{ let mut path = $self.$dir_method_name(); path.push(Path::new(&$path_extra)); path }}; (opt: $self: ident, $path_extra: expr, $dir_method_name: ident) => {{ let mut path = $self.$dir_method_name()?; path.push(Path::new(&$path_extra)); Some(path) }}; } /// Allows applications to retrieve the paths of configuration, data, and cache directories specifically for them. pub trait AppStrategy: Sized { /// The error type returned by `new`. type CreationError: std::error::Error; /// The constructor requires access to some basic information about your application. fn new(args: AppStrategyArgs) -> Result; /// Gets the home directory of the current user. fn home_dir(&self) -> &Path; /// Gets the configuration directory for your application. fn config_dir(&self) -> PathBuf; /// Gets the data directory for your application. fn data_dir(&self) -> PathBuf; /// Gets the cache directory for your application. fn cache_dir(&self) -> PathBuf; /// Gets the state directory for your application. /// State directory may not to exist for all conventions. fn state_dir(&self) -> Option; /// Gets the runtime directory for your application. /// Runtime directory may not to exist for all conventions. fn runtime_dir(&self) -> Option; /// Constructs a path inside your application’s configuration directory to which a path of your choice has been appended. fn in_config_dir>(&self, path: P) -> PathBuf { in_dir_method!(self, path, config_dir) } /// Constructs a path inside your application’s data directory to which a path of your choice has been appended. fn in_data_dir>(&self, path: P) -> PathBuf { in_dir_method!(self, path, data_dir) } /// Constructs a path inside your application’s cache directory to which a path of your choice has been appended. fn in_cache_dir>(&self, path: P) -> PathBuf { in_dir_method!(self, path, cache_dir) } /// Constructs a path inside your application’s state directory to which a path of your choice has been appended. fn in_state_dir>(&self, path: P) -> Option { in_dir_method!(opt: self, path, state_dir) } /// Constructs a path inside your application’s runtime directory to which a path of your choice has been appended. fn in_runtime_dir>(&self, path: P) -> Option { in_dir_method!(opt: self, path, runtime_dir) } } macro_rules! create_strategies { ($native: ty, $app: ty) => { /// Returns the current OS’s native [`AppStrategy`](trait.AppStrategy.html). /// This uses the [`Windows`](struct.Windows.html) strategy on Windows, [`Apple`](struct.Apple.html) on macOS & iOS, and [`Xdg`](struct.Xdg.html) everywhere else. /// This is the convention used by most GUI applications. pub fn choose_native_strategy( args: AppStrategyArgs, ) -> Result<$native, <$native as AppStrategy>::CreationError> { <$native>::new(args) } /// Returns the current OS’s default [`AppStrategy`](trait.AppStrategy.html). /// This uses the [`Windows`](struct.Windows.html) strategy on Windows, and [`Xdg`](struct.Xdg.html) everywhere else. /// This is the convention used by most CLI applications. pub fn choose_app_strategy( args: AppStrategyArgs, ) -> Result<$app, <$app as AppStrategy>::CreationError> { <$app>::new(args) } }; } cfg_if::cfg_if! { if #[cfg(target_os = "windows")] { create_strategies!(Windows, Windows); } else if #[cfg(any(target_os = "macos", target_os = "ios"))] { create_strategies!(Apple, Xdg); } else { create_strategies!(Xdg, Xdg); } } mod apple; mod unix; mod windows; mod xdg; pub use apple::Apple; pub use unix::Unix; pub use windows::Windows; pub use xdg::Xdg; etcetera-0.8.0/src/base_strategy/apple.rs000064400000000000000000000041621046102023000165220ustar 00000000000000use std::path::{Path, PathBuf}; /// This is the strategy created by Apple for use on macOS and iOS devices. It is always used by GUI apps on macOS, and is sometimes used by command-line applications there too. iOS only has GUIs, so all iOS applications follow this strategy. The specification is available [here](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW1). /// /// ``` /// use etcetera::base_strategy::Apple; /// use etcetera::base_strategy::BaseStrategy; /// use std::path::Path; /// /// let base_strategy = Apple::new().unwrap(); /// /// let home_dir = etcetera::home_dir().unwrap(); /// /// assert_eq!( /// base_strategy.home_dir(), /// &home_dir /// ); /// assert_eq!( /// base_strategy.config_dir().strip_prefix(&home_dir), /// Ok(Path::new("Library/Preferences/")) /// ); /// assert_eq!( /// base_strategy.data_dir().strip_prefix(&home_dir), /// Ok(Path::new("Library/Application Support/")) /// ); /// assert_eq!( /// base_strategy.cache_dir().strip_prefix(&home_dir), /// Ok(Path::new("Library/Caches/")) /// ); /// assert_eq!( /// base_strategy.state_dir(), /// None /// ); /// assert_eq!( /// base_strategy.runtime_dir(), /// None /// ); /// ``` #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Apple { home_dir: PathBuf, } impl super::BaseStrategy for Apple { type CreationError = crate::HomeDirError; fn new() -> Result { Ok(Self { home_dir: crate::home_dir()?, }) } fn home_dir(&self) -> &Path { &self.home_dir } fn config_dir(&self) -> PathBuf { self.home_dir.join("Library/Preferences/") } fn data_dir(&self) -> PathBuf { self.home_dir.join("Library/Application Support/") } fn cache_dir(&self) -> PathBuf { self.home_dir.join("Library/Caches/") } fn state_dir(&self) -> Option { None } fn runtime_dir(&self) -> Option { None } } etcetera-0.8.0/src/base_strategy/windows.rs000064400000000000000000000121201046102023000171040ustar 00000000000000use std::path::{Path, PathBuf}; /// This strategy follows Windows’ conventions. It seems that all Windows GUI apps, and some command-line ones follow this pattern. The specification is available [here](https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid). /// /// This initial example removes all the relevant environment variables to show the strategy’s use of the: /// - (on Windows) SHGetFolderPathW API. /// - (on non-Windows) Windows default directories. /// /// ``` /// use etcetera::base_strategy::BaseStrategy; /// use etcetera::base_strategy::Windows; /// use std::path::Path; /// /// // Remove the environment variables that the strategy reads from. /// std::env::remove_var("USERPROFILE"); /// std::env::remove_var("APPDATA"); /// std::env::remove_var("LOCALAPPDATA"); /// /// let base_strategy = Windows::new().unwrap(); /// /// let home_dir = etcetera::home_dir().unwrap(); /// /// assert_eq!( /// base_strategy.home_dir(), /// &home_dir /// ); /// assert_eq!( /// base_strategy.config_dir().strip_prefix(&home_dir), /// Ok(Path::new("AppData/Roaming/")) /// ); /// assert_eq!( /// base_strategy.data_dir().strip_prefix(&home_dir), /// Ok(Path::new("AppData/Roaming/")) /// ); /// assert_eq!( /// base_strategy.cache_dir().strip_prefix(&home_dir), /// Ok(Path::new("AppData/Local/")) /// ); /// assert_eq!( /// base_strategy.state_dir(), /// None /// ); /// assert_eq!( /// base_strategy.runtime_dir(), /// None /// ); /// ``` /// /// This next example gives the environment variables values: /// /// ``` /// use etcetera::base_strategy::BaseStrategy; /// use etcetera::base_strategy::Windows; /// use std::path::Path; /// /// let home_path = if cfg!(windows) { /// "C:\\foo\\".to_string() /// } else { /// etcetera::home_dir().unwrap().to_string_lossy().to_string() /// }; /// let data_path = if cfg!(windows) { /// "C:\\bar\\" /// } else { /// "/bar/" /// }; /// let cache_path = if cfg!(windows) { /// "C:\\baz\\" /// } else { /// "/baz/" /// }; /// /// std::env::set_var("USERPROFILE", &home_path); /// std::env::set_var("APPDATA", data_path); /// std::env::set_var("LOCALAPPDATA", cache_path); /// /// let base_strategy = Windows::new().unwrap(); /// /// assert_eq!( /// base_strategy.home_dir(), /// Path::new(&home_path) /// ); /// assert_eq!( /// base_strategy.config_dir(), /// Path::new(data_path) /// ); /// assert_eq!( /// base_strategy.data_dir(), /// Path::new(data_path) /// ); /// assert_eq!( /// base_strategy.cache_dir(), /// Path::new(cache_path) /// ); /// assert_eq!( /// base_strategy.state_dir(), /// None /// ); /// assert_eq!( /// base_strategy.runtime_dir(), /// None /// ); /// ``` #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Windows { home_dir: PathBuf, } // Ref: https://github.com/rust-lang/cargo/blob/home-0.5.5/crates/home/src/windows.rs // We should keep this code in sync with the above. impl Windows { fn dir_inner(env: &'static str) -> Option { std::env::var_os(env) .filter(|s| !s.is_empty()) .map(PathBuf::from) .or_else(|| Self::dir_crt(env)) } #[cfg(all(windows, target_vendor = "uwp"))] fn dir_crt(env: &'static str) -> Option { use std::ffi::OsString; use std::os::windows::ffi::OsStringExt; use windows_sys::Win32::Foundation::{MAX_PATH, S_OK}; use windows_sys::Win32::UI::Shell::{SHGetFolderPathW, CSIDL_APPDATA, CSIDL_LOCAL_APPDATA}; let csidl = match env { "APPDATA" => CSIDL_APPDATA, "LOCALAPPDATA" => CSIDL_LOCAL_APPDATA, _ => return None, }; extern "C" { fn wcslen(buf: *const u16) -> usize; } unsafe { let mut path: Vec = Vec::with_capacity(MAX_PATH as usize); match SHGetFolderPathW(0, csidl, 0, 0, path.as_mut_ptr()) { S_OK => { let len = wcslen(path.as_ptr()); path.set_len(len); let s = OsString::from_wide(&path); Some(PathBuf::from(s)) } _ => None, } } } #[cfg(not(all(windows, target_vendor = "uwp")))] fn dir_crt(_env: &'static str) -> Option { None } } impl super::BaseStrategy for Windows { type CreationError = crate::HomeDirError; fn new() -> Result { Ok(Self { home_dir: crate::home_dir()?, }) } fn home_dir(&self) -> &Path { &self.home_dir } fn config_dir(&self) -> PathBuf { self.data_dir() } fn data_dir(&self) -> PathBuf { Self::dir_inner("APPDATA").unwrap_or_else(|| self.home_dir.join("AppData").join("Roaming")) } fn cache_dir(&self) -> PathBuf { Self::dir_inner("LOCALAPPDATA") .unwrap_or_else(|| self.home_dir.join("AppData").join("Local")) } fn state_dir(&self) -> Option { None } fn runtime_dir(&self) -> Option { None } } etcetera-0.8.0/src/base_strategy/xdg.rs000064400000000000000000000135611046102023000162060ustar 00000000000000use std::path::Path; use std::path::PathBuf; /// This strategy implements the [XDG Base Directories Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html). It is the most common on Linux, but is increasingly being adopted elsewhere. /// /// This initial example removes all the XDG environment variables to show the strategy’s use of the XDG default directories. /// /// ``` /// use etcetera::base_strategy::BaseStrategy; /// use etcetera::base_strategy::Xdg; /// use std::path::Path; /// /// // Remove the environment variables that the strategy reads from. /// std::env::remove_var("XDG_CONFIG_HOME"); /// std::env::remove_var("XDG_DATA_HOME"); /// std::env::remove_var("XDG_CACHE_HOME"); /// std::env::remove_var("XDG_STATE_HOME"); /// std::env::remove_var("XDG_RUNTIME_DIR"); /// /// let base_strategy = Xdg::new().unwrap(); /// /// let home_dir = etcetera::home_dir().unwrap(); /// /// assert_eq!( /// base_strategy.home_dir(), /// &home_dir /// ); /// assert_eq!( /// base_strategy.config_dir().strip_prefix(&home_dir), /// Ok(Path::new(".config/")) /// ); /// assert_eq!( /// base_strategy.data_dir().strip_prefix(&home_dir), /// Ok(Path::new(".local/share/")) /// ); /// assert_eq!( /// base_strategy.cache_dir().strip_prefix(&home_dir), /// Ok(Path::new(".cache/")) /// ); /// assert_eq!( /// base_strategy.state_dir().unwrap().strip_prefix(&home_dir), /// Ok(Path::new(".local/state")) /// ); /// assert_eq!( /// base_strategy.runtime_dir(), /// None /// ); /// ``` /// /// This next example gives the environment variables values: /// /// ``` /// use etcetera::base_strategy::BaseStrategy; /// use etcetera::base_strategy::Xdg; /// use std::path::Path; /// /// // We need to conditionally set these to ensure that they are absolute paths both on Windows and other systems. /// let config_path = if cfg!(windows) { /// "C:\\foo\\" /// } else { /// "/foo/" /// }; /// let data_path = if cfg!(windows) { /// "C:\\bar\\" /// } else { /// "/bar/" /// }; /// let cache_path = if cfg!(windows) { /// "C:\\baz\\" /// } else { /// "/baz/" /// }; /// let state_path = if cfg!(windows) { /// "C:\\foobar\\" /// } else { /// "/foobar/" /// }; /// let runtime_path = if cfg!(windows) { /// "C:\\qux\\" /// } else { /// "/qux/" /// }; /// /// std::env::set_var("XDG_CONFIG_HOME", config_path); /// std::env::set_var("XDG_DATA_HOME", data_path); /// std::env::set_var("XDG_CACHE_HOME", cache_path); /// std::env::set_var("XDG_STATE_HOME", state_path); /// std::env::set_var("XDG_RUNTIME_DIR", runtime_path); /// /// let base_strategy = Xdg::new().unwrap(); /// /// assert_eq!( /// base_strategy.config_dir(), /// Path::new(config_path) /// ); /// assert_eq!( /// base_strategy.data_dir(), /// Path::new(data_path) /// ); /// assert_eq!( /// base_strategy.cache_dir(), /// Path::new(cache_path) /// ); /// assert_eq!( /// base_strategy.state_dir().unwrap(), /// Path::new(state_path) /// ); /// assert_eq!( /// base_strategy.runtime_dir().unwrap(), /// Path::new(runtime_path) /// ); /// ``` /// /// The XDG spec requires that when the environment variables’ values are not absolute paths, their values should be ignored. This example exemplifies this behaviour: /// /// ``` /// use etcetera::base_strategy::BaseStrategy; /// use etcetera::base_strategy::Xdg; /// use std::path::Path; /// /// // Remove the environment variables that the strategy reads from. /// std::env::set_var("XDG_CONFIG_HOME", "foo/"); /// std::env::set_var("XDG_DATA_HOME", "bar/"); /// std::env::set_var("XDG_CACHE_HOME", "baz/"); /// std::env::set_var("XDG_STATE_HOME", "foobar/"); /// std::env::set_var("XDG_RUNTIME_DIR", "qux/"); /// /// let base_strategy = Xdg::new().unwrap(); /// /// let home_dir = etcetera::home_dir().unwrap(); /// /// // We still get the default values. /// assert_eq!( /// base_strategy.config_dir().strip_prefix(&home_dir), /// Ok(Path::new(".config/")) /// ); /// assert_eq!( /// base_strategy.data_dir().strip_prefix(&home_dir), /// Ok(Path::new(".local/share/")) /// ); /// assert_eq!( /// base_strategy.cache_dir().strip_prefix(&home_dir), /// Ok(Path::new(".cache/")) /// ); /// assert_eq!( /// base_strategy.state_dir().unwrap().strip_prefix(&home_dir), /// Ok(Path::new(".local/state/")) /// ); /// assert_eq!( /// base_strategy.runtime_dir(), /// None /// ); /// ``` #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Xdg { home_dir: PathBuf, } impl Xdg { fn env_var_or_none(env_var: &str) -> Option { std::env::var(env_var).ok().and_then(|path| { let path = PathBuf::from(path); // Return None if the path obtained from the environment variable isn’t absolute. if path.is_absolute() { Some(path) } else { None } }) } fn env_var_or_default(&self, env_var: &str, default: impl AsRef) -> PathBuf { Self::env_var_or_none(env_var).unwrap_or_else(|| self.home_dir.join(default)) } } impl super::BaseStrategy for Xdg { type CreationError = crate::HomeDirError; fn new() -> Result { Ok(Self { home_dir: crate::home_dir()?, }) } fn home_dir(&self) -> &Path { &self.home_dir } fn config_dir(&self) -> PathBuf { self.env_var_or_default("XDG_CONFIG_HOME", ".config/") } fn data_dir(&self) -> PathBuf { self.env_var_or_default("XDG_DATA_HOME", ".local/share/") } fn cache_dir(&self) -> PathBuf { self.env_var_or_default("XDG_CACHE_HOME", ".cache/") } fn state_dir(&self) -> Option { Some(self.env_var_or_default("XDG_STATE_HOME", ".local/state/")) } fn runtime_dir(&self) -> Option { Self::env_var_or_none("XDG_RUNTIME_DIR") } } etcetera-0.8.0/src/base_strategy.rs000064400000000000000000000046611046102023000154250ustar 00000000000000//! These strategies simply provide the user’s configuration, data, and cache directories, without knowing about the application specifically. use std::path::{Path, PathBuf}; /// Provides configuration, data, and cache directories of the current user. pub trait BaseStrategy: Sized { /// The error type returned by `new`. type CreationError: std::error::Error; /// Base strategies are constructed without knowledge of the application. fn new() -> Result; /// Gets the home directory of the current user. fn home_dir(&self) -> &Path; /// Gets the user’s configuration directory. fn config_dir(&self) -> PathBuf; /// Gets the user’s data directory. fn data_dir(&self) -> PathBuf; /// Gets the user’s cache directory. fn cache_dir(&self) -> PathBuf; /// Gets the user’s state directory. /// State directory may not exist for all conventions. fn state_dir(&self) -> Option; /// Gets the user’s runtime directory. /// Runtime directory may not exist for all conventions. fn runtime_dir(&self) -> Option; } macro_rules! create_strategies { ($native: ty, $base: ty) => { /// Returns the current OS’s native [`BaseStrategy`](trait.BaseStrategy.html). /// This uses the [`Windows`](struct.Windows.html) strategy on Windows, [`Apple`](struct.Apple.html) on macOS & iOS, and [`Xdg`](struct.Xdg.html) everywhere else. /// This is the convention used by most GUI applications. pub fn choose_native_strategy() -> Result<$native, <$native as BaseStrategy>::CreationError> { <$native>::new() } /// Returns the current OS’s default [`BaseStrategy`](trait.BaseStrategy.html). /// This uses the [`Windows`](struct.Windows.html) strategy on Windows, and [`Xdg`](struct.Xdg.html) everywhere else. /// This is the convention used by most CLI applications. pub fn choose_base_strategy() -> Result<$base, <$base as BaseStrategy>::CreationError> { <$base>::new() } }; } cfg_if::cfg_if! { if #[cfg(target_os = "windows")] { create_strategies!(Windows, Windows); } else if #[cfg(any(target_os = "macos", target_os = "ios"))] { create_strategies!(Apple, Xdg); } else { create_strategies!(Xdg, Xdg); } } mod apple; mod windows; mod xdg; pub use apple::Apple; pub use windows::Windows; pub use xdg::Xdg; etcetera-0.8.0/src/lib.rs000064400000000000000000000137111046102023000133330ustar 00000000000000//! This is a Rust library that allows you to determine the locations of configuration, data, cache & other files for your application. //! Existing Rust libraries generally do not give you a choice in terms of which standards/conventions they follow. //! Etcetera, on the other hand, gives you the choice. //! //! # Conventions //! Etcetera supports the following conventions: //! - the [XDG base directory](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) //! - Apple's [Standard Directories](https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html) //! - Window's [Known Folder Locations](https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid) //! - the "Unix Single-folder Convention" i.e. everything in `~/.myapp` //! //! # Strategies //! If you want to get started quickly, you can use the following convenience functions that use the default strategies (as determined arbitrarily by yours truly) or the native strategies for each OS. //! //! ## BaseStrategy //! If you just want to get the path to a configuration, data, cache or another directory, you can use the `choose_base_strategy` function. //! //! ``` //! use etcetera::{choose_base_strategy, BaseStrategy}; //! //! let strategy = choose_base_strategy().unwrap(); //! //! let config_dir = strategy.config_dir(); //! let data_dir = strategy.data_dir(); //! let cache_dir = strategy.cache_dir(); //! let state_dir = strategy.state_dir(); //! let runtime_dir = strategy.runtime_dir(); //! ``` //! //! ## AppStrategy //! If you want to get the path to a configuration, data, cache or another directory, and you want to follow the naming conventions for your application, you can use the `choose_app_strategy` function. //! //! Let’s take an application created by `Acme Corp` with the name `Frobnicator Plus` and the top-level domain of `jrg` as an example. //! - XDG strategy would place these in `~/.config/frobnicator-plus`. //! - Unix strategy would place these in `~/.frobnicator-plus`. //! - Apple strategy would place these in `~/Library/Preferences/org.acmecorp.FrobnicatorPlus`. //! - Windows strategy would place these in `~\AppData\Roaming\Acme Corp\Frobnicator Plus`. //! //! ``` //! use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs}; //! //! let strategy = choose_app_strategy(AppStrategyArgs { //! top_level_domain: "org".to_string(), //! author: "Acme Corp".to_string(), //! app_name: "Frobnicator Plus".to_string(), //! }).unwrap(); //! //! let config_dir = strategy.config_dir(); //! let data_dir = strategy.data_dir(); //! let cache_dir = strategy.cache_dir(); //! let state_dir = strategy.state_dir(); //! let runtime_dir = strategy.runtime_dir(); //! ``` //! //! ## Native Strategy //! //! `choose_base_strategy()` and `choose_app_strategy()` will use the `XDG` strategy on Linux & macOS, and the `Windows` strategy on Windows. //! This is used by most CLI tools & some GUI tools on each platform. //! //! If you're developing a GUI application, you might want to use the "Standard directories" on macOS by using `choose_native_strategy()` instead. //! Note that if your application expects the user to modify the configuration files, you should still prefer the `XDG` strategy on macOS. //! //! ## Custom Conventions //! //! You aren’t limited to the built-in conventions – you can implement the relevant traits yourself. Please consider contributing these back, as the more preset conventions there are, the better. //! //! # More Examples //! Say you were a hardened Unix veteran, and didn’t want to have any of this XDG nonsense, clutter in the home directory be damned! Instead of using `choose_app_strategy` or `choose_base_strategy`, you can pick a strategy yourself. Here’s an example using the [`Unix`](app_strategy/struct.Unix.html) strategy – see its documentation to see what kind of folder structures it produces: //! //! ``` //! use etcetera::{app_strategy, AppStrategy, AppStrategyArgs}; //! //! let strategy = app_strategy::Unix::new(AppStrategyArgs { //! top_level_domain: "com".to_string(), //! author: "Hardened Unix Veteran Who Likes Short Command Names".to_string(), //! app_name: "wry".to_string(), //! }).unwrap(); //! //! let config_dir = strategy.config_dir(); // produces ~/.wry/ //! // et cetera. //! ``` //! //! Oftentimes the location of a configuration, data or cache directory is needed solely to create a path that starts inside it. For this purpose, [`AppStrategy`](app_strategy/trait.AppStrategy.html) implements a couple of convenience methods for you: //! //! ``` //! use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs}; //! //! let strategy = choose_app_strategy(AppStrategyArgs { //! top_level_domain: "org".to_string(), //! author: "Acme Corp".to_string(), //! app_name: "Frobnicator".to_string(), //! }).unwrap(); //! //! // Path to configuration directory. //! let config_dir = strategy.config_dir(); //! //! // Path to config.toml inside the configuration directory. //! let config_file = strategy.in_config_dir("config.toml"); //! //! assert_eq!(config_dir.join("config.toml"), config_file); //! ``` #![warn(missing_docs, rust_2018_idioms, missing_debug_implementations)] pub mod app_strategy; pub mod base_strategy; pub use app_strategy::{choose_app_strategy, AppStrategy, AppStrategyArgs}; pub use base_strategy::{choose_base_strategy, BaseStrategy}; /// A convenience function that wraps the [`home_dir`](https://docs.rs/home/0.5.4/home/fn.home_dir.html) function from the [home](https://docs.rs/home) crate. pub fn home_dir() -> Result { home::home_dir().ok_or(HomeDirError) } /// This error occurs when the home directory cannot be located. #[derive(Debug)] pub struct HomeDirError; impl std::fmt::Display for HomeDirError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "could not locate home directory") } } impl std::error::Error for HomeDirError {}