Skip to main content

garden/cmds/
shell.rs

1use anyhow::Result;
2use clap::Parser;
3
4use crate::{cmd, errors, eval, model, query};
5
6/// Open a shell in a garden environment
7#[derive(Parser, Clone, Debug)]
8#[command(author, about, long_about)]
9pub struct ShellOptions {
10    /// Increase verbosity level (default: 0)
11    #[arg(short, long, action = clap::ArgAction::Count)]
12    verbose: u8,
13    /// Query for trees to build an environment
14    #[arg(default_value = ".")]
15    query: String,
16    /// Tree to chdir into
17    tree: Option<String>,
18}
19
20pub fn main(app_context: &model::ApplicationContext, options: &ShellOptions) -> Result<()> {
21    let config = app_context.get_root_config_mut();
22    let contexts = query::resolve_trees(app_context, config, None, &options.query);
23    if contexts.is_empty() {
24        return Err(errors::GardenError::EmptyTreeQueryResult(options.query.clone()).into());
25    }
26    let mut context = contexts[0].clone();
27
28    // If a tree's name in the returned contexts exactly matches the tree
29    // query that was used to find it then chdir into that tree.
30    // This makes it convenient to have gardens and trees with the same name.
31    for ctx in &contexts {
32        if ctx.tree == options.query {
33            context = ctx.clone();
34            break;
35        }
36    }
37
38    if let Some(tree) = &options.tree {
39        let mut found = false;
40        if let Some(ctx) = query::tree_from_name(config, tree, None, None) {
41            for query_ctx in &contexts {
42                if ctx.tree == query_ctx.tree {
43                    context = query_ctx.clone();
44                    found = true;
45                    break;
46                }
47            }
48        } else {
49            error!("unable to find '{}': No tree exists with that name", tree);
50        }
51        if !found {
52            error!(
53                "'{}' was not found in the tree query '{}'",
54                tree, options.query
55            );
56        }
57    }
58
59    // Evaluate garden.shell
60    let graft_config = context.config.map(|id| app_context.get_config(id));
61    let shell_expr = if config.interactive_shell.is_empty() {
62        &config.shell
63    } else {
64        &config.interactive_shell
65    };
66    let shell = eval::tree_value(
67        app_context,
68        config,
69        graft_config,
70        shell_expr,
71        &context.tree,
72        context.garden.as_ref(),
73    );
74
75    let verbose = app_context.options.verbose + options.verbose;
76    let quiet = verbose == 0;
77    if let Some(value) = shlex::split(&shell) {
78        cmd::exec_in_context(
79            app_context,
80            config,
81            &context,
82            quiet,
83            verbose,
84            /*dry_run*/ false,
85            &value,
86        )
87        .map_err(|err| err.into())
88    } else {
89        Err(errors::GardenError::InvalidConfiguration {
90            msg: format!("unable to shlex::split '{shell}'"),
91        }
92        .into())
93    }
94}