Our man Andrew Peng has a look at shell extension direnv.net and how it can improve your development environment.
From direnv.net
direnv is an extension for your shell. It augments existing shells with a new feature that can load and unload environment variables depending on the current directory.
direnv allows you to do fancy things like creating isolated development environments using tools such as Nix but in this blog post we're going to look at a simpler example that involves modifying the PATH environment variable within a particular directory.
Imagine we're working on a Dockerised project and we want to be able to easily run command-line tools (such as our old friend rubocop
in the container environment from our usual shell on the host system.
We might start with something like
$ docker-compose run --rm web bash -c "bundle exec rubocop --version"
0.83.0
We can simplify this a bit by wrapping it in an executable shell script.
#!/usr/bin/env bash
docker-compose run --rm web bash -c "bundle exec rubocop $*"
We can save this as rubocop
and run
chmod +x rubocop
mkdir bin
mv rubocop bin/
to make it executable and move it into a subdirectory.
$ bin/rubocop --version
0.83.0
This is much shorter but we still need to remember where we put the wrapper script whenever we want to run it. It would be nice if we could just run the command rubocop
and have this automatically resolve to the wrapper script.
We could prepend the bin
directory to the PATH environment variable which would make this possible but would also mean that we would end up using the wrapper script whenever we ran the command rubocop
from anywhere, including from outside of this project.
$ cd ~/test-project
$ ls bin/
rubocop
$ export PATH=$PWD/bin:$PATH
$ which rubocop
/Users/andrew/test-project/bin/rubocop
# success!
$ rubocop --version
0.83.0
$ cd ~
$ which rubocop
/Users/andrew/test-project/bin/rubocop
# :(
$ rubocop --version
ERROR:
Can't find a suitable configuration file in this directory or any
parent. Are you in the right directory?
Supported filenames: docker-compose.yml, docker-compose.yaml
We can solve this problem quite nicely by only prepending the bin
directory to PATH when we're in the project directory; when we're in the project directory then rubocop
will resolve to the wrapper script but when we're somewhere else then rubocop
will resolve to whatever would be found in PATH normally.
Conveniently, this is exactly what direnv allows us to do.
direnv is able to automatically update PATH whenever we change into or out of the project directory.
From direnv.net
Before each prompt, direnv checks for the existence of a .envrc file in the current and parent directories. If the file exists (and is authorized), it is loaded into a bash sub-shell and all exported variables are then captured by direnv and then made available to the current shell.
This might look something like
$ cd ~
$ echo $PATH
/Users/andrew/bin:...
$ cd ~/test-project
direnv: loading ~/test-project/.envrc
direnv: export ~PATH
$ echo $PATH
/Users/andrew/test-project/bin:/Users/andrew/bin:...
$ cd ~
direnv: unloading
$ echo $PATH
/Users/andrew/bin:...
To configure direnv for our use case, we need to create a .envrc
file in the project directory and add the following
PATH_add ~/test-project/bin
PATH_add
is a helper function provided by direnv which prepends the supplied directory to PATH when changing into the project directory and removes it when changing out of the project directory.
If we change into the project directory then we should see
$ cd ~/test-project
direnv: error /Users/andrew/test-project/.envrc is blocked. Run `direnv allow` to approve its content
direnv will start managing PATH within this directory once we have authorised the .envrc
file.
$ direnv allow
direnv: loading ~/test-project/.envrc
direnv: export ~PATH
$ which rubocop
/Users/andrew/test-project/bin/rubocop
# success!
$ rubocop --version
0.83.0
$ cd ~
direnv: unloading
# global installation
$ which rubocop
/Users/andrew/bin/rubocop
# phew
$ rubocop --version
0.83.0
And we're done!
In practice, my home directory looks something like
$HOME
|-- bin
| |-- docker
| | |-- rubocop
| | |-- ... other tools
|-- docker-projects
| |-- .envrc
| |-- project1
| |-- project2
| |-- ... other projects
where .envrc
contains
PATH_add ~/bin/docker
which allows me to use the same set of wrapper scripts across all Dockerised projects without needing to add any additional configuration for new projects.
How Ruby if statements can help you write better code by Dave Cocks
How do you solve a problem like caching in Progressive Web Apps? by Dave Quilter
Rails 6: Seeing Action Text in... action by Stephen Giles
A Quick Comment on Git Stash by Karen Fielding