diff --git a/CHANGELOG.md b/CHANGELOG.md index c735baa..f90aaaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [Unreleased] - ReleaseDate + +### Changed + +- `es set` subcommand no longer requires `--source-file` arg + - The intention is to make the command useful even without the wrapping function (and to provide helpful hint output) +- Rename binary from `env-select` to `es` + - I'm _not_ considering this a breaking change, because running the binary directly was not considered a supported use case. + ## [1.1.0] - 2024-02-09 ### Added diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 2e1bd77..17279c0 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -14,7 +14,6 @@ - [Load Values from Kubernetes](./user_guide/env/kubernetes.md) - [Inheritance & Cascading Configs](./user_guide/inheritance.md) - [Side Effects](./user_guide/side_effects.md) -- [Troubleshooting](./user_guide/troubleshooting.md) # API Reference diff --git a/docs/src/install.md b/docs/src/install.md index 02dbec9..1ef6a72 100644 --- a/docs/src/install.md +++ b/docs/src/install.md @@ -1,3 +1,33 @@ # Install -See [installation instructions](/artifacts) +See [the artifacts page](/artifacts) to download and install the `es` binary using your preferred method. + +## Install Shell Function + +While not strictly required, it's highly recommended to install the `es` shell function. This wraps the `es` binary command, allowing it to automatically modify your current shell environment with the `es set` subcommand. Otherwise, you'll have to manually pipe the output of `es set` to `source`. + +> If you only plan to use the `es run` command, this is **not relevant**. + +This is necessary because a child process is not allowed to modify its parent's environment. That means the `es` process cannot modify the environment of the invoking shell. The wrapping shell function takes the output of `es` and runs it in that shell session to update the environment. + +Here's how you install it: + +**Restart your shell afterward to apply changes.** + +### Bash + +```sh +echo "source <(es init --shell bash)" >> ~/.bashrc +``` + +### Zsh + +```sh +echo "source <(es init --shell zsh)" >> ~/.zshrc +``` + +### Fish + +```sh +echo "es init --shell fish | source" >> ~/.config/fish/config.fish +``` diff --git a/docs/src/user_guide/troubleshooting.md b/docs/src/user_guide/troubleshooting.md deleted file mode 100644 index 86c9355..0000000 --- a/docs/src/user_guide/troubleshooting.md +++ /dev/null @@ -1,30 +0,0 @@ -# Troubleshooting - -## `es: command not found` - -Because env-select modifies your shell environment, it requires a wrapper function defined in the shell that can call the `env-select` binary and automatically apply its output. - -This error indicates the `es` shell function has not been loaded. Generally it should be installed by the installer, but depending on what shell you use and how you installed env-select, it may be missing. If so, follow the steps for your shell: - -#### Bash - -```sh -echo 'eval "$(env-select --shell bash init)"' >> ~/.bashrc -source ~/.bashrc # Run this in every existing shell -``` - -#### Zsh - -```sh -echo 'source <(env-select --shell zsh init)' >> ~/.zshrc -source ~/.zshrc # Run this in every existing shell -``` - -#### Fish - -```sh -echo 'env-select --shell fish init | source' >> ~/.config/fish/config.fish -source ~/.config/fish/config.fish # Run this in every existing shell -``` - -**Restart your shell (or `source `) after running the above command.** diff --git a/src/commands/init.rs b/src/commands/init.rs index c0bd1a5..c7cb29b 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -1,7 +1,4 @@ -use crate::{ - commands::{CommandContext, SubcommandTrait}, - console, -}; +use crate::commands::{CommandContext, SubcommandTrait}; use anyhow::Context; use clap::Parser; @@ -17,6 +14,6 @@ impl SubcommandTrait for InitCommand { .init_script() .context("Error generating shell init script")?; println!("{script}"); - console::print_installation_hint() + Ok(()) } } diff --git a/src/commands/set.rs b/src/commands/set.rs index a5f8dd5..60aaf18 100644 --- a/src/commands/set.rs +++ b/src/commands/set.rs @@ -1,8 +1,13 @@ -use crate::commands::{CommandContext, Selection, SubcommandTrait}; -use anyhow::{anyhow, Context}; +use crate::{ + commands::{CommandContext, Selection, SubcommandTrait}, + console::print_hint, +}; +use anyhow::Context; use clap::Parser; use std::fs; +const WEBSITE: &str = "https://env-select.lucaspickering.me"; + /// Modify shell environment via a configured variable/application #[derive(Clone, Debug, Parser)] pub struct SetCommand { @@ -12,21 +17,32 @@ pub struct SetCommand { impl SubcommandTrait for SetCommand { fn execute(self, context: CommandContext) -> anyhow::Result<()> { - let source_file = context.source_file.as_ref().ok_or_else(|| { - anyhow!("--source-file argument required for subcommand `set`") - })?; - let profile = context.select_profile(&self.selection)?; let environment = context.load_environment(profile)?; let source_output = context.shell.export(&environment); - fs::write(source_file, source_output).with_context(|| { - format!("Error writing sourceable output to file {source_file:?}") - })?; - // Tell the user what we exported - println!("The following variables will be set:"); - println!("{environment:#}"); + // If --source-file was passed, we were probably called from the shell + // wrapper function. Write sourceable output to the given file. + if let Some(source_file) = context.source_file.as_ref() { + fs::write(source_file, source_output).with_context(|| { + format!( + "Error writing sourceable output to file {source_file:?}" + ) + })?; + // Tell the user what we exported + println!("The following variables will be set:"); + println!("{environment:#}"); + } else { + // We were *not* called from the shell wrapper here, so just print + // the output and let the user know about a pro tip + print!("{source_output}"); + // TODO update message/link + print_hint(&format!( + "Add `es` wrapping shell function to apply to env-select automatically on shell startup: \ + {WEBSITE}/book/install.html#install-shell-function", + ))?; + } Ok(()) } diff --git a/src/console.rs b/src/console.rs index c945058..5a46a2a 100644 --- a/src/console.rs +++ b/src/console.rs @@ -2,15 +2,9 @@ use crate::config::{Application, MapExt, Name, Profile}; use anyhow::bail; use dialoguer::{theme::ColorfulTheme, Select}; use indexmap::IndexMap; -use std::{ - fmt::Write, - io::{self, IsTerminal}, -}; +use std::fmt::Write; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; -const REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY"); -const VERSION: &str = env!("CARGO_PKG_VERSION"); - /// Prompt the user to select one option from a list. pub fn prompt_options<'a, T: Prompt>( options: &'a IndexMap, @@ -47,28 +41,17 @@ pub fn prompt_options<'a, T: Prompt>( } } -/// Print the given message, but only if we're connected to a TTY. If not on a -/// TTY, this hint isn't relevant so hide it. +/// Print the given message to stderr, with warning styling pub fn print_hint(message: &str) -> anyhow::Result<()> { - if io::stdout().is_terminal() { - let mut stdout = StandardStream::stdout(ColorChoice::Always); - stdout.set_color( - ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true), - )?; - println!("{message}"); - stdout.reset()?; - } + let mut stderr = StandardStream::stderr(ColorChoice::Always); + stderr.set_color( + ColorSpec::new().set_fg(Some(Color::Yellow)).set_bold(true), + )?; + eprintln!("{message}"); + stderr.reset()?; Ok(()) } -/// Print a friendly hint reminding the user to configure their shell -pub fn print_installation_hint() -> anyhow::Result<()> { - print_hint(&format!( - "Initialize env-select automatically on shell startup: \ - {REPOSITORY}/tree/v{VERSION}#configure-your-shell", - )) -} - /// Little helper to define how a type should be rendered in a TUI prompt pub trait Prompt: Sized { const SELF_NAME: &'static str; diff --git a/src/main.rs b/src/main.rs index b7b7737..aede2d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -71,7 +71,7 @@ fn main() -> ExitCode { // If the error includes an exit code, use it match error.downcast::() { // If we're propagating the exit code, we don't want to print - // the error. This is for `env-select run`, which means + // the error. This is for `es run`, which means // stdout/stderr have been forwarded and we don't want to tack // on any more logging. Ok(error) => error.into(), diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 8d6dc08..e48713b 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -24,8 +24,8 @@ pub fn shell_path(shell_kind: &str) -> PathBuf { ) } -/// Run a script inside the given shell. This will use `env-select init` to -/// load the correct shell function, then +/// Run a script inside the given shell. This will use `es init` to load the +/// correct shell function, then run the script. /// /// `detect_shell` argument controls whether env-select will guess which shell /// it's running under (true) or we'll explicitly tell it with -s (false). @@ -34,7 +34,7 @@ pub fn execute_script( shell_kind: &str, detect_shell: bool, ) -> Command { - // Get the function source from `env-select init` + // Get the function source from `es init` let mut es = env_select(); if detect_shell { es.env("SHELL", shell_path(shell_kind));