Skip to content

Commit

Permalink
Merge pull request #18 from gianluca-mascolo/snippets
Browse files Browse the repository at this point in the history
Implement a layer system using snippets script
  • Loading branch information
gianluca-mascolo authored Sep 21, 2023
2 parents f84326e + 8b3a384 commit 30e0991
Show file tree
Hide file tree
Showing 21 changed files with 237 additions and 60 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
README.md.backup
./tests/profiles/ephemeral.load
./tests/profiles/ephemeral.profile
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ repos:
entry: shellcheck
language: system
types: [file, shell]
files: ^bash_profile_switcher.sh$
files: ^(bash_profile_switcher.sh|tests/profiles/snippets/.*\.sh)$
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ test:
shellcheck bash_profile_switcher.sh
docker pull bash:5
./tests/automated_tests.exp
format:
shfmt -w -i 4 *.sh
clean:
docker-compose down
install:
Expand Down
46 changes: 22 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ Easily change environment variables and settings using bash
## About

This script aim to manage multiple profile files for bash. It's like having multiple `.bashrc` files to load or unload when needed.
All you need to do is to create your custom profile files under `~/.bash_profiles` directory. Each profile must have extension `.load` where you define variables, aliases and so on. You can optionally create files with extension `.unload` to clear what is loaded with a speficic profile.
You need to create your custom profile files under `~/.bash_profile.d` directory. Each profile must have extension `.profile` and it is a plain text file containing a list of "snippets" (one per line) to be loaded into your profile.
Snippets are `.sh` files where you can set variables/aliases/functions or any command you want to execute when you spawn a shell ([snippet example](examples/snippet-example.sh)).

## Installation

Expand All @@ -13,7 +14,7 @@ _Note_: You can use `make install INSTALL_PATH=<path>` to install the script in

## Usage

Use `switch_profile` function to manage profiles
Use `switch_profile` function to change profile

```
~]$ switch_profile -h
Expand All @@ -29,39 +30,36 @@ OPTIONS
-l
List available profiles
-h Show help instructions (this help)
PROFILE
A profile to load in ~/.bash_profiles. Profile files end with extension '.load' for loading (set variables) and '.unload' for unloading (unset variables)
Current profile is stored in environment variable BASH_CURRENT_PROFILE and in file ~/.bash_saved_profile
Example:
~]$ switch_profile dev
To load profile dev from ~/.bash_profiles/dev.load and unload it from ~/.bash_profiles/dev.unload
```

## Example

Create the following profile files in `~/.bash_profiles`:

> `myprofile.load`
```bash
export PS1="Funky Prompt:: \w ]\\$ "
As an example take the following directory structure for `~/.bash_profile.d`
```
.bash_profile.d/
├── foo.profile
├── bar.profile
└── snippets
├── tools.sh
├── cloudvars.sh
└── setpath.sh
```

> `myprofile.unload`
```bash
unset PS1
```
Then load it with: `switch_profile myprofile`
]$ cat ~/.bash_profile.d/foo.profile
tools
cloudvars
setpath
]$ cat ~/.bash_profile.d/bar.profile
setpath
```

When you do `switch_profile foo` snippets `tools.sh`,`cloudvars.sh`,`setpath.sh` will be loaded (in the same order they are listed in profile).
On profile change `switch_profile bar` bash will first unload all the snippets applied by `foo` in reverse order then load the snippets listed in `bar`, that is `setpath.sh`
``
## Issues

* Spaces and blank characters are not supported on profile filenames
* Unload files must be written manually to match exactly what you loaded or defined
* Be careful when managing special variables like `PATH`

## Test automation
Expand Down
119 changes: 100 additions & 19 deletions bash_profile_switcher.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,80 @@
### GENERAL CONFIGURATION ###

# Setup default directory
export SWITCH_PROFILE_DIRECTORY=".bash_profiles"
export SWITCH_PROFILE_DIRECTORY=".bash_profile.d"
[ -d "$HOME/$SWITCH_PROFILE_DIRECTORY" ] || mkdir "$HOME/$SWITCH_PROFILE_DIRECTORY"
[ -d "$HOME/$SWITCH_PROFILE_DIRECTORY/snippets" ] || mkdir "$HOME/$SWITCH_PROFILE_DIRECTORY/snippets"

# Setup save profile filename
export SWITCH_PROFILE_SAVED=".bash_saved_profile"

