1use std::{path::PathBuf, sync::Arc};
5
6use objects::{HeddleError, NoopProgress, NoopWarnings, ProgressSink, WarningSink};
7use repo::{FsMonitorMode, Repository, WorktreeStatusOptions};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
11pub enum Verbosity {
12 Quiet,
13 #[default]
14 Normal,
15 Verbose,
16}
17
18pub struct ExecutionContext {
23 repo: Option<Repository>,
24 start_path: Option<PathBuf>,
25 principal_fallback: Option<(String, String)>,
26 fsmonitor_mode: FsMonitorMode,
27 verbosity: Verbosity,
28 progress: Arc<dyn ProgressSink>,
29 warnings: Arc<dyn WarningSink>,
30 op_id: Option<String>,
31 }
33
34impl ExecutionContext {
35 pub fn builder() -> ExecutionContextBuilder {
36 ExecutionContextBuilder::default()
37 }
38
39 pub fn require_repo(&self) -> Result<&Repository, HeddleError> {
40 self.repo
41 .as_ref()
42 .ok_or_else(|| HeddleError::RepositoryNotFound(PathBuf::from(".")))
43 }
44
45 pub fn repo(&self) -> Option<&Repository> {
46 self.repo.as_ref()
47 }
48
49 pub fn start_path(&self) -> Option<&std::path::Path> {
50 self.start_path.as_deref()
51 }
52
53 pub fn principal_fallback(&self) -> Option<(&str, &str)> {
55 self.principal_fallback
56 .as_ref()
57 .map(|(name, email)| (name.as_str(), email.as_str()))
58 }
59
60 pub fn fsmonitor_mode(&self) -> FsMonitorMode {
62 self.fsmonitor_mode
63 }
64
65 pub fn worktree_status_options(&self) -> WorktreeStatusOptions {
66 WorktreeStatusOptions {
67 fsmonitor: repo::FsMonitorSettings {
68 mode: self.fsmonitor_mode,
69 },
70 }
71 }
72
73 pub fn progress(&self) -> &dyn ProgressSink {
74 &*self.progress
75 }
76
77 pub fn warnings(&self) -> &dyn WarningSink {
78 &*self.warnings
79 }
80
81 pub fn verbosity(&self) -> Verbosity {
82 self.verbosity
83 }
84
85 pub fn op_id(&self) -> Option<&str> {
86 self.op_id.as_deref()
87 }
88}
89
90pub struct ExecutionContextBuilder {
92 repo: Option<Repository>,
93 start_path: Option<PathBuf>,
94 principal_fallback: Option<(String, String)>,
95 fsmonitor_mode: FsMonitorMode,
96 verbosity: Verbosity,
97 progress: Arc<dyn ProgressSink>,
98 warnings: Arc<dyn WarningSink>,
99 op_id: Option<String>,
100}
101
102impl Default for ExecutionContextBuilder {
103 fn default() -> Self {
104 Self {
105 repo: None,
106 start_path: None,
107 principal_fallback: None,
108 fsmonitor_mode: FsMonitorMode::default(),
109 verbosity: Verbosity::Normal,
110 progress: Arc::new(NoopProgress),
111 warnings: Arc::new(NoopWarnings),
112 op_id: None,
113 }
114 }
115}
116
117impl ExecutionContextBuilder {
118 pub fn repo(mut self, repo: Repository) -> Self {
119 self.repo = Some(repo);
120 self
121 }
122
123 pub fn start_path(mut self, path: impl Into<PathBuf>) -> Self {
124 self.start_path = Some(path.into());
125 self
126 }
127
128 pub fn principal_fallback(mut self, principal: Option<(String, String)>) -> Self {
129 self.principal_fallback = principal;
130 self
131 }
132
133 pub fn fsmonitor_mode(mut self, mode: FsMonitorMode) -> Self {
134 self.fsmonitor_mode = mode;
135 self
136 }
137
138 pub fn verbosity(mut self, verbosity: Verbosity) -> Self {
139 self.verbosity = verbosity;
140 self
141 }
142
143 pub fn progress(mut self, progress: Arc<dyn ProgressSink>) -> Self {
144 self.progress = progress;
145 self
146 }
147
148 pub fn warnings(mut self, warnings: Arc<dyn WarningSink>) -> Self {
149 self.warnings = warnings;
150 self
151 }
152
153 pub fn op_id(mut self, op_id: impl Into<String>) -> Self {
154 self.op_id = Some(op_id.into());
155 self
156 }
157
158 pub fn build(self) -> ExecutionContext {
159 ExecutionContext {
160 repo: self.repo,
161 start_path: self.start_path,
162 principal_fallback: self.principal_fallback,
163 fsmonitor_mode: self.fsmonitor_mode,
164 verbosity: self.verbosity,
165 progress: self.progress,
166 warnings: self.warnings,
167 op_id: self.op_id,
168 }
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn default_context_has_no_repo_and_noop_sinks() {
178 let ctx = ExecutionContext::builder().build();
179
180 assert!(matches!(
181 ctx.require_repo(),
182 Err(HeddleError::RepositoryNotFound(_))
183 ));
184 assert_eq!(ctx.verbosity(), Verbosity::Normal);
185 assert!(ctx.op_id().is_none());
186 assert_eq!(ctx.fsmonitor_mode(), FsMonitorMode::Off);
187 assert!(ctx.principal_fallback().is_none());
188 ctx.progress().event(objects::ProgressEvent::Finish {
189 id: objects::TaskId(1),
190 });
191 ctx.warnings().warn(objects::Warning {
192 kind: "test".into(),
193 message: "ignored".to_string(),
194 });
195 }
196
197 #[test]
198 fn builder_sets_non_repo_fields() {
199 let ctx = ExecutionContext::builder()
200 .start_path("/tmp/heddle-verbs-context-test")
201 .principal_fallback(Some(("Luke".into(), "luke@example.com".into())))
202 .fsmonitor_mode(FsMonitorMode::Watchman)
203 .verbosity(Verbosity::Verbose)
204 .op_id("op-123")
205 .build();
206
207 assert_eq!(ctx.verbosity(), Verbosity::Verbose);
208 assert_eq!(ctx.op_id(), Some("op-123"));
209 assert_eq!(
210 ctx.start_path(),
211 Some(std::path::Path::new("/tmp/heddle-verbs-context-test"))
212 );
213 assert_eq!(ctx.principal_fallback(), Some(("Luke", "luke@example.com")));
214 assert_eq!(
215 ctx.worktree_status_options().fsmonitor.mode,
216 FsMonitorMode::Watchman
217 );
218 }
219}