├── MIT-LICENSE.txt ├── README.md ├── bash-ctx ├── install └── lib ├── git ├── heroku ├── rails4 ├── rails5 └── tmux /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Greg Navis (Grzegorz Niewisiewicz) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bash-ctx - Make working on multiple projects a breeze! 2 | 3 | I spend most of my time in a terminal and frequently issue commands that are 4 | _almost_ similar. Unfortunately, _almost_ makes a big difference. Here are a 5 | few things that can go wrong: 6 | 7 | * You rebase a branch on top of `master` instead of `develop` because another 8 | project you're working on doesn't have `develop` and you were confused. 9 | * You start the development server with wrong parameters as they're slightly 10 | different for every project. 11 | * You type `bundle exec rails routes | grep ` only to discover this is 12 | a Rails 4 project and you should have used `rake` instead of `rails`. 13 | 14 | `bash-ctx` helps you avoid such annoyances by helping you define 15 | project-specific aliases and configuration variables. You can use it to: 16 | 17 | * Start the development server with `.start` - doesn't matter whether it's 18 | Rails, Django, or any other framework. 19 | * Run the tests with `.test` - again, no need to think about which tool the 20 | current project is using. 21 | * Pull `master` and rebase the current branch on top of it just by calling 22 | `.update`. 23 | 24 | ## Installation 25 | 26 | Download and extract https://github.com/grn/bash-ctx/archive/master.zip and run 27 | `install`: 28 | 29 | ``` 30 | $ wget https://github.com/grn/bash-ctx/archive/master.zip 31 | $ unzip master.zip 32 | $ cd bash-ctx-master 33 | $ ./install 34 | ``` 35 | 36 | This will install `bash-ctx` in `~/.bash-ctx`. Remember to load it in your 37 | `.bashrc`: 38 | 39 | ```bash 40 | # Load bash-ctx if available. 41 | if [ -r "${HOME}/.bash-ctx/bash-ctx" ]; then 42 | source "${HOME}/.bash-ctx/bash-ctx" 43 | fi 44 | ``` 45 | 46 | ## Basic Concepts 47 | 48 | The two essential concepts in `bash-ctx` are: 49 | 50 | * Contexts - they represent project-specific settings and are stored in 51 | `~/.bash-ctx/ctx`. Each context is a file with bash commands to execute when 52 | entering the context. 53 | * Libraries or libs - common pieces of functionality you can share across your 54 | contexts. For example, all your Rails 5 projects can just use the `rails5` lib 55 | instead of redefining the same functions and aliases over and over again. 56 | 57 | You first create a context and then enter it. If you enter a context then its 58 | name is passed in an environment variable to a newly created bash child process 59 | (instead of just sourcing it in the calling process). Your `.bashrc` will then 60 | source the right context file. The benefit of this approach is that you avoid 61 | polluting your top-level shell with context-specific stuff and keep contexts 62 | separated. 63 | 64 | ## Usage 65 | 66 | Create a new context for your project: 67 | 68 | ``` 69 | ctx new my-project 70 | ``` 71 | 72 | Edit the newly created context file with: 73 | 74 | ``` 75 | ctx edit my-project 76 | ``` 77 | 78 | You can add project specific aliases (e.g. `alias .deploy='git push heroku'`), 79 | set environment variables, change directories, and so on. 80 | 81 | In order to enter the context run: 82 | 83 | ``` 84 | ctx enter my-project 85 | ``` 86 | 87 | This will launch a new `bash` shell and run the context script you created 88 | above. When you're done you can just quit this shell and return to the parent 89 | shell (where you called `ctx enter`). 90 | 91 | You can also list all contexts with `ctx list`: 92 | 93 | ``` 94 | ctx list 95 | my-context 96 | ``` 97 | 98 | ## Example Context File 99 | 100 | ``` 101 | ctx_libs git rails5 102 | 103 | # I often need to reset accounts but wouldn't like to reset the other tables. 104 | function .cleanup() { 105 | .run 'Account.update_all status: 0' 106 | } 107 | 108 | cd ~/Projects/MyProject 109 | ctx_libs tmux 110 | ``` 111 | 112 | ## Libs 113 | 114 | Libs are small shell script libraries that you can share across projects. It's 115 | an easy way to define aliases, functions, and set environment variables. 116 | Creating your own lib is easy. Just run `ctx lib ` and call `ctx_libs 117 | ` in your context file to include the lib in it. 118 | 119 | Below is a list of provided libs. 120 | 121 | ### `git` 122 | 123 | Allowed commands: 124 | 125 | * `.update [] []` - stash changes, pull the specified remote 126 | branch (as defined in `$CTX_GIT_ORIGIN` and `$CTX_GIT_BRANCH` which default to 127 | `origin` and `master`) and rebase the current branch on top of it. 128 | default) from the specified remote and rebase the current branch on top of it. 129 | * `.squash ` - squash the last `n` commits. 130 | * `.amend` - edit the current commit message (`git commit --amend -v`). 131 | * `.fix` - amend the current commit with the changes from the work tree 132 | (equivalent of `git commit -a --amend -C HEAD`). 133 | * `.commit ` - equivalent of `git commit -m `. 134 | 135 | Allowed configuration variables: 136 | 137 | * `CTX_GIT_ORIGIN` - the default origin. Defaults to `origin`. 138 | * `CTX_GIT_BRANCH` - the default branch from origin. Defaults to `master`. 139 | 140 | ### `rails4` and `rails5` 141 | 142 | All commands are prefixed by `bundle exec` and use `rake` (Rails 4) or `rails` 143 | (Rails 5): 144 | 145 | * `.migrate` - `rails db:migrate` 146 | * `.rollback` - `rails db:rollback` 147 | * `.seed` - `rails db:seed` 148 | * `.reset` - `rails db:reset` 149 | * `.start []` - `rails s ` 150 | * `.run []` - `rails runner ` 151 | * `.console []` - `rails console ` 152 | * `.database []` - `rails dbconsole ` 153 | * `.generate []` - `rails generate ` 154 | * `.destroy []` - `rails destroy ` 155 | * `.test []` - `rails test ` 156 | * `.rspec []` - `rspec ` 157 | * `.routes` - `rails routes` 158 | * `.routes ` - `rails routes | grep ` 159 | 160 | ### `tmux` 161 | 162 | Does not define any commands but runs `tmux` upon inclusion via `ctx_libs` 163 | unless already in a `tmux` session. 164 | 165 | ## Inspirations 166 | 167 | Upon entering a context you can: 168 | 169 | * Go to the project directory. 170 | * Start a `screen` or `tmux` session. 171 | * Set project-specific environment variables (e.g. `PATH` or the prompt). 172 | * Set project- or client-specific aliases and functions. 173 | * Start some services in the background (e.g. Redis, Guard). 174 | * Activate a Python virtual environment. 175 | * Use an rbenv gem set. 176 | 177 | ## Bugs, Feature and Enhancement Requests 178 | 179 | I'd love to hear your feedback! If something doesn't work, or should work 180 | differently, just file an issue at https://github.com/grn/bash-ctx/issues/new. 181 | -------------------------------------------------------------------------------- /bash-ctx: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # PUBLIC VARIABLES AND FUNCTIONS 4 | # ============================== 5 | 6 | CTX_BASE="${HOME}/.bash-ctx" # The top-most directory. 7 | CTX_BIN="${CTX_BASE}/bash-ctx" # The binary to source from .bashrc. 8 | CTX_ROOT="${CTX_BASE}/ctx" # The directory containing the contexts. 9 | CTX_LIB="${CTX_BASE}/lib" # The directory containing libraries. 10 | 11 | # Context management. Call `ctx help` for details. 12 | ctx() { 13 | local command="$1" 14 | 15 | shift 16 | 17 | case "${command}" in 18 | "new") _ctx_new $@ ;; 19 | "delete") _ctx_delete $@ ;; 20 | "enter") _ctx_enter $@ ;; 21 | "edit") _ctx_edit $@ ;; 22 | "lib") _ctx_lib $@ ;; 23 | "list") _ctx_list $@ ;; 24 | "help") _ctx_help ;; 25 | "") _ctx_help ;; 26 | *) _ctx_unknown "${command}" ;; 27 | esac 28 | } 29 | 30 | # SUBCOMMANDS 31 | # =========== 32 | 33 | # Usage: _ctx_new 34 | # Create a new context or report an _error if it does not exist. 35 | _ctx_new() { 36 | if [ $# -ne 1 ]; then 37 | _error "invalid invocation; expected: ctx new " 38 | return 39 | fi 40 | 41 | local ctx_path="${CTX_ROOT}/$1" 42 | 43 | if [ -e "${ctx_path}" ]; then 44 | _error "the context $1 already exist" 45 | return 46 | fi 47 | 48 | touch "${ctx_path}" 49 | _success "created context $1" 50 | } 51 | 52 | # Usage: _ctx_delete 53 | # Delete an existing context. 54 | _ctx_delete() { 55 | if [ "${CTX_NAME}" == "$1" ]; then 56 | _error "leave the current context before deleting it" 57 | fi 58 | 59 | local ctx_path="${CTX_ROOT}/$1" 60 | 61 | if [ ! -e "${ctx_path}" ]; then 62 | _error "the context $1 does not exist" 63 | return 64 | fi 65 | 66 | rm -f "${ctx_path}" 67 | } 68 | 69 | # Usage: _ctx_enter 70 | # Enter the specified context: call its enter script (if it exists) and restore 71 | # history. Reports an _error if the context does not exist or if already in 72 | # another context. Does nothing when called with the current context. 73 | _ctx_enter() { 74 | if [ "${CTX_NAME}" == "$1" ]; then 75 | return 76 | fi 77 | 78 | if [ -n "${CTX_NAME}" ]; then 79 | _error "leave the current context ${CTX_NAME} first" 80 | return 1 81 | fi 82 | 83 | local ctx_path="${CTX_ROOT}/$1" 84 | 85 | if [ ! -e "${ctx_path}" ]; then 86 | _error "the context $1 does not exist" 87 | return 1 88 | fi 89 | 90 | CTX_NAME="$1" bash 91 | } 92 | 93 | # Usage: _ctx_list 94 | # List existing contexts. 95 | _ctx_list() { 96 | command ls "${CTX_ROOT}" 97 | } 98 | 99 | # Usage: _ctx_edit 100 | # Edit the context file. 101 | _ctx_edit() { 102 | if [ -z "${EDITOR}" ]; then 103 | _error "set EDITOR to the editor of your choice" 104 | return 1 105 | fi 106 | 107 | local ctx_path="${CTX_ROOT}/$1" 108 | 109 | if [ ! -e "${ctx_path}" ]; then 110 | _error "the context $1 does not exist" 111 | return 1 112 | fi 113 | 114 | "$EDITOR" "${ctx_path}" 115 | } 116 | 117 | # Usage: _ctx_lib 118 | # Edit the lib file. 119 | _ctx_lib() { 120 | if [ -z "${EDITOR}" ]; then 121 | _error "set EDITOR to the editor of your choice" 122 | return 1 123 | fi 124 | 125 | local lib_path="${CTX_LIB}/$1" 126 | "$EDITOR" "${lib_path}" 127 | } 128 | 129 | # Usage: _ctx_help 130 | # Display a help for the commend. 131 | _ctx_help() { 132 | echo "Usage:" 133 | echo " ctx " 134 | echo 135 | echo "Commands:" 136 | echo " new create a new context" 137 | echo " delete delete an existing context" 138 | echo " enter enter a context" 139 | echo " edit edit a context file" 140 | echo " lib edit a lib file" 141 | echo " leave leave the current context" 142 | echo " list list all existing contexts" 143 | echo " help display this message" 144 | } 145 | 146 | # Usage: _ctx_unknown 147 | # Report an _error about an unknown command. 148 | _ctx_unknown() { 149 | _error "unknown command $1" 150 | _ctx_help 151 | return 1 152 | } 153 | 154 | # INTERNALS 155 | # ========= 156 | 157 | # Color sequences. 158 | _CTX_RED="$(tput setaf 1)" 159 | _CTX_GREEN="$(tput setaf 2)" 160 | _CTX_RESET="$(tput sgr0)" 161 | 162 | # Usage: _try_source 163 | # Load the specified bash script if it exists; do nothing otherwise. 164 | _try_source() { 165 | if [ -r "$1" ]; then 166 | source "$1" 167 | fi 168 | } 169 | 170 | # Usage: _error 171 | # Dispaly an error message. 172 | _error() { 173 | echo "${_CTX_RED}Error: $1${_CTX_RESET}" 174 | } 175 | 176 | # Usage: _success 177 | # Display a success message. 178 | _success() { 179 | echo "${_CTX_GREEN}Success: $1${_CTX_RESET}" 180 | } 181 | 182 | # COMMAND COMPLETION 183 | # ================== 184 | 185 | # Command line completion function. 186 | _ctx_complete() { 187 | local options='' 188 | 189 | case "${COMP_WORDS[COMP_CWORD - 1]}" in 190 | ctx) options='new delete enter edit lib list help' ;; 191 | delete) options=$(ls -1 "${CTX_ROOT}") ;; 192 | enter) options=$(ls -1 "${CTX_ROOT}") ;; 193 | edit) options=$(ls -1 "${CTX_ROOT}") ;; 194 | lib) options=$(ls -1 "${CTX_LIB}") ;; 195 | esac 196 | 197 | COMPREPLY=($(compgen -W "${options}" ${COMP_WORDS[COMP_CWORD]})) 198 | } 199 | 200 | # Complete ctx. 201 | complete -F _ctx_complete ctx 202 | 203 | # SUPPORTING FILES AND DIRECTORIES 204 | # ================================ 205 | 206 | # Usage: ctx_libs ... 207 | # Source the given files from the lib directory. 208 | ctx_libs() { 209 | while [ $# -gt 0 ]; do 210 | local lib_path="${CTX_LIB}/$1" 211 | shift 212 | source "${lib_path}" 213 | done 214 | } 215 | 216 | # Usage: _setup 217 | # Setup supporting files and directories if they don't exist. Enter the context 218 | # specified by CTX_NAME. 219 | _setup() { 220 | mkdir -p "${CTX_ROOT}" 221 | mkdir -p "${CTX_LIB}" 222 | 223 | if [ "${PS1+x}" = "x" -a "${-#*i}" != "$-" -a -n "${CTX_NAME}" ]; then 224 | local ctx_path="${CTX_ROOT}/${CTX_NAME}" 225 | _try_source "${ctx_path}" 226 | fi 227 | } 228 | 229 | _setup 230 | -------------------------------------------------------------------------------- /install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | TARGET="${HOME}/.bash-ctx" 6 | 7 | mkdir -p "${TARGET}" 8 | cp bash-ctx "${TARGET}" 9 | cp -r lib "${TARGET}" 10 | 11 | echo 12 | echo 'Please remember to add the following to your .bashrc:' 13 | echo 14 | echo 'if [ -r "${HOME}/.bash-ctx/bash-ctx" ]; then' 15 | echo ' source "${HOME}/.bash-ctx/bash-ctx"' 16 | echo 'fi' 17 | -------------------------------------------------------------------------------- /lib/git: -------------------------------------------------------------------------------- 1 | CTX_GIT_ORIGIN="${CTX_GIT_ORIGIN:-origin}" 2 | CTX_GIT_BRANCH="${CTX_GIT_BRANCH:-master}" 3 | 4 | function .update() { 5 | git pull --autostash --rebase \ 6 | "${1:-${CTX_GIT_ORIGIN}}" "${2:-${CTX_GIT_BRANCH}}" 7 | } 8 | 9 | function .squash() { 10 | if [ $# -ne 1 ]; then 11 | _error "Usage: .squash " 12 | return 1 13 | fi 14 | if [ $1 -le 1 ]; then 15 | _error "must squash at least 2 commits" 16 | return 1 17 | fi 18 | 19 | git rebase -i "HEAD~$1" 20 | } 21 | 22 | function .amend() { 23 | git commit --amend -v 24 | } 25 | 26 | function .fix() { 27 | git commit -a --amend -C HEAD 28 | } 29 | 30 | function .commit() { 31 | git commit -m "$1" 32 | } 33 | -------------------------------------------------------------------------------- /lib/heroku: -------------------------------------------------------------------------------- 1 | CTX_HEROKU_DEFAULT_ORIGIN="${CTX_HEROKU_DEFAULT_ORIGIN:-heroku}" 2 | 3 | .deploy() { 4 | if [ $# -eq 0 ]; then 5 | echo git push -f HEAD "${CTX_HEROKU_DEFAULT_ORIGIN}" 6 | else 7 | echo git push -f HEAD "$1" 8 | fi 9 | } 10 | -------------------------------------------------------------------------------- /lib/rails4: -------------------------------------------------------------------------------- 1 | .migrate() { 2 | bundle exec rake db:migrate 3 | } 4 | 5 | .rollback() { 6 | bundle exec rake db:rollback 7 | } 8 | 9 | .seed() { 10 | bundle exec rake db:seed 11 | } 12 | 13 | .reset() { 14 | bundle exec rake db:reset 15 | } 16 | 17 | .start() { 18 | bundle exec rails server "$@" 19 | } 20 | 21 | .run() { 22 | bundle exec rails runner "$@" 23 | } 24 | 25 | .console() { 26 | bundle exec rails console "$@" 27 | } 28 | 29 | .database() { 30 | bundle exec rails dbconsole "$@" 31 | } 32 | 33 | .generate() { 34 | bundle exec rails generate "$@" 35 | } 36 | 37 | .destroy() { 38 | bundle exec rails destroy "$@" 39 | } 40 | 41 | .test() { 42 | bundle exec rake test "$@" 43 | } 44 | 45 | .rspec() { 46 | bundle exec rspec "$@" 47 | } 48 | 49 | .routes() { 50 | if [ $# -eq 0 ]; then 51 | bundle exec rake routes 52 | else 53 | bundle exec rake routes | grep "$@" 54 | fi 55 | } 56 | -------------------------------------------------------------------------------- /lib/rails5: -------------------------------------------------------------------------------- 1 | .migrate() { 2 | bundle exec rails db:migrate 3 | } 4 | 5 | .rollback() { 6 | bundle exec rails db:rollback 7 | } 8 | 9 | .seed() { 10 | bundle exec rails db:seed 11 | } 12 | 13 | .reset() { 14 | bundle exec rails db:reset 15 | } 16 | 17 | .start() { 18 | bundle exec rails server "$@" 19 | } 20 | 21 | .run() { 22 | bundle exec rails runner "$@" 23 | } 24 | 25 | .console() { 26 | bundle exec rails console "$@" 27 | } 28 | 29 | .database() { 30 | bundle exec rails dbconsole "$@" 31 | } 32 | 33 | .generate() { 34 | bundle exec rails generate "$@" 35 | } 36 | 37 | .destroy() { 38 | bundle exec rails destroy "$@" 39 | } 40 | 41 | .test() { 42 | bundle exec rails test "$@" 43 | } 44 | 45 | .rspec() { 46 | bundle exec rspec "$@" 47 | } 48 | 49 | .routes() { 50 | if [ $# -eq 0 ]; then 51 | bundle exec rails routes 52 | else 53 | bundle exec rails routes | grep "$@" 54 | fi 55 | } 56 | -------------------------------------------------------------------------------- /lib/tmux: -------------------------------------------------------------------------------- 1 | if [ "$TERM" != "screen" -o -z "$TMUX" ]; then 2 | exec tmux 3 | fi 4 | --------------------------------------------------------------------------------