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