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