winget_types/installer/
scope.rs

1use core::{fmt, str::FromStr};
2
3use thiserror::Error;
4
5use crate::utils::RelativeDir;
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
10pub enum Scope {
11    User,
12    Machine,
13}
14
15const USER: &str = "user";
16const MACHINE: &str = "machine";
17
18impl Scope {
19    #[must_use]
20    pub fn find_in<T: AsRef<[u8]>>(value: T) -> Option<Self> {
21        let bytes = value.as_ref();
22
23        let value_contains = |scope: Self| -> Option<Self> {
24            bytes
25                .windows(scope.as_str().len())
26                .any(|window| window.eq_ignore_ascii_case(scope.as_str().as_bytes()))
27                .then_some(scope)
28        };
29
30        value_contains(Self::User).or_else(|| value_contains(Self::Machine))
31    }
32
33    #[must_use]
34    pub fn from_install_directory<T: AsRef<str>>(install_directory: T) -> Option<Self> {
35        const USER_INSTALL_DIRS: [&str; 2] = [RelativeDir::APP_DATA, RelativeDir::LOCAL_APP_DATA];
36        const MACHINE_INSTALL_DIRS: [&str; 7] = [
37            RelativeDir::PROGRAM_FILES_64,
38            RelativeDir::PROGRAM_FILES_32,
39            RelativeDir::COMMON_FILES_64,
40            RelativeDir::COMMON_FILES_32,
41            RelativeDir::PROGRAM_DATA,
42            RelativeDir::WINDOWS_DIR,
43            RelativeDir::SYSTEM_ROOT,
44        ];
45
46        let install_directory = install_directory.as_ref();
47
48        USER_INSTALL_DIRS
49            .iter()
50            .any(|directory| install_directory.starts_with(directory))
51            .then_some(Self::User)
52            .or_else(|| {
53                MACHINE_INSTALL_DIRS
54                    .iter()
55                    .any(|directory| install_directory.starts_with(directory))
56                    .then_some(Self::Machine)
57            })
58    }
59
60    /// Returns `true` if the scope is user.
61    #[must_use]
62    #[inline]
63    pub const fn is_user(&self) -> bool {
64        matches!(self, Self::User)
65    }
66
67    /// Returns `true` if the scope is machine.
68    #[must_use]
69    #[inline]
70    pub const fn is_machine(&self) -> bool {
71        matches!(self, Self::Machine)
72    }
73
74    #[must_use]
75    pub const fn as_str(&self) -> &'static str {
76        match self {
77            Self::User => USER,
78            Self::Machine => MACHINE,
79        }
80    }
81}
82
83impl AsRef<str> for Scope {
84    #[inline]
85    fn as_ref(&self) -> &str {
86        self.as_str()
87    }
88}
89
90impl fmt::Display for Scope {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        self.as_str().fmt(f)
93    }
94}
95
96#[derive(Error, Debug, Eq, PartialEq)]
97#[error("Scope did not match either `{USER}` or `{MACHINE}`")]
98pub struct ScopeParseError;
99
100impl FromStr for Scope {
101    type Err = ScopeParseError;
102
103    fn from_str(s: &str) -> Result<Self, Self::Err> {
104        match s {
105            USER => Ok(Self::User),
106            MACHINE => Ok(Self::Machine),
107            _ => Err(ScopeParseError),
108        }
109    }
110}