dodot_lib/commands/
mod.rs1pub mod addignore;
9pub mod adopt;
10pub mod down;
11pub mod fill;
12pub mod init;
13pub mod list;
14pub mod status;
15pub mod up;
16
17#[cfg(test)]
18mod tests;
19
20use serde::Serialize;
21
22pub fn handler_symbol(handler: &str) -> &'static str {
26 match handler {
27 "symlink" => "➞",
28 "shell" => "⚙",
29 "path" => "+",
30 "homebrew" => "⚙",
31 "install" => "×",
32 _ => "?",
33 }
34}
35
36pub fn status_style(deployed: bool) -> &'static str {
38 if deployed {
39 "deployed"
40 } else {
41 "pending"
42 }
43}
44
45pub fn handler_description(handler: &str, rel_path: &str, user_target: Option<&str>) -> String {
47 match handler {
48 "symlink" => {
49 user_target
56 .map(str::to_string)
57 .unwrap_or_else(|| "<symlink>".to_string())
58 }
59 "shell" => "shell profile".into(),
60 "path" => format!("$PATH/{rel_path}"),
61 "install" => "run script".into(),
62 "homebrew" => "brew install".into(),
63 _ => String::new(),
64 }
65}
66
67#[derive(Debug, Clone, Serialize)]
69pub struct DisplayFile {
70 pub name: String,
71 pub symbol: String,
72 pub description: String,
73 pub status: String,
74 pub status_label: String,
75 pub handler: String,
76 #[serde(skip_serializing_if = "Option::is_none", default)]
82 pub note_ref: Option<u32>,
83}
84
85#[derive(Debug, Clone, Serialize)]
87pub struct DisplayPack {
88 pub name: String,
89 pub files: Vec<DisplayFile>,
90}
91
92#[derive(Debug, Clone, Serialize)]
96pub struct DisplayNote {
97 pub body: String,
98 #[serde(skip_serializing_if = "Option::is_none", default)]
99 pub hint: Option<String>,
100}
101
102#[derive(Debug, Clone, Serialize)]
104pub struct DisplayClaimant {
105 pub pack: String,
107 pub source: String,
109}
110
111#[derive(Debug, Clone, Serialize)]
113pub struct DisplayConflict {
114 pub kind: String,
117 pub target: String,
119 pub claimants: Vec<DisplayClaimant>,
120}
121
122impl DisplayConflict {
123 pub fn from_conflict(c: &crate::conflicts::Conflict, home: &std::path::Path) -> Self {
126 let kind = match c.kind {
127 crate::conflicts::ConflictKind::SymlinkTarget => "symlink",
128 crate::conflicts::ConflictKind::PathExecutable => "path",
129 };
130 let target = match c.kind {
131 crate::conflicts::ConflictKind::SymlinkTarget => shorten_path(&c.target, home),
132 crate::conflicts::ConflictKind::PathExecutable => c
133 .target
134 .file_name()
135 .map(|n| n.to_string_lossy().into_owned())
136 .unwrap_or_else(|| c.target.display().to_string()),
137 };
138 let claimants = c
139 .claimants
140 .iter()
141 .map(|cl| DisplayClaimant {
142 pack: cl.pack.clone(),
143 source: pack_relative_source(&cl.source, &cl.pack),
144 })
145 .collect();
146 DisplayConflict {
147 kind: kind.into(),
148 target,
149 claimants,
150 }
151 }
152}
153
154fn shorten_path(p: &std::path::Path, home: &std::path::Path) -> String {
155 if let Ok(rel) = p.strip_prefix(home) {
156 format!("~/{}", rel.display())
157 } else {
158 p.display().to_string()
159 }
160}
161
162fn pack_relative_source(source: &std::path::Path, pack: &str) -> String {
165 let s = source.to_string_lossy();
166 let marker = format!("/{pack}/");
167 if let Some(idx) = s.rfind(&marker) {
168 let rel = &s[idx + 1..];
169 return rel.to_string();
170 }
171 let fname = source
173 .file_name()
174 .map(|n| n.to_string_lossy().into_owned())
175 .unwrap_or_default();
176 format!("{pack}/{fname}")
177}
178
179#[derive(Debug, Clone, Serialize)]
182pub struct PackStatusResult {
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub message: Option<String>,
185 pub dry_run: bool,
186 pub packs: Vec<DisplayPack>,
187 #[serde(skip_serializing_if = "Vec::is_empty")]
191 pub warnings: Vec<String>,
192 #[serde(skip_serializing_if = "Vec::is_empty")]
196 pub notes: Vec<DisplayNote>,
197 #[serde(skip_serializing_if = "Vec::is_empty")]
199 pub conflicts: Vec<DisplayConflict>,
200 #[serde(skip_serializing_if = "Vec::is_empty")]
204 pub ignored_packs: Vec<String>,
205}