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 if let Some(target) = user_target {
50 target.to_string()
51 } else {
52 let display_path = if !rel_path.contains('/') && rel_path.starts_with("dot.") {
54 format!(".{}", &rel_path[4..])
55 } else {
56 format!(".{rel_path}")
57 };
58 format!("~/{display_path}")
59 }
60 }
61 "shell" => "shell profile".into(),
62 "path" => format!("$PATH/{rel_path}"),
63 "install" => "run script".into(),
64 "homebrew" => "brew install".into(),
65 _ => String::new(),
66 }
67}
68
69#[derive(Debug, Clone, Serialize)]
71pub struct DisplayFile {
72 pub name: String,
73 pub symbol: String,
74 pub description: String,
75 pub status: String,
76 pub status_label: String,
77 pub handler: String,
78}
79
80#[derive(Debug, Clone, Serialize)]
82pub struct DisplayPack {
83 pub name: String,
84 pub files: Vec<DisplayFile>,
85 #[serde(skip_serializing_if = "Vec::is_empty", default)]
89 pub footnotes: Vec<String>,
90}
91
92#[derive(Debug, Clone, Serialize)]
94pub struct DisplayClaimant {
95 pub pack: String,
97 pub source: String,
99}
100
101#[derive(Debug, Clone, Serialize)]
103pub struct DisplayConflict {
104 pub kind: String,
107 pub target: String,
109 pub claimants: Vec<DisplayClaimant>,
110}
111
112impl DisplayConflict {
113 pub fn from_conflict(c: &crate::conflicts::Conflict, home: &std::path::Path) -> Self {
116 let kind = match c.kind {
117 crate::conflicts::ConflictKind::SymlinkTarget => "symlink",
118 crate::conflicts::ConflictKind::PathExecutable => "path",
119 };
120 let target = match c.kind {
121 crate::conflicts::ConflictKind::SymlinkTarget => shorten_path(&c.target, home),
122 crate::conflicts::ConflictKind::PathExecutable => c
123 .target
124 .file_name()
125 .map(|n| n.to_string_lossy().into_owned())
126 .unwrap_or_else(|| c.target.display().to_string()),
127 };
128 let claimants = c
129 .claimants
130 .iter()
131 .map(|cl| DisplayClaimant {
132 pack: cl.pack.clone(),
133 source: pack_relative_source(&cl.source, &cl.pack),
134 })
135 .collect();
136 DisplayConflict {
137 kind: kind.into(),
138 target,
139 claimants,
140 }
141 }
142}
143
144fn shorten_path(p: &std::path::Path, home: &std::path::Path) -> String {
145 if let Ok(rel) = p.strip_prefix(home) {
146 format!("~/{}", rel.display())
147 } else {
148 p.display().to_string()
149 }
150}
151
152fn pack_relative_source(source: &std::path::Path, pack: &str) -> String {
155 let s = source.to_string_lossy();
156 let marker = format!("/{pack}/");
157 if let Some(idx) = s.rfind(&marker) {
158 let rel = &s[idx + 1..];
159 return rel.to_string();
160 }
161 let fname = source
163 .file_name()
164 .map(|n| n.to_string_lossy().into_owned())
165 .unwrap_or_default();
166 format!("{pack}/{fname}")
167}
168
169#[derive(Debug, Clone, Serialize)]
172pub struct PackStatusResult {
173 #[serde(skip_serializing_if = "Option::is_none")]
174 pub message: Option<String>,
175 pub dry_run: bool,
176 pub packs: Vec<DisplayPack>,
177 #[serde(skip_serializing_if = "Vec::is_empty")]
178 pub warnings: Vec<String>,
179 #[serde(skip_serializing_if = "Vec::is_empty")]
181 pub conflicts: Vec<DisplayConflict>,
182 #[serde(skip_serializing_if = "Vec::is_empty")]
186 pub ignored_packs: Vec<String>,
187}