garden/cmds/
list.rs

1use anyhow::Result;
2use clap::Parser;
3
4use crate::cli::GardenOptions;
5use crate::{constants, display, model, query};
6
7/// Query tree status
8#[derive(Parser, Clone, Debug)]
9#[command(author, about, long_about)]
10pub struct ListOptions {
11    /// Display details for all trees, including missing trees
12    #[arg(short, long, default_value_t = false)]
13    all: bool,
14    /// Do not show commands
15    #[arg(long, short = 'C', default_value_t = false)]
16    no_commands: bool,
17    /// Display commands. Omits descriptions, remotes and links so that commands are more visible
18    #[arg(long = "commands", short = 'c', default_value_t = false)]
19    only_commands: bool,
20    /// Do not show gardens
21    #[arg(long, short = 'N', default_value_t = false)]
22    no_gardens: bool,
23    /// Do not show groups
24    #[arg(long, short = 'G', default_value_t = false)]
25    no_groups: bool,
26    /// Print trees in reverse order
27    #[arg(short, long, default_value_t = false)]
28    reverse: bool,
29    /// Sort trees using the specified mode [none, name, time]
30    #[arg(
31        long,
32        short,
33        require_equals = true,
34        num_args = 0..=1,
35        default_value_t = model::TreeSortMode::None,
36        default_missing_value = "name",
37        value_name = "MODE",
38        value_parser = model::TreeSortMode::parse_from_str,
39    )]
40    sort: model::TreeSortMode,
41    /// Increase verbosity level (default: 0)
42    #[arg(short, long, action = clap::ArgAction::Count)]
43    verbose: u8,
44    /// Display worktrees
45    #[arg(short, long, default_value_t = false)]
46    worktrees: bool,
47    /// Filter trees by name post-query using a glob pattern
48    #[arg(long, short, default_value = "*")]
49    trees: String,
50    /// Tree query for the gardens, groups or trees to display
51    queries: Vec<String>,
52}
53
54/// Main entry point for the "garden ls" command
55pub fn main(app_context: &model::ApplicationContext, options: &mut ListOptions) -> Result<()> {
56    if options.queries.is_empty() {
57        options.queries.push(string!("@*"));
58    }
59    list(app_context, options)
60}
61
62/// List tree details
63fn list(app_context: &model::ApplicationContext, options: &ListOptions) -> Result<()> {
64    let config = app_context.get_root_config();
65    let display_all = options.all;
66    let display_worktrees = options.worktrees;
67    let show_commands = !options.no_commands;
68    let only_commands = options.only_commands;
69    let show_gardens = !only_commands && !options.no_gardens;
70    let show_groups = !only_commands && !options.no_groups;
71    let verbose = app_context.options.verbose + options.verbose;
72    let mut needs_newline = false;
73
74    if app_context.options.debug_level(constants::DEBUG_LEVEL_LIST) > 0 {
75        debug!("queries: {:?}", options.queries);
76    }
77
78    for query in &options.queries {
79        // Resolve the tree query into a vector of tree contexts.
80        let mut contexts =
81            query::resolve_and_filter_trees(app_context, config, query, &options.trees);
82        match options.sort {
83            model::TreeSortMode::None => (),
84            model::TreeSortMode::Name => {
85                contexts.sort_by(|context_a, context_b| context_a.tree.cmp(&context_b.tree));
86            }
87            model::TreeSortMode::Time => {
88                contexts.sort_by(|context_a, context_b| {
89                    let config_a = match context_a.config {
90                        Some(config_id) => app_context.get_config(config_id),
91                        None => config,
92                    };
93                    let config_b = match context_b.config {
94                        Some(config_id) => app_context.get_config(config_id),
95                        None => config,
96                    };
97                    match (
98                        config_a
99                            .trees
100                            .get(&context_a.tree)
101                            .and_then(|tree| tree.pathbuf())
102                            .and_then(|pathbuf| pathbuf.metadata().ok())
103                            .and_then(|metadata| metadata.modified().ok()),
104                        config_b
105                            .trees
106                            .get(&context_b.tree)
107                            .and_then(|tree| tree.pathbuf())
108                            .and_then(|pathbuf| pathbuf.metadata().ok())
109                            .and_then(|metadata| metadata.modified().ok()),
110                    ) {
111                        (Some(a), Some(b)) => a.cmp(&b),
112                        (None, Some(_)) => std::cmp::Ordering::Less,
113                        (Some(_), None) => std::cmp::Ordering::Greater,
114                        (None, None) => std::cmp::Ordering::Equal,
115                    }
116                });
117            }
118        }
119        if options.reverse {
120            contexts.reverse();
121        }
122        // Loop over each context and display the tree.
123        for (idx, context) in contexts.iter().enumerate() {
124            let config = match context.config {
125                Some(config_id) => app_context.get_config(config_id),
126                None => config,
127            };
128            let tree = match config.trees.get(&context.tree) {
129                Some(tree) => tree,
130                None => continue,
131            };
132            let path = match tree.path_as_ref() {
133                Ok(path) => path,
134                Err(_) => continue,
135            };
136            // Sparse gardens/missing trees are okay -> skip these entries.
137            if !std::path::PathBuf::from(&path).exists() {
138                if needs_newline {
139                    println!();
140                }
141                display::print_missing_tree(tree, path, verbose);
142                if display_all {
143                    if !only_commands {
144                        display::print_tree_extended_details(
145                            app_context,
146                            context,
147                            tree,
148                            display_worktrees,
149                        );
150                    }
151                    if show_commands && !tree.commands.is_empty() {
152                        display::print_commands(&tree.commands);
153                    }
154                }
155                needs_newline = display_all;
156                continue;
157            }
158
159            if tree.is_symlink {
160                if needs_newline {
161                    println!();
162                }
163                display::print_symlink_tree_entry(tree, path, verbose);
164                needs_newline = false;
165                continue;
166            }
167
168            if idx > 0 {
169                println!();
170            }
171            display::print_tree(tree, config.tree_branches, verbose, false, false);
172            if !only_commands {
173                display::print_tree_extended_details(app_context, context, tree, display_worktrees);
174            }
175            if show_commands && !tree.commands.is_empty() {
176                display::print_commands(&tree.commands);
177            }
178            needs_newline = true;
179        }
180    }
181
182    if show_groups && !config.groups.is_empty() {
183        println!();
184        display::print_groups(&config.groups);
185    }
186
187    if show_gardens && !config.gardens.is_empty() {
188        println!();
189        display::print_gardens(&config.gardens);
190    }
191
192    if show_commands && !config.commands.is_empty() {
193        println!();
194        display::print_commands(&config.commands);
195    }
196
197    Ok(())
198}