af_core/
env.rs

1// Copyright © 2020 Alexandra Frydl
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7//! Provides access to environment variables and well-known paths.
8
9use crate::path;
10use crate::prelude::*;
11use std::io;
12use std::path::{Path, PathBuf};
13
14/// One of the possible errors that can occur when reading an environment
15/// variable.
16#[derive(Debug, Error)]
17pub enum VarError {
18  /// Environment variable not present.
19  #[error("Environment variable not present.")]
20  NotPresent,
21  /// Environment variable contains non-Unicode characters.
22  #[error("Environment variable contains non-Unicode characters.")]
23  NotUnicode,
24}
25
26/// One of the possible errors returned by [`working_path()`].
27#[derive(Debug, Error)]
28pub enum WorkingPathError {
29  /// The working path was not found.
30  #[error("The current working directory was not found.")]
31  NotFound,
32  /// The user does not have permission to access the current working directory.
33  #[error("Permission denied reading the current working directory.")]
34  PermissionDenied,
35  /// The working path is not unicode.
36  #[error("The current working directory `{}` is not unicode.", .0.display())]
37  NotUnicode(PathBuf),
38}
39
40/// The full file system path to the current executable.
41static EXE_PATH: Lazy<Result<(String, String), String>> = Lazy::new(|| {
42  let mut path: String = std::env::current_exe()
43    .map_err(|err| format!("IO error. {}.", err))?
44    .to_str()
45    .ok_or("non-unicode path name.")?
46    .into();
47
48  let file = path::pop(&mut path).unwrap_or_default();
49
50  Ok((path, file))
51});
52
53/// Returns the file name of the currently running executable.
54pub fn exe_name() -> &'static str {
55  &EXE_PATH.as_ref().expect("Failed to determine path to current executable").1
56}
57
58/// Returns the full file system path to directory containing the currently
59/// running executable.
60pub fn exe_path() -> &'static str {
61  &EXE_PATH.as_ref().expect("Failed to determine path to current executable").0
62}
63
64/// Returns the full file system path to the cargo project of the currently
65/// running executable.
66///
67/// This function panics if the executable was not run with a `cargo` command.
68/// Use [`is_cargo_run()`] to check whether this function will panic.
69pub fn project_path() -> Option<&'static str> {
70  static PATH: Lazy<Option<&'static str>> = Lazy::new(|| {
71    let value = var("CARGO_MANIFEST_DIR").ok()?;
72
73    Some(Box::leak(value.into_boxed_str()))
74  });
75
76  *PATH
77}
78
79/// Returns the value of the given environment variable.
80pub fn var(name: &str) -> Result<String, VarError> {
81  std::env::var(name).map_err(|err| match err {
82    std::env::VarError::NotPresent => VarError::NotPresent,
83    std::env::VarError::NotUnicode(_) => VarError::NotUnicode,
84  })
85}
86
87/// Returns the full file system path to the current working directory.
88pub fn working_path() -> Result<String, WorkingPathError> {
89  let path = std::env::current_dir().map_err(|err| match err.kind() {
90    io::ErrorKind::NotFound => WorkingPathError::NotFound,
91    io::ErrorKind::PermissionDenied => WorkingPathError::PermissionDenied,
92    _ => panic!("{}", err),
93  })?;
94
95  Ok(path.to_str().map(String::from).ok_or(WorkingPathError::NotUnicode(path))?)
96}
97
98/// Returns the full file system path to the cargo workspace of the currently
99/// running executable.
100///
101/// If the currently running executable was not started by `cargo run` or a
102/// similar command, this function returns `None`.
103pub fn workspace_path() -> Option<&'static str> {
104  static PATH: Lazy<Option<&'static str>> = Lazy::new(|| {
105    // Starting at the project path, look for a directory containing `Cargo.lock`.
106
107    let project_path = project_path()?;
108    let mut workspace_path: String = project_path.into();
109
110    loop {
111      path::append(&mut workspace_path, "Cargo.lock");
112
113      let found = AsRef::<Path>::as_ref(&workspace_path).exists();
114
115      path::pop(&mut workspace_path);
116
117      if found {
118        break;
119      }
120
121      // Try the parent directory next. If there's no parent directory, default to
122      // the project path.
123
124      if path::pop(&mut workspace_path).is_none() {
125        workspace_path.replace_range(.., project_path);
126        break;
127      }
128    }
129
130    Some(Box::leak(workspace_path.into_boxed_str()))
131  });
132
133  *PATH
134}