1pub fn default_mount_name(child_path: &str) -> &str {
13 child_path
14 .trim_end_matches('/')
15 .rsplit('/')
16 .find(|s| !s.is_empty())
17 .unwrap_or(child_path)
18}
19
20pub fn short_id(id: &str) -> &str {
24 &id[..id.len().min(12)]
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum ChildEdgeStatusKind {
30 Unspecified,
32 UpToDate,
34 FastForwardable,
36 Diverged,
38 NoChildHead,
40}
41
42pub fn edge_status_from_i32(status: i32) -> ChildEdgeStatusKind {
46 match status {
47 1 => ChildEdgeStatusKind::UpToDate,
48 2 => ChildEdgeStatusKind::FastForwardable,
49 3 => ChildEdgeStatusKind::Diverged,
50 4 => ChildEdgeStatusKind::NoChildHead,
51 _ => ChildEdgeStatusKind::Unspecified,
52 }
53}
54
55pub fn edge_status_label(kind: ChildEdgeStatusKind) -> &'static str {
57 match kind {
58 ChildEdgeStatusKind::UpToDate => "up-to-date",
59 ChildEdgeStatusKind::FastForwardable => "fast-forwardable",
60 ChildEdgeStatusKind::Diverged => "diverged",
61 ChildEdgeStatusKind::NoChildHead => "no-child-head",
62 ChildEdgeStatusKind::Unspecified => "unspecified",
63 }
64}
65
66pub fn edge_status_label_from_i32(status: i32) -> &'static str {
68 edge_status_label(edge_status_from_i32(status))
69}
70
71pub fn spool_attach_message(child: &str, parent: &str, mount: &str) -> String {
73 format!("Attached {child} under {parent} at '{mount}'")
74}
75
76pub fn spool_detach_message(mount_name: &str, parent: &str, removed: bool) -> String {
78 if removed {
79 format!("Detached '{mount_name}' from {parent}")
80 } else {
81 format!("No child mounted at '{mount_name}' under {parent} (nothing to detach).")
82 }
83}
84
85pub fn spool_children_empty_message(parent: &str) -> String {
87 format!("{parent} has no child spools.")
88}
89
90pub fn spool_children_header(count: usize, parent: &str) -> String {
92 format!("{count} child spool(s) of {parent}:")
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn default_mount_name_uses_last_path_segment() {
101 assert_eq!(default_mount_name("acme/lib"), "lib");
102 assert_eq!(default_mount_name("acme/team/lib"), "lib");
103 assert_eq!(default_mount_name("acme/lib/"), "lib");
104 assert_eq!(default_mount_name("solo"), "solo");
105 assert_eq!(default_mount_name("/"), "/");
106 }
107
108 #[test]
109 fn short_id_truncates_to_twelve() {
110 assert_eq!(short_id("abcdefghijklmnop"), "abcdefghijkl");
111 assert_eq!(short_id("abc"), "abc");
112 }
113
114 #[test]
115 fn edge_status_label_covers_every_variant() {
116 assert_eq!(
117 edge_status_label(ChildEdgeStatusKind::UpToDate),
118 "up-to-date"
119 );
120 assert_eq!(
121 edge_status_label(ChildEdgeStatusKind::FastForwardable),
122 "fast-forwardable"
123 );
124 assert_eq!(edge_status_label(ChildEdgeStatusKind::Diverged), "diverged");
125 assert_eq!(
126 edge_status_label(ChildEdgeStatusKind::NoChildHead),
127 "no-child-head"
128 );
129 assert_eq!(
130 edge_status_label(ChildEdgeStatusKind::Unspecified),
131 "unspecified"
132 );
133 assert_eq!(edge_status_from_i32(0), ChildEdgeStatusKind::Unspecified);
135 assert_eq!(edge_status_from_i32(1), ChildEdgeStatusKind::UpToDate);
136 assert_eq!(
137 edge_status_from_i32(2),
138 ChildEdgeStatusKind::FastForwardable
139 );
140 assert_eq!(edge_status_from_i32(3), ChildEdgeStatusKind::Diverged);
141 assert_eq!(edge_status_from_i32(4), ChildEdgeStatusKind::NoChildHead);
142 assert_eq!(edge_status_from_i32(99), ChildEdgeStatusKind::Unspecified);
143 assert_eq!(edge_status_label_from_i32(1), "up-to-date");
144 }
145
146 #[test]
147 fn attach_detach_messages() {
148 assert_eq!(
149 spool_attach_message("acme/lib", "acme/app", "lib"),
150 "Attached acme/lib under acme/app at 'lib'"
151 );
152 assert!(spool_detach_message("lib", "acme/app", true).starts_with("Detached"));
153 assert!(spool_detach_message("lib", "acme/app", false).contains("nothing to detach"));
154 }
155}