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