repo_description: dev tools, env vars, task runner
code: use eyre::Result;
  use std::{collections::BTreeMap, sync::Arc};
  
  use crate::cli::args::ToolArg;
  use crate::config::Config;
  use crate::env_diff::EnvMap;
  use crate::shell::{ShellType, get_shell};
  use crate::toolset::{InstallOptions, Toolset, ToolsetBuilder};
  use indexmap::IndexSet;
  
  /// Exports env vars to activate mise a single time
  ///
  /// Use this if you don't want to permanently install mise. It's not necessary to
  /// use this if you have `mise activate` in your shell rc file.
  #[derive(Debug, clap::Args)]
  #[clap(visible_alias = "e", verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
  pub struct Env {
      /// Tool(s) to use
      #[clap(value_name = "TOOL@VERSION")]
      tool: Vec<ToolArg>,
  
      /// Output in dotenv format
      #[clap(long, short = 'D', overrides_with = "shell")]
      dotenv: bool,
  
      /// Output in JSON format
      #[clap(long, short = 'J', overrides_with = "shell")]
      json: bool,
  
      /// Shell type to generate environment variables for
      #[clap(long, short, overrides_with = "json")]
      shell: Option<ShellType>,
  
      /// Output in JSON format with additional information (source, tool)
      #[clap(long, overrides_with = "shell")]
      json_extended: bool,
  
      /// Only show redacted environment variables
      #[clap(long)]
      redacted: bool,
  
      /// Only show values of environment variables
      #[clap(long)]
      values: bool,
  }
  
  impl Env {
      pub async fn run(self) -> Result<()> {
          let mut config = Config::get().await?;
          let mut ts = ToolsetBuilder::new()
              .with_args(&self.tool)
              .build(&config)
              .await?;
          let (_, missing) = ts
              .install_missing_versions(&mut config, &InstallOptions::default())
              .await?;
          ts.notify_missing_versions(missing);
  
          // Pre-compute final_env when needed by --redacted or --dotenv to
          // avoid calling it twice. final_env includes the tools-only pass
          // whose redactions are not in config.env_results().
          let final_env = if self.redacted || self.dotenv {
              Some(ts.final_env(&config).await?)
          } else {
              None
          };
  
          let redacted_keys = if self.redacted {
              let env_results = config.env_results().await?;
              let mut keys = IndexSet::new();
              keys.extend(env_results.redactions.clone());
              if let Some((_, ref tools_env_results)) = final_env {
                  keys.extend(tools_env_results.redactions.clone());
              }
              keys.extend(config.redaction_keys());
              Some(keys)
          } else {
              None
          };
  
          if self.json {
              self.output_json(&config, ts, &redacted_keys).await
          } else if self.json_extended {
              self.output_extended_json(&config, ts, &redacted_keys).await
          } else if self.dotenv {
              self.output_dotenv(final_env.unwrap().0, &redacted_keys)
          } else if self.values {
              self.output_values(&config, ts, &redacted_keys).await
          } else {
              self.output_shell(&config, ts, &redacted_keys).await
          }
      }
  
      async fn output_json(
          &self,
          config: &Arc<Config>,
          ts: Toolset,
          redacted_keys: &Option<IndexSet<String>>,
      ) -> Result<()> {
          let mut env = ts.env_with_path(config).await?;
  
          if let Some(keys) = redacted_keys {
              env.retain(|k, _| self.should_include_key(k, keys));
          }
  
          miseprintln!("{}", serde_json::to_string_pretty(&env)?);
          Ok(())
      }
  
      async fn output_extended_json(
          &self,
          config: &Arc<Config>,
          ts: Toolset,
          redacted_keys: &Option<IndexSet<String>>,
      ) -> Result<()> {
          let mut res = BTreeMap::new();
  
          ts.env_with_path(config).await?.iter().for_each(|(k, v)| {
              res.insert(k.to_string(), BTreeMap::from([("value", v.to_string())]));
          });
  
          config.env_with_sources().await?.iter().for_each(|(k, v)| {
              res.insert(
                  k.to_string(),
                  BTreeMap::from([
                      ("value", v.0.to_string()),
                      ("source", v.1.to_string_lossy().into_owned()),
                  ]),
              );
          });
  
          let tool_map: BTreeMap<String, String> = ts
              .list_all_versions(config)
              .await?
              .into_iter()
              .map(|(b, tv)| {
                  (
                      b.id().into(),
                      tv.request
                          .source()
                          .path()
                          .map(|p| p.to_string_lossy().into_owned())
                          .unwrap_or_else(|| "".to_string()),
                  )
              })
              .collect();
  
          ts.env_from_tools(config)
              .await
              .iter()
              .for_each(|(name, value, tool_id)| {
                  res.insert(
                      name.to_string(),
                      BTreeMap::from([
                          ("value", value.to_string()),
                          ("tool", tool_id.to_string()),
                          (
                              "source",
                              tool_map
                                  .get(tool_id)
                                  .cloned()
                                  .unwrap_or_else(|| "unknown_source".to_string()),
                          ),
                      ]),
                  );
              });
  
          if let Some(keys) = redacted_keys {
              res.retain(|k, _| self.should_include_key(k, keys));
          }
  
          miseprintln!("{}", serde_json::to_string_pretty(&res)?);
          Ok(())
      }
  
      async fn output_shell(
          &self,
          config: &Arc<Config>,
          ts: Toolset,
          redacted_keys: &Option<IndexSet<String>>,
      ) -> Result<()> {
          let default_shell = get_shell(Some(ShellType::Bash)).unwrap();
          let shell = get_shell(self.shell).unwrap_or(default_shell);
          let mut env = ts.env_with_path(config).await?;
  
          if let Some(keys) = redacted_keys {
              env.retain(|k, _| self.should_include_key(k, keys));
          }
  
          for (k, v) in env {
              let k = k.to_string();
              let v = v.to_string();
              miseprint!("{}", shell.set_env(&k, &v))?;
          }
          Ok(())
      }
  
      fn output_dotenv(
          &self,
          mut env: EnvMap,
          redacted_keys: &Option<IndexSet<String>>,
      ) -> Result<()> {
          if let Some(keys) = redacted_keys {
              env.retain(|k, _| self.should_include_key(k, keys));
          }
  
          for (k, v) in env {
              let k = k.to_string();
              let v = v.to_string();
              miseprint!("{}={}\n", k, v)?;
          }
          Ok(())
      }
  
      async fn output_values(
          &self,
          config: &Arc<Config>,
          ts: Toolset,
          redacted_keys: &Option<IndexSet<String>>,
      ) -> Result<()> {
          let mut env = ts.env_with_path(config).await?;
  
          if let Some(keys) = redacted_keys {
              env.retain(|k, _| self.should_include_key(k, keys));
          }
  
          for (_, v) in env {
              miseprintln!("{}", v);
          }
          Ok(())
      }
  
      fn should_include_key(&self, key: &str, redacted_keys: &IndexSet<String>) -> bool {
          // Check if key matches any redaction pattern (supporting wildcards)
          redacted_keys.iter().any(|pattern| {
              if pattern.contains('*') {
                  // Handle wildcard patterns
                  if pattern == "*" {
                      true
                  } else if let Some(prefix) = pattern.strip_suffix('*') {
                      key.starts_with(prefix)
                  } else if let Some(suffix) = pattern.strip_prefix('*') {
                      key.ends_with(suffix)
                  } else {
                      // Pattern has * in the middle, not supported yet
                      false
                  }
              } else {
                  key == pattern
              }
          })
      }
  }
  
  static AFTER_LONG_HELP: &str = color_print::cstr!(
      r#"<bold><underline>Examples:</underline></bold>
  
      $ <bold>eval "$(mise env -s bash)"</bold>
      $ <bold>eval "$(mise env -s zsh)"</bold>
      $ <bold>mise env -s fish | source</bold>
      $ <bold>execx($(mise env -s xonsh))</bold>
  "#
  );
  