Skip to main content

dodot_lib/commands/
mod.rs

1//! Public command API — the entry points for all dodot operations.
2//!
3//! Each function returns a `Result<T>` where `T: Serialize`. These
4//! types are the contract with standout's rendering layer — they
5//! carry everything needed to produce both human-readable (template)
6//! and machine-readable (JSON) output.
7
8pub 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
22// ── Shared display types ────────────────────────────────────────
23
24/// Handler symbols matching the Go implementation.
25pub fn handler_symbol(handler: &str) -> &'static str {
26    match handler {
27        "symlink" => "➞",
28        "shell" => "⚙",
29        "path" => "+",
30        "homebrew" => "⚙",
31        "install" => "×",
32        _ => "?",
33    }
34}
35
36/// Human-readable status label.
37pub fn status_label(handler: &str, deployed: bool) -> String {
38    match (handler, deployed) {
39        ("symlink", true) => "deployed".into(),
40        ("symlink", false) => "pending".into(),
41        ("shell", true) => "sourced".into(),
42        ("shell", false) => "not sourced".into(),
43        ("path", true) => "in PATH".into(),
44        ("path", false) => "not in PATH".into(),
45        ("install", true) => "installed".into(),
46        ("install", false) => "never run".into(),
47        ("homebrew", true) => "installed".into(),
48        ("homebrew", false) => "not installed".into(),
49        (_, true) => "deployed".into(),
50        (_, false) => "pending".into(),
51    }
52}
53
54/// Status string for standout template tag matching (maps to theme style names).
55pub fn status_style(deployed: bool) -> &'static str {
56    if deployed {
57        "deployed"
58    } else {
59        "pending"
60    }
61}
62
63/// Human-readable handler description for a file.
64pub fn handler_description(handler: &str, rel_path: &str, user_target: Option<&str>) -> String {
65    match handler {
66        "symlink" => {
67            if let Some(target) = user_target {
68                target.to_string()
69            } else {
70                // Apply dot. prefix convention: dot.bashrc → ~/.bashrc
71                let display_path = if !rel_path.contains('/') && rel_path.starts_with("dot.") {
72                    format!(".{}", &rel_path[4..])
73                } else {
74                    format!(".{rel_path}")
75                };
76                format!("~/{display_path}")
77            }
78        }
79        "shell" => "shell profile".into(),
80        "path" => format!("$PATH/{rel_path}"),
81        "install" => "run script".into(),
82        "homebrew" => "brew install".into(),
83        _ => String::new(),
84    }
85}
86
87/// A file entry for pack status display.
88#[derive(Debug, Clone, Serialize)]
89pub struct DisplayFile {
90    pub name: String,
91    pub symbol: String,
92    pub description: String,
93    pub status: String,
94    pub status_label: String,
95    pub handler: String,
96}
97
98/// A pack entry for status display.
99#[derive(Debug, Clone, Serialize)]
100pub struct DisplayPack {
101    pub name: String,
102    pub files: Vec<DisplayFile>,
103}
104
105/// Result type for commands that display pack status
106/// (status, up, down).
107#[derive(Debug, Clone, Serialize)]
108pub struct PackStatusResult {
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub message: Option<String>,
111    pub dry_run: bool,
112    pub packs: Vec<DisplayPack>,
113    #[serde(skip_serializing_if = "Vec::is_empty")]
114    pub warnings: Vec<String>,
115}