# List of loaded snippets separated by colon like
# snipname1:snipname2:snippetname3
export SWITCH_PROFILE_SNIPPETS=""

# Setup aliases to manage profiles
alias _load_bash_profile='eval [ -f "$HOME/$SWITCH_PROFILE_DIRECTORY/${BASH_CURRENT_PROFILE}.load" ] && source "$HOME/$SWITCH_PROFILE_DIRECTORY/${BASH_CURRENT_PROFILE}.load"'
alias _unload_bash_profile='eval [ -f "$HOME/$SWITCH_PROFILE_DIRECTORY/${BASH_CURRENT_PROFILE}.unload" ] && source "$HOME/$SWITCH_PROFILE_DIRECTORY/${BASH_CURRENT_PROFILE}.unload"'
alias _save_bash_profile='eval echo "export BASH_CURRENT_PROFILE=$SELECTED_PROFILE" > "$HOME/$SWITCH_PROFILE_SAVED"'
alias _reset_bash_profile='eval echo "unset BASH_CURRENT_PROFILE" > "$HOME/$SWITCH_PROFILE_SAVED"'
alias _save_bash_profile='eval echo "export SWITCH_PROFILE_CURRENT=$SELECTED_PROFILE" > "$HOME/$SWITCH_PROFILE_SAVED"'
alias _reset_bash_profile='eval echo "unset SWITCH_PROFILE_CURRENT" > "$HOME/$SWITCH_PROFILE_SAVED"'

alias _get_snippets='eval unset LOAD_SNIPPETS; declare -a LOAD_SNIPPETS; mapfile -c 1 -C _parse_profile -t <"${HOME}/${SWITCH_PROFILE_DIRECTORY}/${SWITCH_PROFILE_CURRENT}.profile"'
# shellcheck disable=SC2154
alias _load_bash_profile='_get_snippets; for ((n=0;n<${#LOAD_SNIPPETS[*]};n++)); do source "${LOAD_SNIPPETS[$n]}" load; done'
alias _unload_bash_profile='for ((n=-1;n>=-${#LOAD_SNIPPETS[*]};n--)); do source "${LOAD_SNIPPETS[$n]}" unload; done'

