Skip to content

Commit

Permalink
added "en" command
Browse files Browse the repository at this point in the history
Fixes #1655
  • Loading branch information
jdx committed Nov 28, 2024
1 parent 46e9b8a commit 0085704
Show file tree
Hide file tree
Showing 15 changed files with 263 additions and 56 deletions.
3 changes: 3 additions & 0 deletions docs/.vitepress/cli_commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ export const commands: { [key: string]: Command } = {
doctor: {
hide: false,
},
en: {
hide: false,
},
env: {
hide: false,
},
Expand Down
44 changes: 44 additions & 0 deletions docs/cli/en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# `mise en`

- **Usage**: `mise en [-s --shell <SHELL>] [DIR]`
- **Source code**: [`src/cli/en.rs`](https://github.com/jdx/mise/blob/main/src/cli/en.rs)

[experimental] starts a new shell with the mise environment built from the current configuration

This is an alternative to `mise activate` that allows you to explicitly start a mise session.
It will have the tools and environment variables in the configs loaded.
Note that changing directories will not update the mise environment.

It's a lightweight alternative to `mise activate` if you don't want to put that into your shell rc but
still don't want to deal with shims. It probably only makes sense for interactive use-cases.

It's sort of akin to manually running `source .venv/bin/activate` for Python virtualenvs if you're
familiar with that workflow.

## Arguments

### `[DIR]`

Directory to start the shell in

**Default:** `.`

## Flags

### `-s --shell <SHELL>`

Shell to start

Defaults to $SHELL

Examples:

$ mise en .
$ node -v
v20.0.0

Skip loading bashrc:
$ mise en -s "bash --norc"

Skip loading zshrc:
$ mise en -s "zsh -f"
1 change: 1 addition & 0 deletions docs/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Answer yes to all confirmation prompts
- [`mise direnv <SUBCOMMAND>`](/cli/direnv.md)
- [`mise direnv activate`](/cli/direnv/activate.md)
- [`mise doctor`](/cli/doctor.md)
- [`mise en [-s --shell <SHELL>] [DIR]`](/cli/en.md)
- [`mise env [FLAGS] [TOOL@VERSION]...`](/cli/env.md)
- [`mise exec [FLAGS] [TOOL@VERSION]... [COMMAND]...`](/cli/exec.md)
- [`mise generate <SUBCOMMAND>`](/cli/generate.md)
Expand Down
3 changes: 3 additions & 0 deletions man/man1/mise.1
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ Output direnv function to use mise inside direnv
mise\-doctor(1)
Check mise installation for possible problems
.TP
mise\-en(1)
[experimental] starts a new shell with the mise environment built from the current configuration
.TP
mise\-env(1)
Exports env vars to activate mise a single time
.TP
Expand Down
24 changes: 24 additions & 0 deletions mise.usage.kdl
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,30 @@ cmd "doctor" help="Check mise installation for possible problems" {
[WARN] plugin node is not installed
"
}
cmd "en" help="[experimental] starts a new shell with the mise environment built from the current configuration" {
long_help r"[experimental] starts a new shell with the mise environment built from the current configuration

This is an alternative to `mise activate` that allows you to explicitly start a mise session.
It will have the tools and environment variables in the configs loaded.
Note that changing directories will not update the mise environment."
after_long_help r#"Examples:

$ mise en .
$ node -v
v20.0.0

Skip loading bashrc:
$ mise en -s "bash --norc"

Skip loading zshrc:
$ mise en -s "zsh -f"
"#
flag "-s --shell" help="Shell to start" {
long_help "Shell to start\n\nDefaults to $SHELL"
arg "<SHELL>"
}
arg "[DIR]" help="Directory to start the shell in" default="."
}
cmd "env" help="Exports env vars to activate mise a single time" {
alias "e"
long_help r"Exports env vars to activate mise a single time
Expand Down
2 changes: 1 addition & 1 deletion settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,7 @@ description = "List of default shell arguments for unix to be used with `file`.
env = "MISE_UNIX_DEFAULT_INLINE_SHELL_ARGS"
type = "ListString"
rust_type = "Vec<String>"
default = ["sh", "-c"]
default = ["sh", "-c", "-o", "pipefail", "-o", "errexit"]
parse_env = "list_by_comma"
description = "List of default shell arguments for unix to be used with inline commands. For example, `sh`, `-c` for sh."

Expand Down
59 changes: 59 additions & 0 deletions src/cli/en.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use crate::cli::exec::Exec;
use crate::config::Settings;
use std::path::PathBuf;

use crate::env;

/// [experimental] starts a new shell with the mise environment built from the current configuration
///
/// This is an alternative to `mise activate` that allows you to explicitly start a mise session.
/// It will have the tools and environment variables in the configs loaded.
/// Note that changing directories will not update the mise environment.
#[derive(Debug, clap::Args)]
#[clap(verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
pub struct En {
/// Directory to start the shell in
#[clap(default_value = ".", verbatim_doc_comment, value_hint = clap::ValueHint::DirPath)]
pub dir: PathBuf,

/// Shell to start
///
/// Defaults to $SHELL
#[clap(verbatim_doc_comment, long, short = 's', env = "MISE_SHELL")]
pub shell: Option<String>,
}

impl En {
pub fn run(self) -> eyre::Result<()> {
let settings = Settings::get();
settings.ensure_experimental("en")?;

env::set_current_dir(&self.dir)?;
let shell = self.shell.unwrap_or((*env::SHELL).clone());
let command = shell_words::split(&shell).map_err(|e| eyre::eyre!(e))?;

Exec {
tool: vec![],
raw: false,
jobs: None,
c: None,
command: Some(command),
}
.run()
}
}

static AFTER_LONG_HELP: &str = color_print::cstr!(
r#"<bold><underline>Examples:</underline></bold>
$ <bold>mise en .</bold>
$ <bold>node -v</bold>
v20.0.0
Skip loading bashrc:
$ <bold>mise en -s "bash --norc"</bold>
Skip loading zshrc:
$ <bold>mise en -s "zsh -f"</bold>
"#
);
31 changes: 25 additions & 6 deletions src/cli/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ pub struct Exec {

/// Command string to execute (same as --command)
#[clap(conflicts_with = "c", required_unless_present = "c", last = true)]
pub command: Option<Vec<OsString>>,
pub command: Option<Vec<String>>,

/// Command string to execute
#[clap(short, long = "command", value_hint = ValueHint::CommandString, conflicts_with = "command")]
pub c: Option<OsString>,
pub c: Option<String>,

/// Number of jobs to run in parallel
/// [default: 4]
Expand Down Expand Up @@ -74,9 +74,28 @@ impl Exec {
ts.notify_if_versions_missing()
});

let (program, args) = parse_command(&env::SHELL, &self.command, &self.c);
let (program, mut args) = parse_command(&env::SHELL, &self.command, &self.c);
let env = measure!("env_with_path", { ts.env_with_path(&CONFIG)? });

if program.rsplit('/').next() == Some("fish") {
let mut cmd = vec![];
for (k, v) in env.iter().filter(|(k, _)| *k != "PATH") {
cmd.push(format!(
"set -gx {} {}",
shell_escape::escape(k.into()),
shell_escape::escape(v.into())
));
}
for p in ts.list_final_paths()? {
cmd.push(format!(
"fish_add_path -gm {}",
shell_escape::escape(p.to_string_lossy())
));
}
args.insert(0, cmd.join("\n"));
args.insert(0, "-C".into());
}

time!("exec");
self.exec(program, args, env)
}
Expand Down Expand Up @@ -142,9 +161,9 @@ impl Exec {

fn parse_command(
shell: &str,
command: &Option<Vec<OsString>>,
c: &Option<OsString>,
) -> (OsString, Vec<OsString>) {
command: &Option<Vec<String>>,
c: &Option<String>,
) -> (String, Vec<String>) {
match (&command, &c) {
(Some(command), _) => {
let (program, args) = command.split_first().unwrap();
Expand Down
6 changes: 6 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod current;
mod deactivate;
mod direnv;
mod doctor;
mod en;
mod env;
pub mod exec;
mod external;
Expand Down Expand Up @@ -112,6 +113,8 @@ pub struct Cli {
/// Set the profile (environment)
#[clap(short = 'P', long, global = true, hide = true)]
pub profile: Option<String>,
#[clap(long, short, hide = true)]
pub shell: Option<String>,
/// Tool(s) to run in addition to what is in mise.toml files
/// e.g.: node@20 python@3.10
#[clap(short, long, hide = true, value_name = "TOOL@VERSION")]
Expand Down Expand Up @@ -159,6 +162,7 @@ pub enum Commands {
Deactivate(deactivate::Deactivate),
Direnv(direnv::Direnv),
Doctor(doctor::Doctor),
En(en::En),
Env(env::Env),
Exec(exec::Exec),
Generate(generate::Generate),
Expand Down Expand Up @@ -219,6 +223,7 @@ impl Commands {
Self::Deactivate(cmd) => cmd.run(),
Self::Direnv(cmd) => cmd.run(),
Self::Doctor(cmd) => cmd.run(),
Self::En(cmd) => cmd.run(),
Self::Env(cmd) => cmd.run(),
Self::Exec(cmd) => cmd.run(),
Self::Generate(cmd) => cmd.run(),
Expand Down Expand Up @@ -325,6 +330,7 @@ impl Cli {
no_timings: self.no_timings,
output: run::TaskOutput::Prefix,
prefix: self.prefix,
shell: self.shell,
quiet: self.quiet,
raw: self.raw,
timings: self.timings,
Expand Down
37 changes: 22 additions & 15 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ pub struct Run {
#[clap(long, short, verbatim_doc_comment, overrides_with = "prefix")]
pub interleave: bool,

/// Shell to use to run toml tasks
///
/// Defaults to `sh -c -o errexit -o pipefail` on unix, and `cmd /c` on Windows
/// Can also be set with the setting `MISE_UNIX_DEFAULT_INLINE_SHELL_ARGS` or `MISE_WINDOWS_DEFAULT_INLINE_SHELL_ARGS`
/// Or it can be overridden with the `shell` property on a task.
#[clap(long, short, verbatim_doc_comment, env = "MISE_SHELL")]
pub shell: Option<String>,

/// Tool(s) to run in addition to what is in mise.toml files
/// e.g.: node@20 python@3.10
#[clap(short, long, value_name = "TOOL@VERSION")]
Expand Down Expand Up @@ -426,7 +434,7 @@ impl Run {
file::make_executable(&file)?;
self.exec(&file, args, task, env, prefix)
} else {
let default_shell = self.clone_default_inline_shell();
let default_shell = self.clone_default_inline_shell()?;
let (program, args) = self.get_cmd_program_and_args(script, task, args, default_shell);
self.exec_program(&program, &args, task, env, prefix)
}
Expand Down Expand Up @@ -471,11 +479,6 @@ impl Run {
trace!("using shell: {}", shell.join(" "));
let mut full_args = shell.clone();
let mut script = script.to_string();
if script.lines().count() > 1
&& (shell[0] == "sh" || shell[0] == "bash" || shell[0] == "zsh")
{
script = format!("set -e\n{}", script);
}
if !args.is_empty() {
#[cfg(windows)]
{
Expand All @@ -490,18 +493,22 @@ impl Run {
(full_args[0].clone(), full_args[1..].to_vec())
}

fn clone_default_inline_shell(&self) -> Vec<String> {
#[cfg(windows)]
return SETTINGS.windows_default_inline_shell_args.clone();
#[cfg(unix)]
return SETTINGS.unix_default_inline_shell_args.clone();
fn clone_default_inline_shell(&self) -> Result<Vec<String>> {
if let Some(shell) = &self.shell {
Ok(shell_words::split(shell)?)
} else if cfg!(windows) {
Ok(SETTINGS.windows_default_inline_shell_args.clone())
} else {
Ok(SETTINGS.unix_default_inline_shell_args.clone())
}
}

fn clone_default_file_shell(&self) -> Vec<String> {
#[cfg(windows)]
return SETTINGS.windows_default_file_shell_args.clone();
#[cfg(unix)]
return SETTINGS.unix_default_file_shell_args.clone();
if cfg!(windows) {
SETTINGS.windows_default_file_shell_args.clone()
} else {
SETTINGS.unix_default_file_shell_args.clone()
}
}

fn get_shell(&self, task: &Task, default_shell: Vec<String>) -> Vec<String> {
Expand Down
18 changes: 9 additions & 9 deletions src/shell/fish.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::path::Path;

use indoc::formatdoc;

use crate::config::Settings;
use crate::shell::Shell;
use indoc::formatdoc;
use shell_escape::unix::escape;

#[derive(Default)]
pub struct Fish {}
Expand Down Expand Up @@ -107,20 +107,20 @@ impl Shell for Fish {
"#}
}

fn set_env(&self, k: &str, v: &str) -> String {
let k = shell_escape::unix::escape(k.into());
let v = shell_escape::unix::escape(v.into());
fn set_env(&self, key: &str, v: &str) -> String {
let k = escape(key.into());
let v = escape(v.into());
format!("set -gx {k} {v}\n")
}

fn prepend_env(&self, k: &str, v: &str) -> String {
let k = shell_escape::unix::escape(k.into());
let v = shell_escape::unix::escape(v.into());
fn prepend_env(&self, key: &str, v: &str) -> String {
let k = escape(key.into());
let v = escape(v.into());
format!("set -gx {k} {v} ${k}\n")
}

fn unset_env(&self, k: &str) -> String {
format!("set -e {k}\n", k = shell_escape::unix::escape(k.into()))
format!("set -e {k}\n", k = escape(k.into()))
}
}

Expand Down
Loading

0 comments on commit 0085704

Please sign in to comment.