Skip to content

Commit

Permalink
Show hint instead failing if --source-file is not passed to es set
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasPickering committed Feb 11, 2024
1 parent 33c7c63 commit f20c1a4
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 78 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 0 additions & 1 deletion docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
32 changes: 31 additions & 1 deletion docs/src/install.md
Original file line number Diff line number Diff line change
@@ -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
```
30 changes: 0 additions & 30 deletions docs/src/user_guide/troubleshooting.md

This file was deleted.

7 changes: 2 additions & 5 deletions src/commands/init.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use crate::{
commands::{CommandContext, SubcommandTrait},
console,
};
use crate::commands::{CommandContext, SubcommandTrait};
use anyhow::Context;
use clap::Parser;

Expand All @@ -17,6 +14,6 @@ impl SubcommandTrait for InitCommand {
.init_script()
.context("Error generating shell init script")?;
println!("{script}");
console::print_installation_hint()
Ok(())
}
}
40 changes: 28 additions & 12 deletions src/commands/set.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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(())
}
Expand Down
33 changes: 8 additions & 25 deletions src/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Name, T>,
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ fn main() -> ExitCode {
// If the error includes an exit code, use it
match error.downcast::<ExitCodeError>() {
// 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(),
Expand Down
6 changes: 3 additions & 3 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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));
Expand Down

0 comments on commit f20c1a4

Please sign in to comment.