Skip to content

Commit

Permalink
Merge pull request #6 from DanCardin/dc/shell
Browse files Browse the repository at this point in the history
Add support for more shell features
  • Loading branch information
DanCardin authored Jan 27, 2021
2 parents 6316669 + d1cb31b commit 96a5afb
Show file tree
Hide file tree
Showing 18 changed files with 330 additions and 65 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ jobs:
- ubuntu-latest
toolchain:
- nightly
cargo_flags:
- "--all-features"
steps:
- name: Checkout source code
uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = 'sauce'
version = '0.4.0'
version = '0.5.0'
authors = ['Dan Cardin <ddcardin@gmail.com>']
edition = '2018'
description = 'A tool for managing directory-specific state.'
Expand Down
61 changes: 44 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,22 @@ foo=bar

### Setup

Currently explicitly supported shells include: `zsh`, `bash`, and
`fish`. The scaffolding exists to support other shells, which should
make supporting other common shells that might require `"$SHELL"`
specific behavior.

Loading things into the environment requires a minimal amount of shell
code to be executed, so after installing the binary (suggestion below),
you will need to add `eval "$(sauce shell init)"` to your `.bashrc`,
`.zshrc`, etc.
you will need to add add a hook to your bashrc/zshrc/config.fish, etc.

Currently explicitly supported shells include: `zsh` and `bash`. The
scaffolding exists to support other shells, which should make supporting
other common shells that might require `"$SHELL"` specific behavior.
- bash `eval "$(sauce --shell bash shell init)"`
- zsh `eval "$(sauce --shell zsh shell init)"`
- fish `sauce --shell fish shell init | source`

When one of the supported shells is not detected, behavior falls back to
the `bash` implementation.
Depending on the level of similarity to the above shells, you may be
able to get away with using one of the above `shell init` hooks until
explicit support is added

## Targets

Expand Down Expand Up @@ -184,6 +189,16 @@ which might be important given that there can be overlap between
targets. Targets are separated from their search term by `:`,
i.e. `--glob env:database*,function:work-*`.

### `sauce clear`

`clear`ing will “unset” everything defined in any cascaded saucefiles,
abiding by any options (–filter/–glob/–as) provided to the command.

The general intent is that one would only/primarily be including targets
which would be safe to unset (or that you will avoid running `clear` if
that’s not true for you), given that they were overwritten when you run
`sauce`.

## Settings

### direnv-like automatic execution of `sauce` on `cd`
Expand Down Expand Up @@ -274,18 +289,30 @@ project) which would enable things like `sauce --as prod` or

## Planned Work

- Autoload in bash
- Support fish shell
- “strategies” (nested shell vs in-place alterations of the current
shell)
- Given strategies, the ability to unset/revert alterations in a more
robust way is enabled (i.e. kill the subshell). Compared to the
in-place modification strategy which essentially requires that
`sauce` maintains sole control over all tracked variables (because
it can/will `unset` them if asked).
- colorized output

- ability to specify values which should not react to `clear`

This might be useful for environment variables like `PATH` or
`PROMPT`, which would otherwise be very unsafe to include, unless you
**never** run `clear`.

- config option to template prompt with `sauce` context.

Potentially something like `prompt = "(${as}) ${prompt}"` rendering to
`(prod) >`.

- ability to subdivide targets by shell

- `sauce config subshell=false/true` (default `false`)

Given subshell=true, a call to `sauce` would create a subprocess into
which the alterations would be made. This would enable one to simply
kill the current shell to revert state.

- more targets: arbitrary key-value pairs

- pipe `sauce show` to a pager when beyond a full terminal height
- colorized output

## Local development

