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
|
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(), 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()
}
|