diff --git a/README.md b/README.md index 5ab092d..44176a5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# 👇local👇bashrc +# 👇local👇(bash|zsh)rc -`.local.bashrc` files that are sourced in a clean bash process when entering +`.local.bashrc` or `.local.zshrc` files that are sourced in a clean process when entering the directory. *Replaces the [projector](https://github.com/bas080/projector) project.* @@ -9,18 +9,20 @@ the directory. - Makes a simple to define env variables, aliases and functions for a specific project directory. -- Creates a bash history that is a separate from the root bash history. +- Creates a shell history that is a separate from the root shell history. - Makes it easier to keep and find commands that are related to that project. - - Doesn't fill up the root bash history. -- Does not inherit env variables of other `.local.bashrc` directories when + - Doesn't fill up the root shell history. +- Does not inherit env variables of other `.local.bashrc`/`.local.zshrc` directories when jumping directories. ## Setup -A quick setup script. +### Bash: + +A quick setup script for bash. ```bash -curl 'https://raw.githubusercontent.com/bas080/dotlocaldotbashrc/master/dotlocaldotbashrc' > "$HOME/.dotlocaldotbashrc" && +curl 'https://raw.githubusercontent.com/brianmatzelle/dotlocaldotrc/refs/heads/master/bash/dotlocaldotbashrc' > "$HOME/.dotlocaldotbashrc" && echo 'source "$HOME/.dotlocaldotbashrc"' >> ~/.bashrc ``` @@ -37,10 +39,36 @@ PS1="${BASHRC_HOME#$HOME/}:\W " projects/dotlocaldotbashrc:dotlocaldotbashrc cd ../█ ``` -When using in repositores consider adding `.local.*` to your `.gitignore`. +When using in repositories consider adding `.local.*` to your `.gitignore`. + +### Zsh: + +A quick setup script for Z shell. + +```zsh +curl 'https://raw.githubusercontent.com/brianmatzelle/dotlocaldotrc/refs/heads/master/zsh/dotlocaldotzshrc' > "$HOME/.dotlocaldotzshrc" && + echo 'source "$HOME/.dotlocaldotzshrc"' >> ~/.zshrc +``` + +After re-sourcing your .zshrc you can create a `.local.zshrc` in a directory where you would like +a local zshrc by running `dotlocaldotzshrc init`. + +Optionally you can show which local zshrc is currently active in your prompt. + +```zsh +# What I use: +PS1="${ZSHRC_HOME#$HOME/}:\W " + +# Looks like this: +projects/dotlocaldotzshrc:dotlocaldotzshrc cd ../█ +``` + +When using in repositories consider adding `.local.*` to your `.gitignore`. ## Usage +### Bash + Quick start: ```text @@ -104,15 +132,88 @@ $ cd .. dotlocaldotbashrc: error: will not exit with jobs in the background. ``` +### Zsh + +Quick start: + +```text +dotlocaldotzshrc [] + Create and configure directory specific zsh sessions. + + Will source .local.zshrc when no subcommand is defined. + + [subcommand] + init - Create a .local.zshrc in current directory. + edit - Opens the zshrc in $EDITOR and re-sources on exit. +``` + +More information: + +``` +$ cd projects/zshrc/ +dotlocaldotzshrc: open /home/user/projects/zshrc +$ cd nested/ +dotlocaldotzshrc: exit /home/user/projects/zshrc +dotlocaldotzshrc: open /home/user/projects/zshrc/nested +$ cd +dotlocaldotzshrc: exit /home/user/projects/zshrc/nested +$ +``` + +> Notice that navigating to the home directory resulted in only a zshrc zsh +> session being exited. We just use the initial zsh process that was started +> when zshrc and the rest of the interactive shell was bootstrapped. + +You can source the local .local.zshrc at anytime with `dotlocaldotzshrc` function. + +```zsh +$ dotlocaldotzshrc +zshrc: source '/home/user/.local.zshrc' +$ +``` + +You can quickly edit and automatically source after editor quit with +`dotlocaldotzshrc edit`. + +One notable feature of `dotlocaldotzshrc` is its behavior when background jobs +are active. When attempting to exit the `dotlocaldotzshrc` session using the +`cd` command or when explicitly running the `dotlocaldotzshrc` command with the +`exit` option, the script checks for any background jobs in the current session. + +If there are background jobs running, the script will display an error message +and refrain from exiting the session. This behavior ensures that you don't +unintentionally lose work by exiting a session with active background jobs. + +```zsh +$ cd projects/myproject/ +dotlocaldotzshrc: open /home/user/projects/myproject + +# Start a background job +$ sleep 300 & # ctrl-z +[1] 12345 + +# Attempt to exit the dotlocaldotzshrc session +$ cd .. +dotlocaldotzshrc: error: will not exit with jobs in the background. +``` + ## Test How to run the tests: +### Bash + ```bash SPAT_SHELL='bash' spat ./dotlocaldotbashrc.t ``` -> Uses [spat](https://github.com/bas080/spat) to run expect tests. +### Zsh + +```bash +SPAT_SHELL='zsh' spat ./dotlocaldotzshrc.t +``` + +Uses [spat](https://github.com/bas080/spat) to run expect tests. ## License diff --git a/dotlocaldotbashrc b/bash/dotlocaldotbashrc similarity index 100% rename from dotlocaldotbashrc rename to bash/dotlocaldotbashrc diff --git a/dotlocaldotbashrc.t b/bash/dotlocaldotbashrc.t similarity index 100% rename from dotlocaldotbashrc.t rename to bash/dotlocaldotbashrc.t diff --git a/dotlocaldotbashrc.t.out b/bash/dotlocaldotbashrc.t.out similarity index 100% rename from dotlocaldotbashrc.t.out rename to bash/dotlocaldotbashrc.t.out diff --git a/zsh/dotlocaldotzshrc b/zsh/dotlocaldotzshrc new file mode 100644 index 0000000..c51ebb5 --- /dev/null +++ b/zsh/dotlocaldotzshrc @@ -0,0 +1,108 @@ +#!/usr/bin/env zsh + +_zshrc_dir() { + local dir="$1" + + test -f "$dir/.local.zshrc" && echo "$dir" && return 0 + test '/' = "$dir" && return 1 + + _zshrc_dir "$(dirname "$1")" +} + +ZSHRC_HOME="$(_zshrc_dir "$PWD")" + +dotlocaldotzshrc () { + local subcommand; + if [ "$1" = "init" ]; then + subcommand="init" + + if [ -f .local.zshrc ]; then + echo "dotlocaldotzshrc: error: $PWD already has a .local.zshrc" + return 1 + fi + + printf '#!/usr/bin/env zsh\n' >> .local.zshrc + "${EDITOR:-vi}" .local.zshrc + fi + + if [ "$1" = "edit" ]; then + subcommand="edit" + if [ -z "$ZSHRC_HOME" ]; then + echo "dotlocaldotzshrc: error: cannot find a .local.zshrc" + return 1 + fi + + "${EDITOR:-vi}" "$ZSHRC_HOME/.local.zshrc" + + # And re-source + dotlocaldotzshrc + fi + + if [ -n "$1" ] && [ -z "$subcommand" ]; then + echo "dotlocaldotzshrc: error: $1 is not a valid command" + return 1 + fi + + _zshrc_dir "$PWD" || { + echo "dotlocaldotzshrc: error: no parent directory with .local.zshrc found" + return + } + + ZSHRC_SOURCED="1" + echo "dotlocaldotzshrc: source '$ZSHRC_HOME/.local.zshrc'" + source "$ZSHRC_HOME/.local.zshrc" +} + +lcd() { + cd "$ZSHRC_HOME/$1" +} + +_zshrc_init() { + local zshrc_dir + local zshrc_temp + + # Return early if no .local.zshrc is found. + zshrc_dir="$(_zshrc_dir "$PWD")" + + test -n "$ZSHRC_TEMP" && test -z "$ZSHRC_SOURCED" && { + dotlocaldotzshrc + } + + # The local zshrc path changed. + test "$zshrc_dir" != "$ZSHRC_HOME" && { + + wait + + # Do a fg to make sure you don't lose work + if [[ -n $(jobs -p) ]]; then + jobs + echo "dotlocaldotzshrc: error: will not exit with jobs in background." + return + fi + + # Current zsh process is a local zshrc session. + test -n "$ZSHRC_TEMP" && { + echo "$PWD" > "$ZSHRC_TEMP" + echo "dotlocaldotzshrc: exit $ZSHRC_HOME" + exit + } + + # Current zsh process is the original (root) session. + zshrc_temp="$(mktemp)" + + echo "dotlocaldotzshrc: open $zshrc_dir" + HISTFILE="$zshrc_dir/.local.zsh_history" \ + ZSHRC_TEMP="$zshrc_temp" \ + zsh + + test -s "$ZSHRC_TEMP" || exit + cd "$(cat "$ZSHRC_TEMP")" || true + _zshrc_init + + } + +} + +autoload -Uz add-zsh-hook +add-zsh-hook precmd _zshrc_init + diff --git a/zsh/dotlocaldotzshrc.t b/zsh/dotlocaldotzshrc.t new file mode 100644 index 0000000..2a6c324 --- /dev/null +++ b/zsh/dotlocaldotzshrc.t @@ -0,0 +1,37 @@ +#!/usr/bin/env zsh + +EDITOR="cat" + +{ +source ./dotlocaldotzshrc + +rm -r /tmp/dotlocaldotzshrc/test || true +mkdir -p /tmp/dotlocaldotzshrc/test/subdirectory +cd /tmp/dotlocaldotzshrc/test || exit + +# Test Case 1: Initialize a new .local.zshrc +dotlocaldotzshrc init + +# Validate that the .local.zshrc file has been created +test -f .local.zshrc + +# Test Case 2: Attempt to initialize .local.zshrc again (expecting an error) +dotlocaldotzshrc init + +echo 'echo hello .local.zshrc' >> .local.zshrc + +# Test Case 3: Source the .local.zshrc file +dotlocaldotzshrc + +# Test Case 4: Navigate to a subdirectory and source .local.zshrc +cd subdirectory + +dotlocaldotzshrc + +# Test Case 5: Exit the .local.zshrc session +cd ../../ + +_zshrc_init + +dotlocaldotzshrc +} 2>&1 | sed "s#$PWD#/#g" diff --git a/zsh/dotlocaldotzshrc.t.out b/zsh/dotlocaldotzshrc.t.out new file mode 100644 index 0000000..6794feb --- /dev/null +++ b/zsh/dotlocaldotzshrc.t.out @@ -0,0 +1,14 @@ + rm: /tmp/dotlocaldotzshrc/test: No such file or directory + #!/usr/bin/env zsh + /tmp/dotlocaldotzshrc/test + dotlocaldotzshrc: source '/.local.zshrc' + dotlocaldotzshrc:source:39: no such file or directory: /.local.zshrc + dotlocaldotzshrc: error: /tmp/dotlocaldotzshrc/test already has a .local.zshrc + /tmp/dotlocaldotzshrc/test + dotlocaldotzshrc: source '/.local.zshrc' + dotlocaldotzshrc:source:39: no such file or directory: /.local.zshrc + /tmp/dotlocaldotzshrc/test + dotlocaldotzshrc: source '/.local.zshrc' + dotlocaldotzshrc:source:39: no such file or directory: /.local.zshrc + dotlocaldotzshrc: error: no parent directory with .local.zshrc found + (Exit Code: 0)