1use crate::output::Format;
2use crate::registry::Invocation;
3use crate::{Error, Registry, Result, Value};
4use std::any::{Any, TypeId};
5use std::collections::HashMap;
6use std::path::{Path, PathBuf};
7use std::process::Command;
8use std::sync::Arc;
9
10pub struct Context {
15 root: PathBuf,
16 registry: Arc<Registry>,
17 previous: Value,
18 state: HashMap<TypeId, Box<dyn Any>>,
19 depth: usize,
20 terminal_output: String,
21 default_format: Option<Format>,
22}
23
24impl Context {
25 pub(crate) fn new(root: PathBuf, registry: Arc<Registry>) -> Self {
26 Self {
27 root,
28 registry,
29 previous: Value::Null,
30 state: HashMap::new(),
31 depth: 0,
32 terminal_output: String::new(),
33 default_format: None,
34 }
35 }
36 pub fn root(&self) -> &Path {
37 &self.root
38 }
39 pub fn previous(&self) -> &Value {
40 &self.previous
41 }
42 pub fn write_output(&mut self, output: &str) {
44 self.terminal_output.push_str(output);
45 }
46 pub(crate) fn take_output(&mut self) -> String {
47 std::mem::take(&mut self.terminal_output)
48 }
49 pub(crate) fn set_default_format(&mut self, format: Option<Format>) {
50 self.default_format = format;
51 }
52 pub(crate) fn default_format(&self) -> Option<Format> {
53 self.default_format
54 }
55 pub fn insert<State: Any>(&mut self, state: State) {
56 self.state.insert(TypeId::of::<State>(), Box::new(state));
57 }
58 pub fn get<State: Any>(&self) -> Option<&State> {
59 self.state.get(&TypeId::of::<State>())?.downcast_ref()
60 }
61 pub fn get_mut<State: Any>(&mut self) -> Option<&mut State> {
62 self.state.get_mut(&TypeId::of::<State>())?.downcast_mut()
63 }
64
65 pub fn command(&self, program: impl AsRef<std::ffi::OsStr>) -> Command {
68 let mut command = Command::new(program);
69 command.current_dir(&self.root);
70 command
71 }
72
73 pub fn call(&mut self, name: &str, arguments: &[&str]) -> Result<Value> {
76 let tokens: Vec<_> = std::iter::once(name)
77 .chain(arguments.iter().copied())
78 .map(str::to_owned)
79 .collect();
80 let mut invocations = self.registry.plan(&tokens)?;
81 if invocations.len() != 1 {
82 return Err(Error::new("Context::call accepts exactly one task"));
83 }
84 self.invoke(invocations.remove(0))
85 }
86
87 pub(crate) fn invoke(&mut self, invocation: Invocation) -> Result<Value> {
88 if self.depth >= 64 {
89 return Err(Error::new(
90 "task call depth exceeded 64; check for recursive hooks",
91 ));
92 }
93 let handler = self
94 .registry
95 .tasks
96 .get(&invocation.name)
97 .ok_or_else(|| Error::new(format!("unknown task {:?}", invocation.name)))?
98 .invoke;
99 self.depth += 1;
100 let result = handler(self, &invocation.arguments);
101 self.depth -= 1;
102 let result = result.map_err(|error| Error::new(format!("{}: {error}", invocation.name)))?;
103 self.previous = result.clone();
104 Ok(result)
105 }
106}