Skip to main content

verbs/
context.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Execution context shared by future facade operations.
3
4use std::path::PathBuf;
5
6use objects::HeddleError;
7use repo::{FsMonitorMode, Repository, WorktreeStatusOptions};
8
9/// Semantic execution state for embeddable Heddle operations.
10///
11/// Carries only resolved values: the caller (usually the CLI) resolves user
12/// config before construction, so this crate never reads config files.
13pub struct ExecutionContext {
14    repo: Option<Repository>,
15    start_path: Option<PathBuf>,
16    principal_fallback: Option<(String, String)>,
17    hosted_principal: Option<(String, String)>,
18    fsmonitor_mode: FsMonitorMode,
19}
20
21impl ExecutionContext {
22    pub fn builder() -> ExecutionContextBuilder {
23        ExecutionContextBuilder::default()
24    }
25
26    pub fn require_repo(&self) -> Result<&Repository, HeddleError> {
27        self.repo
28            .as_ref()
29            .ok_or_else(|| HeddleError::RepositoryNotFound(PathBuf::from(".")))
30    }
31
32    pub fn repo(&self) -> Option<&Repository> {
33        self.repo.as_ref()
34    }
35
36    pub fn start_path(&self) -> Option<&std::path::Path> {
37        self.start_path.as_deref()
38    }
39
40    /// User-config principal fallback as an optional `(name, email)` pair.
41    pub fn principal_fallback(&self) -> Option<(&str, &str)> {
42        self.principal_fallback
43            .as_ref()
44            .map(|(name, email)| (name.as_str(), email.as_str()))
45    }
46
47    /// Hosted-account principal derived from a local login, used only when no
48    /// local principal is configured.
49    pub fn hosted_principal(&self) -> Option<(&str, &str)> {
50        self.hosted_principal
51            .as_ref()
52            .map(|(name, email)| (name.as_str(), email.as_str()))
53    }
54
55    /// Resolved fsmonitor mode for worktree-status hot paths.
56    pub fn fsmonitor_mode(&self) -> FsMonitorMode {
57        self.fsmonitor_mode
58    }
59
60    pub fn worktree_status_options(&self) -> WorktreeStatusOptions {
61        WorktreeStatusOptions {
62            fsmonitor: repo::FsMonitorSettings {
63                mode: self.fsmonitor_mode,
64            },
65        }
66    }
67}
68
69/// Builder for [`ExecutionContext`].
70#[derive(Default)]
71pub struct ExecutionContextBuilder {
72    repo: Option<Repository>,
73    start_path: Option<PathBuf>,
74    principal_fallback: Option<(String, String)>,
75    hosted_principal: Option<(String, String)>,
76    fsmonitor_mode: FsMonitorMode,
77}
78
79impl ExecutionContextBuilder {
80    pub fn repo(mut self, repo: Repository) -> Self {
81        self.repo = Some(repo);
82        self
83    }
84
85    pub fn start_path(mut self, path: impl Into<PathBuf>) -> Self {
86        self.start_path = Some(path.into());
87        self
88    }
89
90    pub fn principal_fallback(mut self, principal: Option<(String, String)>) -> Self {
91        self.principal_fallback = principal;
92        self
93    }
94
95    pub fn hosted_principal(mut self, principal: Option<(String, String)>) -> Self {
96        self.hosted_principal = principal;
97        self
98    }
99
100    pub fn fsmonitor_mode(mut self, mode: FsMonitorMode) -> Self {
101        self.fsmonitor_mode = mode;
102        self
103    }
104
105    pub fn build(self) -> ExecutionContext {
106        ExecutionContext {
107            repo: self.repo,
108            start_path: self.start_path,
109            principal_fallback: self.principal_fallback,
110            hosted_principal: self.hosted_principal,
111            fsmonitor_mode: self.fsmonitor_mode,
112        }
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn default_context_has_no_repo_and_noop_sinks() {
122        let ctx = ExecutionContext::builder().build();
123
124        assert!(matches!(
125            ctx.require_repo(),
126            Err(HeddleError::RepositoryNotFound(_))
127        ));
128        assert_eq!(ctx.fsmonitor_mode(), FsMonitorMode::Off);
129        assert!(ctx.principal_fallback().is_none());
130    }
131
132    #[test]
133    fn builder_sets_non_repo_fields() {
134        let ctx = ExecutionContext::builder()
135            .start_path("/tmp/heddle-verbs-context-test")
136            .principal_fallback(Some(("Luke".into(), "luke@example.com".into())))
137            .fsmonitor_mode(FsMonitorMode::Watchman)
138            .build();
139
140        assert_eq!(
141            ctx.start_path(),
142            Some(std::path::Path::new("/tmp/heddle-verbs-context-test"))
143        );
144        assert_eq!(ctx.principal_fallback(), Some(("Luke", "luke@example.com")));
145        assert_eq!(
146            ctx.worktree_status_options().fsmonitor.mode,
147            FsMonitorMode::Watchman
148        );
149    }
150}