1use std::{path::PathBuf, sync::Arc};
5
6use cli_shared::UserConfig;
7use objects::{HeddleError, NoopProgress, NoopWarnings, ProgressSink, WarningSink};
8use repo::Repository;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum Verbosity {
13 Quiet,
14 #[default]
15 Normal,
16 Verbose,
17}
18
19pub struct ExecutionContext {
21 repo: Option<Repository>,
22 config: UserConfig,
23 verbosity: Verbosity,
24 progress: Arc<dyn ProgressSink>,
25 warnings: Arc<dyn WarningSink>,
26 op_id: Option<String>,
27 }
29
30impl ExecutionContext {
31 pub fn builder() -> ExecutionContextBuilder {
32 ExecutionContextBuilder::default()
33 }
34
35 pub fn require_repo(&self) -> Result<&Repository, HeddleError> {
36 self.repo
37 .as_ref()
38 .ok_or_else(|| HeddleError::RepositoryNotFound(PathBuf::from(".")))
39 }
40
41 pub fn repo(&self) -> Option<&Repository> {
42 self.repo.as_ref()
43 }
44
45 pub fn config(&self) -> &UserConfig {
46 &self.config
47 }
48
49 pub fn progress(&self) -> &dyn ProgressSink {
50 &*self.progress
51 }
52
53 pub fn warnings(&self) -> &dyn WarningSink {
54 &*self.warnings
55 }
56
57 pub fn verbosity(&self) -> Verbosity {
58 self.verbosity
59 }
60
61 pub fn op_id(&self) -> Option<&str> {
62 self.op_id.as_deref()
63 }
64}
65
66pub struct ExecutionContextBuilder {
68 repo: Option<Repository>,
69 config: UserConfig,
70 verbosity: Verbosity,
71 progress: Arc<dyn ProgressSink>,
72 warnings: Arc<dyn WarningSink>,
73 op_id: Option<String>,
74}
75
76impl Default for ExecutionContextBuilder {
77 fn default() -> Self {
78 Self {
79 repo: None,
80 config: UserConfig::default(),
81 verbosity: Verbosity::Normal,
82 progress: Arc::new(NoopProgress),
83 warnings: Arc::new(NoopWarnings),
84 op_id: None,
85 }
86 }
87}
88
89impl ExecutionContextBuilder {
90 pub fn repo(mut self, repo: Repository) -> Self {
91 self.repo = Some(repo);
92 self
93 }
94
95 pub fn config(mut self, config: UserConfig) -> Self {
96 self.config = config;
97 self
98 }
99
100 pub fn verbosity(mut self, verbosity: Verbosity) -> Self {
101 self.verbosity = verbosity;
102 self
103 }
104
105 pub fn progress(mut self, progress: Arc<dyn ProgressSink>) -> Self {
106 self.progress = progress;
107 self
108 }
109
110 pub fn warnings(mut self, warnings: Arc<dyn WarningSink>) -> Self {
111 self.warnings = warnings;
112 self
113 }
114
115 pub fn op_id(mut self, op_id: impl Into<String>) -> Self {
116 self.op_id = Some(op_id.into());
117 self
118 }
119
120 pub fn build(self) -> ExecutionContext {
121 ExecutionContext {
122 repo: self.repo,
123 config: self.config,
124 verbosity: self.verbosity,
125 progress: self.progress,
126 warnings: self.warnings,
127 op_id: self.op_id,
128 }
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn default_context_has_no_repo_and_noop_sinks() {
138 let ctx = ExecutionContext::builder().build();
139
140 assert!(matches!(
141 ctx.require_repo(),
142 Err(HeddleError::RepositoryNotFound(_))
143 ));
144 assert_eq!(ctx.verbosity(), Verbosity::Normal);
145 assert!(ctx.op_id().is_none());
146 ctx.progress().event(objects::ProgressEvent::Finish {
147 id: objects::TaskId(1),
148 });
149 ctx.warnings().warn(objects::Warning {
150 kind: "test".into(),
151 message: "ignored".to_string(),
152 });
153 }
154
155 #[test]
156 fn builder_sets_non_repo_fields() {
157 let ctx = ExecutionContext::builder()
158 .config(UserConfig::default())
159 .verbosity(Verbosity::Verbose)
160 .op_id("op-123")
161 .build();
162
163 assert_eq!(ctx.verbosity(), Verbosity::Verbose);
164 assert_eq!(ctx.op_id(), Some("op-123"));
165 }
166}