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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
//! Shell runtime hooks
//!
//! Hooks are user provided functions that are called on a variety of events that occur in the
//! shell. Some additional context is provided to these hooks.
// ideas for hooks
// - on start
// - after prompt
// - before prompt
// - internal error hook (call whenever there is internal shell error; good for debug)
// - env hook (when environment variable is set/changed)
// - exit hook (tricky, make sure we know what cases to call this)
use std::{
any::{Any, TypeId},
collections::HashMap,
io::BufWriter,
marker::PhantomData,
path::PathBuf,
process::ExitStatus,
time::Duration,
};
use crossterm::{style::Print, QueueableCommand};
use crate::{
cmd_output::CmdOutput,
shell::{Context, Runtime, Shell},
};
pub type HookFn<C: Clone> =
fn(sh: &Shell, sh_ctx: &mut Context, sh_rt: &mut Runtime, ctx: &C) -> anyhow::Result<()>;
// TODO this is some pretty sus implementation
pub trait Hook<C>: FnMut(&Shell, &mut Context, &mut Runtime, &C) -> anyhow::Result<()> {}
impl<C, T: FnMut(&Shell, &mut Context, &mut Runtime, &C) -> anyhow::Result<()>> Hook<C> for T {}
/// Runs when the shell starts up
#[derive(Clone)]
pub struct StartupCtx {
/// How long it took the shell to startup
pub startup_time: Duration,
}
/// Default implementation for [StartupCtx]
pub fn startup_hook(
sh: &Shell,
sh_ctx: &mut Context,
sh_rt: &mut Runtime,
_ctx: &StartupCtx,
) -> anyhow::Result<()> {
println!("welcome to shrs!");
Ok(())
}
/// Runs before a command is executed
#[derive(Clone)]
pub struct BeforeCommandCtx {
/// Literal command entered by user
pub raw_command: String,
/// Command to be executed, after performing all substitutions
pub command: String,
}
/// Default implementation for [BeforeCommandCtx]
pub fn before_command_hook(
sh: &Shell,
sh_ctx: &mut Context,
sh_rt: &mut Runtime,
ctx: &BeforeCommandCtx,
) -> anyhow::Result<()> {
// let expanded_cmd = format!("[evaluating] {}\n", ctx.command);
// out.queue(Print(expanded_cmd))?;
Ok(())
}
/// Runs after a command is executed
#[derive(Clone)]
pub struct AfterCommandCtx {
/// The command that was ran
pub command: String,
/// Command output
pub cmd_output: CmdOutput,
}
/// Default implementation for [AfterCommandCtx]
pub fn after_command_hook(
sh: &Shell,
sh_ctx: &mut Context,
sh_rt: &mut Runtime,
ctx: &AfterCommandCtx,
) -> anyhow::Result<()> {
// let exit_code_str = format!("[exit +{}]\n", ctx.exit_code);
// out.queue(Print(exit_code_str))?;
Ok(())
}
/// Runs when the current working directory is modified
#[derive(Clone)]
pub struct ChangeDirCtx {
pub old_dir: PathBuf,
pub new_dir: PathBuf,
}
/// Default implementation for [ChangeDirCtx]
pub fn change_dir_hook(
sh: &Shell,
sh_ctx: &mut Context,
sh_rt: &mut Runtime,
ctx: &ChangeDirCtx,
) -> anyhow::Result<()> {
Ok(())
}
/// Runs when a job is completed
#[derive(Clone)]
pub struct JobExitCtx {
pub status: ExitStatus,
}
/// Default implementation for [JobExitCtx]
pub fn job_exit_hook(
sh: &Shell,
sh_ctx: &mut Context,
sh_rt: &mut Runtime,
ctx: &JobExitCtx,
) -> anyhow::Result<()> {
println!("[exit +{:?}]", ctx.status.code());
Ok(())
}
// /// Hook that runs when a command has a specific exit code
// #[derive(Clone)]
// pub struct ExitStatusCtx<const C: i32> { }
/// Collection of all the hooks that are available
pub struct Hooks {
// TODO how to uniquely identify a hook? using the Ctx type?
hooks: anymap::Map,
}
impl Default for Hooks {
/// Register default hooks
fn default() -> Self {
let mut hooks = Hooks::new();
hooks.register(startup_hook);
hooks.register(before_command_hook);
hooks.register(after_command_hook);
hooks.register(change_dir_hook);
hooks.register(job_exit_hook);
hooks
}
}
impl Hooks {
pub fn new() -> Self {
Self {
hooks: anymap::Map::new(),
}
}
/// Registers a new hook
pub fn register<C: Clone + 'static>(&mut self, hook: HookFn<C>) {
match self.hooks.get_mut::<Vec<HookFn<C>>>() {
Some(hook_list) => {
hook_list.push(hook);
},
None => {
// register any empty vector for the type
self.hooks.insert::<Vec<HookFn<C>>>(vec![hook]);
},
};
}
/*
/// Register from an iterator
pub fn register_iter(&mut self) {
unimplemented!()
}
*/
/// Executes all registered hooks
pub fn run<C: Clone + 'static>(
&self,
sh: &Shell,
sh_ctx: &mut Context,
sh_rt: &mut Runtime,
ctx: C,
) -> anyhow::Result<()> {
if let Some(hook_list) = self.hooks.get::<Vec<HookFn<C>>>() {
for hook in hook_list.iter() {
(hook)(sh, sh_ctx, sh_rt, &ctx)?;
}
}
Ok(())
}
}