summaryrefslogtreecommitdiffstats
path: root/src/state/history/builtins.rs
blob: 1bf9d0021e1c7f375a8599c2b2b1cb23fe9ce2a7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use std::os::unix::process::ExitStatusExt as _;

type Builtin = &'static (dyn for<'a> Fn(
    &'a crate::parse::Exe,
    &'a super::ProcessEnv,
) -> std::pin::Pin<
    Box<
        dyn std::future::Future<Output = std::process::ExitStatus>
            + Sync
            + Send
            + 'a,
    >,
> + Sync
              + Send);

static BUILTINS: once_cell::sync::Lazy<
    std::collections::HashMap<&'static str, Builtin>,
> = once_cell::sync::Lazy::new(|| {
    // all this does is convince the type system to do the right thing, i
    // don't think there's any way to just do it directly through annotations
    // or casts or whatever
    fn coerce_builtin<F>(f: &'static F) -> Builtin
    where
        F: for<'a> Fn(
                &'a crate::parse::Exe,
                &'a super::ProcessEnv,
            ) -> std::pin::Pin<
                Box<
                    dyn std::future::Future<Output = std::process::ExitStatus>
                        + Sync
                        + Send
                        + 'a,
                >,
            > + Sync
            + Send
            + 'static,
    {
        f
    }

    let mut builtins = std::collections::HashMap::new();
    builtins.insert("cd", coerce_builtin(&|exe, env| Box::pin(cd(exe, env))));
    builtins
        .insert("and", coerce_builtin(&|exe, env| Box::pin(and(exe, env))));
    builtins.insert("or", coerce_builtin(&|exe, env| Box::pin(or(exe, env))));
    builtins
});

pub async fn run(
    exe: &crate::parse::Exe,
    env: &super::ProcessEnv,
) -> Option<async_std::process::ExitStatus> {
    if let Some(f) = BUILTINS.get(exe.exe()) {
        Some(f(exe, env).await)
    } else {
        None
    }
}

async fn cd(
    exe: &crate::parse::Exe,
    env: &super::ProcessEnv,
) -> async_std::process::ExitStatus {
    let dir = exe
        .args()
        .into_iter()
        .map(std::convert::AsRef::as_ref)
        .next()
        .unwrap_or("");

    let dir = if dir.is_empty() {
        home()
    } else if dir.starts_with('~') {
        let path: std::path::PathBuf = dir.into();
        if let std::path::Component::Normal(prefix) =
            path.components().next().unwrap()
        {
            if prefix.to_str() == Some("~") {
                home().join(path.strip_prefix(prefix).unwrap())
            } else {
                // TODO
                env.write_vt(b"unimplemented").await;
                return async_std::process::ExitStatus::from_raw(1 << 8);
            }
        } else {
            unreachable!()
        }
    } else {
        dir.into()
    };
    let code = match std::env::set_current_dir(&dir) {
        Ok(()) => 0,
        Err(e) => {
            env.write_vt(
                format!(
                    "{}: {}: {}",
                    exe.exe(),
                    crate::format::io_error(&e),
                    dir.display()
                )
                .as_bytes(),
            )
            .await;
            1
        }
    };
    async_std::process::ExitStatus::from_raw(code << 8)
}

async fn and(
    exe: &crate::parse::Exe,
    env: &super::ProcessEnv,
) -> async_std::process::ExitStatus {
    let exe = exe.shift();
    if env.latest_status().success() {
        super::run_exe(&exe, env).await;
    }
    *env.latest_status()
}

async fn or(
    exe: &crate::parse::Exe,
    env: &super::ProcessEnv,
) -> async_std::process::ExitStatus {
    let exe = exe.shift();
    if !env.latest_status().success() {
        super::run_exe(&exe, env).await;
    }
    *env.latest_status()
}

fn home() -> std::path::PathBuf {
    std::env::var_os("HOME").unwrap().into()
}