# _parse_profile
# To be used with mapfile
# Every line in the file is parsed and checked for a corresponding snippet to be loaded
# It will store the valid snippets in global array LOAD_SNIPPETS
_parse_profile() {
local VALUE
local SNIPPET
VALUE="$2"
if [[ "$VALUE" =~ ^[[:blank:]]*([^# ]+)([[:blank:]]|$) ]]; then
{
SNIPPET="${BASH_REMATCH[1]}"
[ -f "$HOME/$SWITCH_PROFILE_DIRECTORY/snippets/$SNIPPET.sh" ] && LOAD_SNIPPETS+=("$HOME/$SWITCH_PROFILE_DIRECTORY/snippets/$SNIPPET.sh")
}
fi
return 0
}

# _snippet [push|pop|search] <snippet name>
# Manage the status of snippets storing it in SWITCH_PROFILE_SNIPPETS if it has loaded or unloaded.
# - push the snippet name into SWITCH_PROFILE_SNIPPETS only if the value is not already present
# - pop the snippet name from SWITCH_PROFILE_SNIPPETS
# - search if a snippet name is present in SWITCH_PROFILE_SNIPPETS
_snippet() {
local -r snippet_cmd="${1:-}"
local -r snippet_name="${2:-}"
local -r REGEX="(^|.*:)(${snippet_name})(:.*|$)"

case "$snippet_cmd" in
push)
if [[ "$SWITCH_PROFILE_SNIPPETS" =~ $REGEX ]]; then return 0; else SWITCH_PROFILE_SNIPPETS="${SWITCH_PROFILE_SNIPPETS}:${snippet_name}"; fi
export SWITCH_PROFILE_SNIPPETS=${SWITCH_PROFILE_SNIPPETS#:}
;;
pop)
if [[ "$SWITCH_PROFILE_SNIPPETS" =~ $REGEX ]]; then SWITCH_PROFILE_SNIPPETS="${BASH_REMATCH[1]%:}:${BASH_REMATCH[3]#:}"; else return 0; fi
SWITCH_PROFILE_SNIPPETS=${SWITCH_PROFILE_SNIPPETS%:}
export SWITCH_PROFILE_SNIPPETS=${SWITCH_PROFILE_SNIPPETS#:}
;;
search)
if [[ "$SWITCH_PROFILE_SNIPPETS" =~ $REGEX ]]; then return 0; else return 1; fi
;;
*)
echo "${SWITCH_PROFILE_SNIPPETS:-}"
;;
esac
return 0
}

# Create list of profiles from .load files
# Create list of profiles from .profile files
_switch_profile_list() {
local PROFILE_LIST
# Note: If there are no matching files, echo *.load output literally "*.load"
PROFILE_LIST="$(echo "$HOME/$SWITCH_PROFILE_DIRECTORY/"*.load)"
# Note: If there are no matching files, echo *.profile output literally "*.profile"
PROFILE_LIST="$(echo "$HOME/$SWITCH_PROFILE_DIRECTORY/"*.profile)"
PROFILE_LIST="${PROFILE_LIST//$HOME\/$SWITCH_PROFILE_DIRECTORY\//}"
PROFILE_LIST="${PROFILE_LIST//.load/}"
PROFILE_LIST="${PROFILE_LIST//.profile/}"
[ "$PROFILE_LIST" = '*' ] && PROFILE_LIST=""
echo "$PROFILE_LIST"
}
Expand All @@ -71,14 +127,39 @@ OPTIONS
-h Show help instructions (this help)
PROFILE
A profile to load in ~/.bash_profiles. Profile files end with extension '.load' for loading (set variables) and '.unload' for unloading (unset variables)
Current profile is stored in environment variable BASH_CURRENT_PROFILE and in file ~/.bash_saved_profile
A profile to load in ~/$SWITCH_PROFILE_DIRECTORY. Profile files end with extension '.profile' and contains a list of snippets to be loaded.
Current profile is stored in environment variable SWITCH_PROFILE_CURRENT and in file ~/$SWITCH_PROFILE_SAVED
Example:
SNIPPETS
Snippets are .sh files located in ~/$SWITCH_PROFILE_DIRECTORY/snippets that accept load or unload as first positional parameter (\$1)
Each snippet will be included with source in your shell.
EXAMPLE
Given the following setup ~/$SWITCH_PROFILE_DIRECTORY
~]$ tree ~/.bash_profile.d/
$HOME/.bash_profile.d/
├── dev.profile
└── snippets
├── snip1.sh
└── snip2.sh
~]$ cat ~/.bash_profile.d/dev.profile
snip1
snip2
~]$ switch_profile dev
To load profile dev from ~/.bash_profiles/dev.load and unload it from ~/.bash_profiles/dev.unload
snip1.sh and snip2.sh will be included into your bash shell.
ADDENDUM
- Snippets will be loaded in the order the are listed into profile file.
- When you switch into a profile snippets are loaded with source command e.g. 'source snip1 load'
- When you change profile, current snippets are unloaded (except if you use -k) in reverse order
they are listed into profile file with source e.g. 'source snip1 unload'
EOF
}
Expand Down Expand Up @@ -122,9 +203,9 @@ switch_profile() {
shift $((OPTIND - 1))

SELECTED_PROFILE="$1"
if [ -f "${HOME}/${SWITCH_PROFILE_DIRECTORY}/${SELECTED_PROFILE}.load" ]; then {
if [ -f "${HOME}/${SWITCH_PROFILE_DIRECTORY}/${SELECTED_PROFILE}.profile" ]; then {
[ $KEEP_ENV -eq 0 ] && _unload_bash_profile
if [ $TEMP_PROFILE -eq 0 ]; then _save_bash_profile; else export BASH_NEXT_PROFILE="$SELECTED_PROFILE"; fi
if [ $TEMP_PROFILE -eq 0 ]; then _save_bash_profile; else export SWITCH_PROFILE_NEXT="$SELECTED_PROFILE"; fi
exec bash
}; else
{
Expand All @@ -140,18 +221,18 @@ switch_profile() {
### MAIN SCRIPT ###
[ -n "$SWITCH_PROFILE_LIST" ] && complete -o nospace -W "$SWITCH_PROFILE_LIST" switch_profile

if [ -z ${BASH_NEXT_PROFILE+is_set} ]; then {
if [ -z ${SWITCH_PROFILE_NEXT+is_set} ]; then {
if [ -f "$HOME/$SWITCH_PROFILE_SAVED" ]; then
{
# shellcheck source=/dev/null
source "$HOME/$SWITCH_PROFILE_SAVED"
[ -n "${BASH_CURRENT_PROFILE+is_set}" ] && _load_bash_profile
if [ -n "${SWITCH_PROFILE_CURRENT+is_set}" ]; then _load_bash_profile; fi
}
fi
}; else
{
export BASH_CURRENT_PROFILE="$BASH_NEXT_PROFILE"
unset BASH_NEXT_PROFILE
export SWITCH_PROFILE_CURRENT="$SWITCH_PROFILE_NEXT"
unset SWITCH_PROFILE_NEXT
_load_bash_profile
}
fi
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ services:
read_only: true
- type: bind
source: $PWD/tests/profiles/
target: /root/.bash_profiles/
target: /root/.bash_profile.d/
read_only: true
23 changes: 23 additions & 0 deletions examples/snippet-example.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# shellcheck shell=bash
SNIPPET_NAME="$(basename "${BASH_SOURCE[0]}" .sh)"
case "$1" in
load)
_snippet search "$SNIPPET_NAME" && return 0
# load your settings here >>>
export var1="test1 variable"
# <<<
_snippet push "$SNIPPET_NAME" 2>/dev/null
;;
unload)
_snippet search "$SNIPPET_NAME" || return 0
# unload your settings here >>>
unset var1
# <<<
_snippet pop "$SNIPPET_NAME" 2>/dev/null
;;
*)
true
;;
esac

return 0
3 changes: 3 additions & 0 deletions tests/automated_tests.exp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ proc print_results {} {

proc abort {} {
print_results
sleep 1
send "exit\n"
send "exit\n"
sleep 1
Expand All @@ -35,7 +36,9 @@ source ./tests/scenarios/06-temporary-profile.exp
source ./tests/scenarios/07-keep-profile.exp
source ./tests/scenarios/08-unexistent-profile.exp
source ./tests/scenarios/09-reload-list.exp
source ./tests/scenarios/10-check-snippet.exp

sleep 1
send "exit\n"
sleep 1
print_results
Expand Down
17 changes: 17 additions & 0 deletions tests/profiles/snippets/varcommon.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# shellcheck shell=bash
SNIPPET_NAME="varcommon"
case "$1" in
load)
export check_var_common="${common_value:-}"
_snippet push "$SNIPPET_NAME" 2>/dev/null
;;
unload)
unset check_var_common
_snippet pop "$SNIPPET_NAME" 2>/dev/null
;;
*)
true
;;
esac

return 0
19 changes: 19 additions & 0 deletions tests/profiles/snippets/vartest1.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# shellcheck shell=bash
SNIPPET_NAME="$(basename "${BASH_SOURCE[0]}" .sh)"
case "$1" in
load)
export check_var_test1="test1 variable"
export common_value="test1 common"
_snippet push "$SNIPPET_NAME" 2>/dev/null
;;
unload)
unset check_var_test1
unset common_value
_snippet pop "$SNIPPET_NAME" 2>/dev/null
;;
*)
true
;;
esac

return 0
19 changes: 19 additions & 0 deletions tests/profiles/snippets/vartest2.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# shellcheck shell=bash
SNIPPET_NAME="$(basename "${BASH_SOURCE[0]}" .sh)"
case "$1" in
load)
export check_var_test2="test2 variable"
export common_value="test2 common"
_snippet push "$SNIPPET_NAME" 2>/dev/null
;;
unload)
unset check_var_test2
unset common_value
_snippet pop "$SNIPPET_NAME" 2>/dev/null
;;
*)
true
;;
esac

return 0
2 changes: 0 additions & 2 deletions tests/profiles/test1.load

This file was deleted.

5 changes: 5 additions & 0 deletions tests/profiles/test1.profile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# comment and spaces will be stripped
# out while parsing the configuration

vartest1
varcommon # also if you comment the single snippet name
2 changes: 0 additions & 2 deletions tests/profiles/test1.unload

This file was deleted.

2 changes: 0 additions & 2 deletions tests/profiles/test2.load

This file was deleted.

2 changes: 2 additions & 0 deletions tests/profiles/test2.profile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
vartest2
varcommon
2 changes: 0 additions & 2 deletions tests/profiles/test2.unload

This file was deleted.

Loading

0 comments on commit 30e0991

Please sign in to comment.