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