Skip to main content

dodot_lib/commands/
list.rs

1//! `list` command — show all available packs.
2
3use serde::Serialize;
4
5use crate::packs::orchestration::ExecutionContext;
6use crate::Result;
7
8#[derive(Debug, Clone, Serialize)]
9pub struct ListResult {
10    pub packs: Vec<ListPack>,
11}
12
13#[derive(Debug, Clone, Serialize)]
14pub struct ListPack {
15    pub name: String,
16    pub ignored: bool,
17}
18
19/// List all packs in the dotfiles root.
20pub fn list(ctx: &ExecutionContext) -> Result<ListResult> {
21    let _root_config = ctx.config_manager.root_config()?;
22
23    // Get all directories (including ignored ones for display)
24    let entries = ctx.fs.read_dir(ctx.paths.dotfiles_root())?;
25    let mut packs = Vec::new();
26
27    for entry in entries {
28        if !entry.is_dir {
29            continue;
30        }
31        if entry.name.starts_with('.') && entry.name != ".config" {
32            continue;
33        }
34        if !is_valid_pack_name(&entry.name) {
35            continue;
36        }
37
38        let ignored = ctx.fs.exists(&entry.path.join(".dodotignore"));
39        packs.push(ListPack {
40            name: entry.name,
41            ignored,
42        });
43    }
44
45    packs.sort_by(|a, b| a.name.cmp(&b.name));
46
47    Ok(ListResult { packs })
48}
49
50fn is_valid_pack_name(name: &str) -> bool {
51    !name.is_empty()
52        && name
53            .chars()
54            .all(|c| c.is_alphanumeric() || c == '_' || c == '-' || c == '.')
55}