logix_type/types/
executable_path.rs

1use logix_vfs::LogixVfs;
2
3use crate::{
4    error::{ParseError, PathError, Result, SourceSpan, Wanted},
5    parser::LogixParser,
6    token::{Literal, StrLit, Token},
7    type_trait::{LogixTypeDescriptor, LogixValueDescriptor, Value},
8    types::{FullPath, NameOnlyPath, ValidPath},
9    LogixType,
10};
11use std::{
12    borrow::Cow,
13    ffi::OsStr,
14    fmt,
15    path::{Path, PathBuf},
16};
17
18#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
19enum Location {
20    Path(FullPath),
21    Name(NameOnlyPath),
22}
23
24/// The environment used when resolving executable paths
25#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
26pub struct ExecutableEnv<'a> {
27    pub path_env: Option<Cow<'a, OsStr>>,
28}
29
30impl<'a> ExecutableEnv<'a> {
31    const DEFAULT: ExecutableEnv<'static> = ExecutableEnv { path_env: None };
32
33    pub fn which(&self, name_or_path: impl AsRef<OsStr>) -> Option<FullPath> {
34        if let Some(path_env) = self.path_env.as_deref() {
35            which::which_in_global(name_or_path, Some(path_env))
36                .ok()?
37                .next()
38                .map(FullPath::try_from)?
39                .ok()
40        } else {
41            which::which_global(name_or_path)
42                .ok()
43                .map(FullPath::try_from)?
44                .ok()
45        }
46    }
47}
48
49/// A path to an executable. This is either a full path, or a filename. If it is a
50/// relative path it will fail to avoid issues. For example, imagine what happens
51/// if EDITOR is set to a relative path.
52#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
53pub struct ExecutablePath {
54    loc: Location,
55}
56
57impl ExecutablePath {
58    pub fn as_path(&self) -> &Path {
59        match &self.loc {
60            Location::Path(v) => v.as_path(),
61            Location::Name(v) => v.as_path(),
62        }
63    }
64
65    pub fn which(&self, env: Option<&ExecutableEnv>) -> Option<FullPath> {
66        env.unwrap_or(&ExecutableEnv::DEFAULT).which(self)
67    }
68
69    pub fn join(&self, path: impl AsRef<Path>) -> Result<Self, PathError> {
70        Ok(Self {
71            loc: match &self.loc {
72                Location::Path(v) => v.join(path).map(Location::Path)?,
73                Location::Name(v) => v.join(path).map(Location::Name)?,
74            },
75        })
76    }
77
78    pub fn with_file_name(&self, name: impl AsRef<OsStr>) -> Self {
79        Self {
80            loc: match &self.loc {
81                Location::Path(v) => Location::Path(v.with_file_name(name)),
82                Location::Name(v) => Location::Name(v.with_file_name(name)),
83            },
84        }
85    }
86
87    pub fn with_extension(&self, ext: impl AsRef<OsStr>) -> Self {
88        Self {
89            loc: match &self.loc {
90                Location::Path(v) => Location::Path(v.with_extension(ext)),
91                Location::Name(v) => Location::Name(v.with_extension(ext)),
92            },
93        }
94    }
95}
96
97impl TryFrom<PathBuf> for ExecutablePath {
98    type Error = PathError;
99
100    fn try_from(path: PathBuf) -> Result<Self, PathError> {
101        match ValidPath::try_from(path)? {
102            ValidPath::Full(path) => Ok(Self {
103                loc: Location::Path(path),
104            }),
105            ValidPath::Name(name) => Ok(Self {
106                loc: Location::Name(name),
107            }),
108            ValidPath::Rel(_) => Err(PathError::NotFullOrNameOnly),
109        }
110    }
111}
112
113impl From<ExecutablePath> for PathBuf {
114    fn from(v: ExecutablePath) -> PathBuf {
115        match v.loc {
116            Location::Path(v) => v.into(),
117            Location::Name(v) => v.into(),
118        }
119    }
120}
121
122impl_path_type_traits!(
123    ExecutablePath,
124    "The name of or full path to an executable file"
125);