Skip to main content

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', action = clap::ArgAction::Count)]
19    commands: u8,
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.commands > 0;
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(
163                            app_context,
164                            context,
165                            &tree.commands,
166                            options.commands > 1,
167                        );
168                    }
169                }
170                needs_newline = display_all;
171                continue;
172            }
173
174            if tree.is_symlink {
175                if needs_newline {
176                    println!();
177                }
178                display::print_symlink_tree_entry(tree, path, verbose);
179                needs_newline = false;
180                continue;
181            }
182
183            if idx > 0 {
184                println!();
185            }
186            display_options.branches = config.tree_branches;
187            display::print_tree(tree, &display_options);
188            if !only_commands {
189                display::print_tree_extended_details(app_context, context, tree, &display_options);
190            }
191            if show_commands && !tree.commands.is_empty() {
192                display::print_commands(app_context, context, &tree.commands, options.commands > 1);
193            }
194            needs_newline = true;
195        }
196    }
197
198    if show_groups && !config.groups.is_empty() {
199        println!();
200        display::print_groups(&config.groups);
201    }
202
203    if show_gardens && !config.gardens.is_empty() {
204        println!();
205        display::print_gardens(&config.gardens);
206    }
207
208    if show_commands && !config.commands.is_empty() {
209        println!();
210        let context = model::TreeContext {
211            tree: "".to_string(),
212            config: config.get_id(),
213            garden: None,
214            group: None,
215        };
216        display::print_commands(
217            app_context,
218            &context,
219            &config.commands,
220            options.commands > 1,
221        );
222    }
223
224    Ok(())
225}