Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a layer system using snippets script #18

Merged
merged 31 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6f7d804
change profile directory name
gianluca-mascolo Sep 16, 2023
6b19d83
make format step
gianluca-mascolo Sep 16, 2023
52d116f
add function to manage snippets
gianluca-mascolo Sep 16, 2023
797bb02
rename .load to .profile
gianluca-mascolo Sep 16, 2023
d17fb31
add first test snippet
gianluca-mascolo Sep 16, 2023
5b4a1b4
precommit
gianluca-mascolo Sep 16, 2023
2abce59
precommit
gianluca-mascolo Sep 16, 2023
708203e
snip
gianluca-mascolo Sep 16, 2023
5862e02
snip
gianluca-mascolo Sep 16, 2023
c31cc60
second snippet
gianluca-mascolo Sep 16, 2023
5828ebf
test snippets
gianluca-mascolo Sep 17, 2023
c9bb336
rename snippets
gianluca-mascolo Sep 17, 2023
6d46d0c
load snippets
gianluca-mascolo Sep 17, 2023
9edb455
remove comment
gianluca-mascolo Sep 17, 2023
92c1b95
infere snippet name from bash source and add test from snippet
gianluca-mascolo Sep 18, 2023
d191892
example snippet
gianluca-mascolo Sep 18, 2023
fc26c25
example
gianluca-mascolo Sep 18, 2023
b85acb6
get rid of sed
gianluca-mascolo Sep 18, 2023
d8d4715
get snippets
gianluca-mascolo Sep 19, 2023
3bb686f
add comments to code
gianluca-mascolo Sep 19, 2023
83bb334
remove old files
gianluca-mascolo Sep 20, 2023
bc702f0
refine test
gianluca-mascolo Sep 20, 2023
4b15976
don't abuse arrays
gianluca-mascolo Sep 20, 2023
c98b30e
readability
gianluca-mascolo Sep 20, 2023
aedf08d
wrong file
gianluca-mascolo Sep 20, 2023
eb31e12
read reverse array
gianluca-mascolo Sep 21, 2023
f05b438
inline help
gianluca-mascolo Sep 21, 2023
64f7966
fix if for unsetted profile
gianluca-mascolo Sep 21, 2023
3763c69
example dir
gianluca-mascolo Sep 21, 2023
2a29ce5
readme
gianluca-mascolo Sep 21, 2023
8b3a384
avoid usage of variables prefixed with BASH_ because it may be reserv…
gianluca-mascolo Sep 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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