Skip to main content

isoloom_spec/
host.rs

1//! The host a command runs on, as Isoloom reads it: its environment variables, passed in by the
2//! program that runs (the command line, the launcher), never read from the process here, so
3//! every function of the crate stays a function of its inputs.
4
5use std::ffi::{OsStr, OsString};
6use std::path::PathBuf;
7
8/// The host's environment variables.
9#[derive(Debug, Clone, Default, PartialEq, Eq)]
10pub struct Host {
11    vars: Vec<(OsString, OsString)>,
12}
13
14impl Host {
15    /// A host with these environment variables (`std::env::vars_os()`).
16    #[must_use]
17    pub fn new(vars: impl IntoIterator<Item = (OsString, OsString)>) -> Host {
18        Host {
19            vars: vars.into_iter().collect(),
20        }
21    }
22
23    /// The value of variable `key`, when it is set.
24    fn var(&self, key: &str) -> Option<&OsStr> {
25        self.vars.iter().find(|(k, _)| k == key).map(|(_, v)| v.as_os_str())
26    }
27
28    /// The folder Isoloom keeps its own state in: `$ISOLOOM_HOME`, else `.isoloom` in the user's
29    /// home (`$HOME`, `%USERPROFILE%`), else in the current folder.
30    #[must_use]
31    pub fn home(&self) -> PathBuf {
32        if let Some(home) = self.var("ISOLOOM_HOME") {
33            return PathBuf::from(home);
34        }
35        let user_home = self
36            .var("HOME")
37            .or_else(|| self.var("USERPROFILE"))
38            .map_or_else(|| PathBuf::from("."), PathBuf::from);
39        user_home.join(".isoloom")
40    }
41
42    /// The variables whose name and value are text, in name order.
43    #[must_use]
44    pub fn text_vars(&self) -> Vec<(String, String)> {
45        let mut out: Vec<(String, String)> = self
46            .vars
47            .iter()
48            .filter_map(|(k, v)| Some((k.to_str()?.to_owned(), v.to_str()?.to_owned())))
49            .collect();
50        out.sort();
51        out
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    fn host(vars: &[(&str, &str)]) -> Host {
60        Host::new(vars.iter().map(|(k, v)| (OsString::from(k), OsString::from(v))))
61    }
62
63    #[test]
64    fn isoloom_home_wins_then_the_users_home() {
65        assert_eq!(host(&[("HOME", "/home/a"), ("ISOLOOM_HOME", "/srv/iso")]).home(), PathBuf::from("/srv/iso"));
66        assert_eq!(host(&[("HOME", "/home/a")]).home(), PathBuf::from("/home/a/.isoloom"));
67        assert_eq!(host(&[("USERPROFILE", "C:\\Users\\a")]).home(), PathBuf::from("C:\\Users\\a").join(".isoloom"));
68        assert_eq!(Host::default().home(), PathBuf::from("./.isoloom"));
69    }
70
71    #[test]
72    fn text_variables_come_in_name_order() {
73        assert_eq!(
74            host(&[("B", "2"), ("A", "1")]).text_vars(),
75            [("A".to_owned(), "1".to_owned()), ("B".to_owned(), "2".to_owned())]
76        );
77    }
78}