Expand Down
10 changes: 5 additions & 5 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,18 @@ pub fn run() -> Result<()> {

let context = Context::new(data_dir, settings, options)?;

let shell_kind = &*shell::detect();
let shell_kind = &*shell::detect(opts.shell);

let output = match_subcommmand(context, shell_kind, &opts.subcmd, opts.autoload);

let out = std::io::stderr();
let out = std::io::stdout();
let mut handle = out.lock();
handle.write_all(output.message().as_ref())?;
handle.write_all(output.result().as_ref())?;
handle.flush()?;

let out = std::io::stdout();
let out = std::io::stderr();
let mut handle = out.lock();
handle.write_all(output.result().as_ref())?;
handle.write_all(output.message().as_ref())?;
handle.flush()?;

Ok(())
Expand Down
30 changes: 29 additions & 1 deletion src/cli/shape.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
use clap::Clap;
use std::io::Write;
use std::{io::Write, str::FromStr};

/// Sauce!
#[derive(Clap, Debug)]
#[clap(version, author)]
pub struct CliOptions {
/// Determines the shell behavior, this flag should always be set automatically
/// by the shell hook. Valid options are: zsh, fish, bash
#[clap(long)]
pub shell: ShellName,

/// Sets a custom config file. Could have been an Option<T> with no default too
#[clap(short, long)]
pub config: Option<String>,
Expand Down Expand Up @@ -53,6 +58,29 @@ impl CliOptions {
}
}

#[derive(Debug)]
pub enum ShellName {
Zsh,
Fish,
Bash,
}

impl FromStr for ShellName {
type Err = String;

fn from_str(s: &str) -> Result<Self, String> {
match s {
"zsh" => Ok(Self::Zsh),
"bash" => Ok(Self::Bash),
"fish" => Ok(Self::Fish),
unhandled => Err(format!(
"Unrecognized shell '{}'. Valid options are: zsh, fish, bash",
unhandled
)),
}
}
}

#[derive(Clap, Debug)]
pub enum SubCommand {
Shell(ShellCommand),
Expand Down
26 changes: 21 additions & 5 deletions src/shell/kinds/bash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@ impl Shell for Bash {
format!("\"$EDITOR\" '{}'", path)
}

fn init(&self, binary: &str, _autoload: bool) -> String {
format!(
r#"function {0} {{ eval "$(command {1} "$@")" }}"#,
fn init(&self, binary: &str, autoload_hook: bool) -> String {
let mut init = format!(
include_str!("bash_init.sh"),
binary,
qualify_binary_path(binary)
)
);

if autoload_hook {
init.push_str(&format!(include_str!("bash_init_autoload.sh"), binary));
}

init
}

fn set_var(&self, var: &str, value: &str) -> String {
Expand Down Expand Up @@ -63,7 +69,17 @@ mod tests {
fn it_defaults() {
let shell = Bash {};
let output = shell.init("foo", false);
assert_eq!(output, r#"function foo { eval "$(command foo "$@")" }"#);
assert_eq!(
output,
"function foo {\n eval \"$(command foo --shell bash \"$@\")\"\n}\n"
);
}

#[test]
fn it_autoloads() {
let shell = Bash {};
let output = shell.init("foo", true);
assert_eq!(output.contains("--autoload"), true);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/shell/kinds/bash_init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function {0} {{
eval "$(command {1} --shell bash "$@")"
}}
30 changes: 30 additions & 0 deletions src/shell/kinds/bash_init_autoload.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# create a PROPMT_COMMAND equivalent to store chpwd functions
typeset -g CHPWD_COMMAND=""

_chpwd_hook() {{
shopt -s nullglob

local f

# run commands in CHPWD_COMMAND variable on dir change
if [[ "$PREVPWD" != "$PWD" ]]; then
local IFS=$';'
for f in $CHPWD_COMMAND; do
"$f"
done
unset IFS
fi

# refresh last working dir record
export PREVPWD="$PWD"
}}

# add `;` after _chpwd_hook if PROMPT_COMMAND is not empty
PROMPT_COMMAND="_chpwd_hook${{PROMPT_COMMAND:+;$PROMPT_COMMAND}}"

_{0}_autoload() {{
{0} --autoload
}}

# append the command into CHPWD_COMMAND
CHPWD_COMMAND="${{CHPWD_COMMAND:+$CHPWD_COMMAND;}}_{0}_autoload"
Loading

0 comments on commit 96a5afb

Please sign in to comment.