Skip to main content

dodot_lib/commands/
list.rs

1//! `list` command — show all available packs.
2
3use serde::Serialize;
4
5use crate::packs;
6use crate::packs::orchestration::ExecutionContext;
7use crate::Result;
8
9#[derive(Debug, Clone, Serialize)]
10pub struct ListResult {
11    pub packs: Vec<ListPack>,
12}
13
14#[derive(Debug, Clone, Serialize)]
15pub struct ListPack {
16    /// User-facing pack name. For prefixed packs (`010-nvim`) this is
17    /// the stripped form (`nvim`); for unprefixed packs it equals the
18    /// directory name.
19    pub name: String,
20    pub ignored: bool,
21}
22
23/// List all packs in the dotfiles root.
24///
25/// Packs appear in the order dodot would apply them (lexicographic by
26/// on-disk directory name); see the `packs` module docs for the pack
27/// ordering contract. Goes through [`packs::scan_packs`] so list
28/// output respects the same `pack.ignore` patterns and surfaces the
29/// same scan-time errors (empty-stem prefix directories, ordering
30/// collisions) that every other command does — `dodot list` should
31/// never show ambiguous duplicates that `dodot up` would refuse.
32pub fn list(ctx: &ExecutionContext) -> Result<ListResult> {
33    let root_config = ctx.config_manager.root_config()?;
34    let scanned = packs::scan_packs(
35        ctx.fs.as_ref(),
36        ctx.paths.dotfiles_root(),
37        &root_config.pack.ignore,
38    )?;
39
40    // Two streams in: active packs (already carry display_name) and
41    // dodotignore-marked dirs (raw names). Merge into one list with
42    // the `ignored` flag, preserving lex order on the on-disk name
43    // so the displayed order still matches deploy order.
44    let mut entries: Vec<(String, ListPack)> = Vec::new();
45    for p in scanned.packs {
46        entries.push((
47            p.name.clone(),
48            ListPack {
49                name: p.display_name,
50                ignored: false,
51            },
52        ));
53    }
54    for dir in scanned.ignored {
55        let display = packs::display_name_for(&dir).to_string();
56        entries.push((
57            dir,
58            ListPack {
59                name: display,
60                ignored: true,
61            },
62        ));
63    }
64    entries.sort_by(|a, b| a.0.cmp(&b.0));
65
66    Ok(ListResult {
67        packs: entries.into_iter().map(|(_, p)| p).collect(),
68    })
69}