garden/
display.rs

1use crate::{eval, git, model};
2use yansi::Paint;
3
4#[derive(Debug, Default, Clone, Copy)]
5pub struct DisplayOptions {
6    pub branches: bool,
7    pub commands: bool,
8    pub force: bool,
9    pub quiet: bool,
10    pub remotes: bool,
11    pub verbose: u8,
12    pub worktrees: bool,
13}
14
15pub(crate) fn display_missing_tree(
16    tree: &model::Tree,
17    path: &str,
18    verbose: u8,
19    force: bool,
20) -> String {
21    let skipped = if force {
22        String::new()
23    } else {
24        " (skipped)".bold().to_string()
25    };
26    if verbose > 0 {
27        format!(
28            "{} {} {}{}",
29            "#".black().bold(),
30            tree.get_name().black().bold(),
31            path.black().bold(),
32            skipped
33        )
34    } else {
35        format!(
36            "{} {}{}",
37            "#".black().bold(),
38            tree.get_name().black().bold(),
39            skipped
40        )
41    }
42}
43
44pub(crate) fn display_tree(
45    tree: &model::Tree,
46    path_str: &str,
47    tree_branches: bool,
48    verbose: u8,
49) -> String {
50    if verbose > 0 {
51        if tree_branches {
52            if let Some(path) = tree.canonical_pathbuf() {
53                if let Some(branch) = git::branch(&path) {
54                    return format!(
55                        "{} {} {}{}{} {}",
56                        "#".cyan(),
57                        tree.get_name().blue().bold(),
58                        "[".blue(),
59                        branch.green().bold(),
60                        "]".blue(),
61                        path_str.blue()
62                    );
63                }
64            }
65        }
66        format!(
67            "{} {} {}",
68            "#".cyan(),
69            tree.get_name().blue().bold(),
70            path_str.blue()
71        )
72    } else {
73        if tree_branches {
74            if let Some(path) = tree.canonical_pathbuf() {
75                if let Some(branch) = git::branch(&path) {
76                    return format!(
77                        "{} {} {}{}{}",
78                        "#".cyan(),
79                        tree.get_name().blue().bold(),
80                        "[".blue(),
81                        branch.green().bold(),
82                        "]".blue()
83                    );
84                }
85            }
86        }
87        format!("{} {}", "#".cyan(), tree.get_name().blue().bold())
88    }
89}
90
91/// Print a tree if it exists, otherwise print a missing tree
92pub(crate) fn print_tree(tree: &model::Tree, options: &DisplayOptions) -> bool {
93    if let Ok(path) = tree.path_as_ref() {
94        // Sparse gardens/missing trees are expected. Skip these entries.
95        if !std::path::PathBuf::from(&path).exists() {
96            if !options.quiet {
97                eprintln!(
98                    "{}",
99                    display_missing_tree(tree, path, options.verbose, options.force)
100                );
101            }
102            return false;
103        }
104
105        print_tree_details(tree, options.branches, options.verbose, options.quiet);
106        return true;
107    }
108    if !options.quiet {
109        eprintln!(
110            "{}",
111            display_missing_tree(tree, "(invalid-path)", options.verbose, options.force)
112        );
113    }
114
115    false
116}
117
118/// Print a tree.
119pub(crate) fn print_tree_details(
120    tree: &model::Tree,
121    tree_branches: bool,
122    verbose: u8,
123    quiet: bool,
124) {
125    if quiet {
126        return;
127    }
128    if let Ok(path) = tree.path_as_ref() {
129        eprintln!("{}", display_tree(tree, path, tree_branches, verbose));
130    }
131}
132
133/// Print non-grown / missing tree.
134pub(crate) fn print_missing_tree(tree: &model::Tree, path: &str, verbose: u8) {
135    if verbose > 0 {
136        println!(
137            "{} {} {}",
138            "#-".red().dim(),
139            tree.get_name().red(),
140            path.red().dim()
141        );
142    } else {
143        println!("{} {}", "#-".red().dim(), tree.get_name().red());
144    }
145}
146
147/// Print a symlink tree entry.
148pub(crate) fn print_symlink_tree_entry(tree: &model::Tree, path: &str, verbose: u8) {
149    let symlink = match tree.symlink_as_ref() {
150        Ok(symlink) => symlink,
151        Err(_) => return,
152    };
153    if verbose > 0 {
154        println!(
155            "{} {} {} {} {}",
156            "#+".cyan(),
157            tree.get_name().blue().bold(),
158            path.green(),
159            "->".green(),
160            symlink.yellow()
161        );
162    } else {
163        println!(
164            "{} {} {} {}",
165            "#".cyan(),
166            tree.get_name().blue().bold(),
167            "->".green(),
168            symlink.yellow()
169        );
170    }
171}
172
173/// Print the description, url, remotes and links for a tree
174pub(crate) fn print_tree_extended_details(
175    app_context: &model::ApplicationContext,
176    context: &model::TreeContext,
177    tree: &model::Tree,
178    display_options: &DisplayOptions,
179) {
180    let config = match context.config {
181        Some(config_id) => app_context.get_config(config_id),
182        None => app_context.get_root_config(),
183    };
184    if !tree.description.is_empty() {
185        println!("{}", tree.description.green());
186    }
187    if tree.is_worktree && !display_options.worktrees {
188        return;
189    }
190    if display_options.remotes && !tree.remotes.is_empty() {
191        println!("{}", "remotes:".blue());
192        for (name, remote) in &tree.remotes {
193            let value = eval::tree_variable(
194                app_context,
195                config,
196                None,
197                &context.tree,
198                context.garden.as_ref(),
199                remote,
200            );
201            println!("  {}{} {}", name.blue(), ":".blue(), value.yellow());
202        }
203    }
204    if !tree.links.is_empty() {
205        println!("{}", "links:".blue());
206        for link in &tree.links {
207            let value = eval::tree_variable(
208                app_context,
209                config,
210                None,
211                &context.tree,
212                context.garden.as_ref(),
213                link,
214            );
215            println!("  {} {}", "-".blue(), value.yellow());
216        }
217    }
218}
219
220/// Print a list of commands
221pub(crate) fn print_commands(commands: &model::MultiVariableMap) {
222    println!("{}", "commands:".blue());
223    for cmd in commands.keys() {
224        println!("  {} {}", "-".blue(), cmd.yellow());
225    }
226}
227
228/// Print groups
229pub(crate) fn print_groups(groups: &model::GroupMap) {
230    println!("{}", "groups:".blue());
231    for group in groups.keys() {
232        println!("  {} {}", "-".blue(), group.yellow());
233    }
234}
235
236/// Print gardens
237pub(crate) fn print_gardens(gardens: &model::GardenMap) {
238    println!("{}", "gardens:".blue());
239    for garden in gardens.keys() {
240        println!("  {} {}", "-".blue(), garden.yellow());
241    }
242}
243
244/// Print a command argument list
245pub fn print_command_vec(command: &[&str]) {
246    // Shell quote the list of commands.
247    let cmd_str = shell_words::join(command);
248    println!("{} {}", ":".cyan(), cmd_str.green(),);
249}
250
251/// Print a string command argument list
252pub fn print_command_string_vec(command: &[String]) {
253    let str_vec: Vec<&str> = command.iter().map(String::as_str).collect();
254    print_command_vec(&str_vec);
255}