1use std::path::{Path, PathBuf};
5
6use objects::object::Principal;
7
8pub const SET_PRINCIPAL_COMMAND: &str =
10 "heddle init --principal-name <name> --principal-email <email>";
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct InitPrincipalPlan {
15 pub status: &'static str,
16 pub source: Option<&'static str>,
17 pub name: Option<String>,
18 pub email: Option<String>,
19 pub recommended_action: Option<&'static str>,
20}
21
22impl InitPrincipalPlan {
23 pub fn configured(source: &'static str, principal: &Principal) -> Self {
24 Self {
25 status: "configured",
26 source: Some(source),
27 name: Some(principal.name_lossy().into_owned()),
28 email: Some(principal.email_lossy().into_owned()),
29 recommended_action: None,
30 }
31 }
32
33 pub fn not_configured() -> Self {
34 Self {
35 status: "not_configured",
36 source: None,
37 name: None,
38 email: None,
39 recommended_action: Some(SET_PRINCIPAL_COMMAND),
40 }
41 }
42}
43
44pub fn principal_is_unconfigured(principal: &Principal) -> bool {
46 principal.name_lossy().trim().is_empty() || principal.email_lossy().trim().is_empty()
47}
48
49pub fn select_init_principal(candidates: &[(&'static str, Principal)]) -> InitPrincipalPlan {
54 for (source, principal) in candidates {
55 if !principal_is_unconfigured(principal) {
56 return InitPrincipalPlan::configured(source, principal);
57 }
58 }
59 InitPrincipalPlan::not_configured()
60}
61
62pub fn init_side_effects(has_git: bool, principal_configured: bool) -> Vec<String> {
64 let mut side_effects = Vec::new();
65 if has_git {
66 side_effects.push("created Heddle sidecar for the existing Git repository".to_string());
67 side_effects.push("updated .git/info/exclude for Heddle metadata".to_string());
68 side_effects.push("left Git-tracked files untouched".to_string());
69 } else {
70 side_effects.push("created Heddle repository metadata".to_string());
71 }
72 if principal_configured {
73 side_effects.push("updated default principal attribution".to_string());
74 }
75 side_effects
76}
77
78pub fn resolve_absolute_path(cwd: &Path, path: &Path) -> PathBuf {
80 if path.is_absolute() {
81 path.to_path_buf()
82 } else {
83 cwd.join(path)
84 }
85}
86
87pub fn init_recommended_action() -> &'static str {
89 "heddle capture -m \"...\""
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn principal_selection_and_side_effects() {
98 let unconfigured = Principal::new("", "");
99 let ada = Principal::new("Ada", "ada@example.com");
100 let plan = select_init_principal(&[("environment", unconfigured), ("user_config", ada)]);
101 assert_eq!(plan.status, "configured");
102 assert_eq!(plan.source, Some("user_config"));
103 assert_eq!(plan.name.as_deref(), Some("Ada"));
104
105 let empty = select_init_principal(&[]);
106 assert_eq!(empty.status, "not_configured");
107 assert_eq!(empty.recommended_action, Some(SET_PRINCIPAL_COMMAND));
108
109 let se = init_side_effects(true, true);
110 assert!(se.iter().any(|s| s.contains("sidecar")));
111 assert!(se.iter().any(|s| s.contains("principal")));
112
113 assert_eq!(
114 resolve_absolute_path(Path::new("/cwd"), Path::new("rel")),
115 PathBuf::from("/cwd/rel")
116 );
117 assert_eq!(
118 resolve_absolute_path(Path::new("/cwd"), Path::new("/abs")),
119 PathBuf::from("/abs")
120 );
121 }
122}