Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
{"$comment":"This file only protects against accidental modifications. It is not a security mechanism and does not protect against malicious changes.","files":{".cargo_vcs_info.json":"8ba9a8015744ca123d21cd490cf735f4fb296e238d93b4dc828d80c775db18de",".github/workflows/ci.yaml":"247945544666a5443b18e3f95894cb25ca3c810ecb8fa09a11d2464c57bf9fa5","Cargo.lock":"70e08cf1533520fa1ed1573255eb72de836b510f3ac7660013ac8e7a61ace478","Cargo.toml":"902200a157ef776f6f88e43f082901b3f9a12ab8932614a08d32416da1cd2bd9","Cargo.toml.orig":"4cf03cbe1d48bb5cb7222d0ab36bc61f7daae57a42aa902d94950c04c857f260","LICENSE-APACHE":"62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"b087b479e1eee01be07645b261b2355bb27528dbe1a92e08c3e371324fbd0606","rustfmt.toml":"310f8c45383f31964c61fc9b003d9882f14225e6cffbfb2a91198538c131ef84","src/app_strategy.rs":"5ab1fa1991c63884c641c112e7b027f81192933874dab7c907c73087f8188b4d","src/app_strategy/apple.rs":"6dc44f083ea9217af76d180c8754033148fa274ee1903f533d847e8c488a36e3","src/app_strategy/unix.rs":"7258edb9d6be7434892c1a6df8c719341ed299f05f68f22fae4a107e26c3d028","src/app_strategy/windows.rs":"cabd2cd7814491043de283b76990dc69d9f7bbbef4d2d23c738343a67a11dd01","src/app_strategy/xdg.rs":"4d5121b71013a78b6d9d4443dd26b4be1742dee9bc876b9b4567d68be25a9fc2","src/base_strategy.rs":"bcb3f54d75096bc21f5096692c920f82646f24259a91ff0db1413c4e221e1926","src/base_strategy/apple.rs":"1b36648b8d6a06af5dc993c1202b12ea775c15092233b90d74767a52985d6c40","src/base_strategy/windows.rs":"3f0b683bde830bf5126200493bf68a02ac7e17f9403d9aab4d99fb688b06aed3","src/base_strategy/xdg.rs":"246fb1502377fc99eebab3d2a9380a6ebc4e560a90568851c50faba835174358","src/lib.rs":"b9e9c25bf66fd0052ea7d3f59b59004bb138ecca6ae0c53d4d6257f331f8be66"},"package":"de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "07b3f54999e4f3ff0f027de0144c9349d820ce80"
},
"path_in_vcs": ""
}
+70
View File
@@ -0,0 +1,70 @@
name: CI
on:
pull_request:
push:
branches:
- master
tags:
- "*"
workflow_dispatch:
jobs:
rustfmt:
name: Formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
components: rustfmt
- run: cargo fmt --all --check
outdated:
name: Outdated
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v3
- uses: dtolnay/install@cargo-outdated
- run: cargo outdated --exit-code 1
miri:
name: Miri
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@miri
- run: cargo miri test
env:
MIRIFLAGS: -Zmiri-strict-provenance -Zmiri-disable-isolation
ci:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
toolchain: [beta, stable, 1.87.0]
name: Test on ${{ matrix.os }} with Rust ${{ matrix.toolchain }}
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.toolchain }}
components: clippy
- run: cargo clippy -- -Dwarnings
- run: cargo test
+32
View File
@@ -0,0 +1,32 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "etcetera"
version = "0.11.0"
dependencies = [
"cfg-if",
"windows-sys",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
+51
View File
@@ -0,0 +1,51 @@
# 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 = "2024"
rust-version = "1.87.0"
name = "etcetera"
version = "0.11.0"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
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"
[lib]
name = "etcetera"
path = "src/lib.rs"
[dependencies.cfg-if]
version = "1"
[target."cfg(windows)".dependencies.windows-sys]
version = "0.61"
features = [
"Win32_Foundation",
"Win32_System_Com",
"Win32_UI_Shell",
]
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "etcetera"
version = "0.11.0"
categories = ["config"]
documentation = "https://docs.rs/etcetera"
edition = "2024"
homepage = "https://github.com/lunacookies/etcetera"
keywords = ["xdg", "dirs", "directories", "basedir", "path"]
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/lunacookies/etcetera"
rust-version = "1.87.0"
description = "An unopinionated library for obtaining configuration, data, cache, & other directories"
[dependencies]
cfg-if = "1"
# We should keep this in sync with the `home` crate.
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_System_Com", "Win32_UI_Shell"] }
+176
View File
@@ -0,0 +1,176 @@
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
+23
View File
@@ -0,0 +1,23 @@
Permission 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.
+54
View File
@@ -0,0 +1,54 @@
[![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.
MSRV: 1.87.0
Note: The MSRV was raised to 1.87.0 in v0.11 to remove the dependency on the `home` crate & instead use the undeprecated `std::env::home_dir`.
If you want, you can use v0.9 with an MSRV of 1.70.0 or v0.10 with an MSRV of 1.81.0 instead.
## 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.acme-corp.Frobnicator-Plus`
- Windows: `~\AppData\Roaming\Acme Corp\Frobnicator Plus`
Note: the location of the home (~) is determined by [`std::env::home_dir`](https://doc.rust-lang.org/std/env/fn.home_dir.html).
### 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.
+5
View File
@@ -0,0 +1,5 @@
edition = "2024"
fn_params_layout = "Compressed"
style_edition = "2024"
use_field_init_shorthand = true
use_try_shorthand = true
+175
View File
@@ -0,0 +1,175 @@
//! 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;
use crate::HomeDirError;
/// 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 applications 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(), "org.acme-corp.Frobnicator-Plus".to_string());
/// ```
pub fn bundle_id(&self) -> String {
let author = self.author.to_lowercase().replace(' ', "-");
let app_name = self.app_name.replace(' ', "-");
let mut parts = vec![
self.top_level_domain.as_str(),
author.as_str(),
app_name.as_str(),
];
parts.retain(|part| !part.is_empty());
parts.join(".")
}
/// Returns a unixy version of the applications 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 {
/// 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.
/// Currently, only the [`Xdg`](struct.Xdg.html) & [`Unix`](struct.Unix.html) strategies support
/// this.
fn state_dir(&self) -> Option<PathBuf>;
/// Gets the runtime directory for your application.
/// Currently, only the [`Xdg`](struct.Xdg.html) & [`Unix`](struct.Unix.html) strategies support
/// this.
///
/// Note: The [XDG Base Directory Specification](spec) places additional requirements on this
/// directory related to ownership, permissions, and persistence. This library does not check
/// these requirements.
///
/// [spec]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
fn runtime_dir(&self) -> Option<PathBuf>;
/// Constructs a path inside your applications configuration directory to which a path of your choice has been appended.
fn in_config_dir<P: AsRef<OsStr>>(&self, path: P) -> PathBuf {
in_dir_method!(self, path, config_dir)
}
/// Constructs a path inside your applications data directory to which a path of your choice has been appended.
fn in_data_dir<P: AsRef<OsStr>>(&self, path: P) -> PathBuf {
in_dir_method!(self, path, data_dir)
}
/// Constructs a path inside your applications cache directory to which a path of your choice has been appended.
fn in_cache_dir<P: AsRef<OsStr>>(&self, path: P) -> PathBuf {
in_dir_method!(self, path, cache_dir)
}
/// Constructs a path inside your applications state directory to which a path of your choice has been appended.
///
/// Currently, this is only implemented for the [`Xdg`](struct.Xdg.html) strategy.
fn in_state_dir<P: AsRef<OsStr>>(&self, path: P) -> Option<PathBuf> {
in_dir_method!(opt: self, path, state_dir)
}
/// Constructs a path inside your applications runtime directory to which a path of your choice has been appended.
/// Currently, only the [`Xdg`](struct.Xdg.html) & [`Unix`](struct.Unix.html) strategies support
/// this.
///
/// See the note in [`runtime_dir`](#method.runtime_dir) for more information.
fn in_runtime_dir<P: AsRef<OsStr>>(&self, path: P) -> Option<PathBuf> {
in_dir_method!(opt: self, path, runtime_dir)
}
}
macro_rules! create_strategies {
($native: ty, $app: ty) => {
/// Returns the current OSs 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, HomeDirError> {
<$native>::new(args)
}
/// Returns the current OSs 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, HomeDirError> {
<$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;
+86
View File
@@ -0,0 +1,86 @@
use crate::base_strategy::BaseStrategy;
use crate::{HomeDirError, base_strategy};
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.acme-corp.Frobnicator-Plus/"))
/// );
/// assert_eq!(
/// app_strategy.data_dir().strip_prefix(&home_dir),
/// Ok(Path::new("Library/Application Support/org.acme-corp.Frobnicator-Plus/"))
/// );
/// assert_eq!(
/// app_strategy.cache_dir().strip_prefix(&home_dir),
/// Ok(Path::new("Library/Caches/org.acme-corp.Frobnicator-Plus/"))
/// );
/// 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 Apple {
/// Create a new Apple AppStrategy
pub fn new(args: super::AppStrategyArgs) -> Result<Self, HomeDirError> {
Ok(Self {
base_strategy: base_strategy::Apple::new()?,
bundle_id: args.bundle_id(),
})
}
}
impl super::AppStrategy for Apple {
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<PathBuf> {
None
}
fn runtime_dir(&self) -> Option<PathBuf> {
None
}
}
+87
View File
@@ -0,0 +1,87 @@
use std::path::{Path, PathBuf};
use crate::HomeDirError;
/// 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 Unix {
/// Create a new Unix AppStrategy
pub fn new(args: super::AppStrategyArgs) -> Result<Self, HomeDirError> {
Ok(Self {
home_dir: crate::home_dir()?,
unixy_name: format!(".{}", args.unixy_name()),
})
}
}
impl super::AppStrategy for Unix {
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<PathBuf> {
Some(self.home_dir.join(&self.unixy_name).join("state/"))
}
fn runtime_dir(&self) -> Option<PathBuf> {
Some(self.home_dir.join(&self.unixy_name).join("runtime/"))
}
}
@@ -0,0 +1,170 @@
use crate::base_strategy::BaseStrategy;
use crate::{HomeDirError, base_strategy};
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 strategys use of the:
/// - (on Windows) SHGetKnownFolderPath 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.
/// unsafe {
/// 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/"
/// };
///
/// unsafe {
/// 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 Windows {
/// Create a new Windows AppStrategy
pub fn new(args: super::AppStrategyArgs) -> Result<Self, HomeDirError> {
Ok(Self {
base_strategy: base_strategy::Windows::new()?,
author_app_name_path: PathBuf::from(args.author).join(args.app_name),
})
}
}
impl super::AppStrategy for Windows {
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<PathBuf> {
None
}
fn runtime_dir(&self) -> Option<PathBuf> {
None
}
}
+223
View File
@@ -0,0 +1,223 @@
use crate::base_strategy::BaseStrategy;
use crate::{HomeDirError, base_strategy};
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 strategys 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.
/// unsafe {
/// 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/"
/// };
///
/// unsafe {
/// 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.
/// unsafe {
/// 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 Xdg {
/// Create a new Xdg AppStrategy
pub fn new(args: super::AppStrategyArgs) -> Result<Self, HomeDirError> {
Ok(Self {
base_strategy: base_strategy::Xdg::new()?,
unixy_name: args.unixy_name(),
})
}
}
impl super::AppStrategy for Xdg {
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<PathBuf> {
Some(
self.base_strategy
.state_dir()
.unwrap()
.join(&self.unixy_name),
)
}
fn runtime_dir(&self) -> Option<PathBuf> {
self.base_strategy
.runtime_dir()
.map(|runtime_dir| runtime_dir.join(&self.unixy_name))
}
}
+69
View File
@@ -0,0 +1,69 @@
//! These strategies simply provide the users configuration, data, and cache directories, without knowing about the application specifically.
use crate::HomeDirError;
use std::path::{Path, PathBuf};
/// Provides configuration, data, and cache directories of the current user.
pub trait BaseStrategy {
/// Gets the home directory of the current user.
fn home_dir(&self) -> &Path;
/// Gets the users configuration directory.
fn config_dir(&self) -> PathBuf;
/// Gets the users data directory.
fn data_dir(&self) -> PathBuf;
/// Gets the users cache directory.
fn cache_dir(&self) -> PathBuf;
/// Gets the users state directory.
/// Currently, only the [`Xdg`](struct.Xdg.html) strategy supports this.
fn state_dir(&self) -> Option<PathBuf>;
/// Gets the users runtime directory.
/// Currently, only the [`Xdg`](struct.Xdg.html) strategy supports this.
///
/// Note: The [XDG Base Directory Specification](spec) places additional requirements on this
/// directory related to ownership, permissions, and persistence. This library does not check
/// these requirements.
///
/// [spec]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
fn runtime_dir(&self) -> Option<PathBuf>;
}
macro_rules! create_strategies {
($native: ty, $base: ty) => {
/// Returns the current OSs 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, HomeDirError> {
<$native>::new()
}
/// Returns the current OSs 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, HomeDirError> {
<$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;
@@ -0,0 +1,78 @@
use std::path::{Path, PathBuf};
use crate::HomeDirError;
/// 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 Apple {
/// Create a new Apple BaseStrategy
pub fn new() -> Result<Self, HomeDirError> {
Ok(Self {
home_dir: crate::home_dir()?,
})
}
}
impl super::BaseStrategy for Apple {
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<PathBuf> {
None
}
fn runtime_dir(&self) -> Option<PathBuf> {
None
}
}
@@ -0,0 +1,210 @@
use std::path::{Path, PathBuf};
use crate::HomeDirError;
/// 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 strategys use of the:
/// - (on Windows) SHGetKnownFolderPath 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.
/// unsafe {
/// 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/"
/// };
///
/// unsafe {
/// 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,
}
impl Windows {
/// Create a new Windows BaseStrategy
pub fn new() -> Result<Self, HomeDirError> {
Ok(Self {
home_dir: crate::home_dir()?,
})
}
fn dir_inner(env: &'static str) -> Option<PathBuf> {
std::env::var_os(env)
.filter(|s| !s.is_empty())
.map(PathBuf::from)
.or_else(|| Self::dir_crt(env))
}
// Ref: https://github.com/rust-lang/cargo/blob/home-0.5.11/crates/home/src/windows.rs
// We should keep this code in sync with the above.
#[cfg(all(windows, not(target_vendor = "uwp")))]
fn dir_crt(env: &'static str) -> Option<PathBuf> {
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use std::ptr;
use std::slice;
use windows_sys::Win32::Foundation::S_OK;
use windows_sys::Win32::System::Com::CoTaskMemFree;
use windows_sys::Win32::UI::Shell::{
FOLDERID_LocalAppData, FOLDERID_RoamingAppData, KF_FLAG_DONT_VERIFY,
SHGetKnownFolderPath,
};
unsafe extern "C" {
fn wcslen(buf: *const u16) -> usize;
}
let folder_id = match env {
"APPDATA" => FOLDERID_RoamingAppData,
"LOCALAPPDATA" => FOLDERID_LocalAppData,
_ => return None,
};
unsafe {
let mut path = ptr::null_mut();
match SHGetKnownFolderPath(
&folder_id,
KF_FLAG_DONT_VERIFY as u32,
std::ptr::null_mut(),
&mut path,
) {
S_OK => {
let path_slice = slice::from_raw_parts(path, wcslen(path));
let s = OsString::from_wide(path_slice);
CoTaskMemFree(path.cast());
Some(PathBuf::from(s))
}
_ => {
// Free any allocated memory even on failure. A null ptr is a no-op for `CoTaskMemFree`.
CoTaskMemFree(path.cast());
None
}
}
}
}
#[cfg(not(all(windows, not(target_vendor = "uwp"))))]
fn dir_crt(_env: &'static str) -> Option<PathBuf> {
None
}
}
impl super::BaseStrategy for Windows {
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<PathBuf> {
None
}
fn runtime_dir(&self) -> Option<PathBuf> {
None
}
}
+213
View File
@@ -0,0 +1,213 @@
use std::path::Path;
use std::path::PathBuf;
use crate::HomeDirError;
/// 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 strategys 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.
/// unsafe {
/// 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/"
/// };
///
/// unsafe {
/// 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.
/// unsafe {
/// 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 {
/// Create a new Xdg BaseStrategy
pub fn new() -> Result<Self, HomeDirError> {
Ok(Self {
home_dir: crate::home_dir()?,
})
}
fn env_var_or_none(env_var: &str) -> Option<PathBuf> {
std::env::var_os(env_var).and_then(|path| {
let path = PathBuf::from(path);
// Return None if the path obtained from the environment variable isnt absolute.
if path.is_absolute() { Some(path) } else { None }
})
}
fn env_var_or_default(&self, env_var: &str, default: impl AsRef<Path>) -> PathBuf {
Self::env_var_or_none(env_var).unwrap_or_else(|| self.home_dir.join(default))
}
}
impl super::BaseStrategy for Xdg {
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<PathBuf> {
Some(self.env_var_or_default("XDG_STATE_HOME", ".local/state/"))
}
fn runtime_dir(&self) -> Option<PathBuf> {
Self::env_var_or_none("XDG_RUNTIME_DIR")
}
}
+126
View File
@@ -0,0 +1,126 @@
//! 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.
//!
//! Lets take an application created by `Acme Corp` with the name `Frobnicator Plus` and the top-level domain of `org` 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.acme-corp.Frobnicator-Plus`.
//! - 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 arent 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 didnt 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. Heres 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::{AppStrategy, AppStrategyArgs, choose_app_strategy};
pub use base_strategy::{BaseStrategy, choose_base_strategy};
/// A convenience function that wraps the [`home_dir`](https://doc.rust-lang.org/std/env/fn.home_dir.html) function from the standard library.
pub fn home_dir() -> Result<std::path::PathBuf, HomeDirError> {
std::env::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 {}