├── tests ├── testdata │ ├── execmode │ │ ├── execmode.bench │ │ └── wb.shelf │ ├── rctest │ │ ├── emptyrc │ │ │ └── empty.rc │ │ ├── custom.rc │ │ ├── .workbenchrc │ │ ├── emptyfolder │ │ │ └── README.md │ │ └── pre_exec_hook.rc │ └── wbhome │ │ ├── chain │ │ ├── shelfbench │ │ │ ├── wb.shelf │ │ │ └── bench.bench │ │ ├── skipshelf │ │ │ ├── wb.shelf │ │ │ └── skip │ │ │ │ └── bench.bench │ │ └── noshelf │ │ │ └── noshelf.bench │ │ ├── wb.shelf │ │ └── simple │ │ ├── wb.shelf │ │ └── outer │ │ ├── wb.shelf │ │ └── inner │ │ ├── simple2.bench │ │ ├── wb.shelf │ │ └── simple1.bench └── test_wb.py ├── asciinema ├── intro.gif └── intro.json ├── docs ├── source │ ├── _static │ │ ├── logo-black.png │ │ ├── logo-brown.png │ │ ├── logo-white.png │ │ └── padded │ │ │ └── logo-white-padded.png │ ├── devnotes │ │ ├── license.rst │ │ ├── testing.rst │ │ └── contrib.rst │ ├── first_steps │ │ ├── completion.rst │ │ ├── installation.rst │ │ └── configuration.rst │ ├── using_wb │ │ ├── exitcodes.rst │ │ ├── subshell.rst │ │ ├── environ.rst │ │ ├── concepts.rst │ │ ├── security.rst │ │ └── usage.rst │ ├── index.rst │ └── conf.py └── Makefile ├── .circleci └── config.yml ├── completion └── wb_complete.bash ├── Makefile ├── demo ├── demo.bench ├── wb.shelf └── go │ └── wb.shelf ├── README.md ├── LICENSE └── wb /tests/testdata/execmode/execmode.bench: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/testdata/rctest/emptyrc/empty.rc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/testdata/wbhome/chain/shelfbench/wb.shelf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/testdata/wbhome/chain/skipshelf/wb.shelf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/testdata/wbhome/wb.shelf: -------------------------------------------------------------------------------- 1 | echo "ROOT" 2 | -------------------------------------------------------------------------------- /tests/testdata/wbhome/chain/noshelf/noshelf.bench: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/testdata/wbhome/chain/shelfbench/bench.bench: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/testdata/wbhome/simple/wb.shelf: -------------------------------------------------------------------------------- 1 | echo "ROOT" 2 | -------------------------------------------------------------------------------- /tests/testdata/wbhome/chain/skipshelf/skip/bench.bench: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/testdata/rctest/custom.rc: -------------------------------------------------------------------------------- 1 | WORKBENCH_TEST=___custom___ 2 | -------------------------------------------------------------------------------- /tests/testdata/wbhome/simple/outer/wb.shelf: -------------------------------------------------------------------------------- 1 | echo "OUTER" 2 | -------------------------------------------------------------------------------- /tests/testdata/rctest/.workbenchrc: -------------------------------------------------------------------------------- 1 | WORKBENCH_TEST=___default___ 2 | -------------------------------------------------------------------------------- /tests/testdata/wbhome/simple/outer/inner/simple2.bench: -------------------------------------------------------------------------------- 1 | echo "SIMPLE2" 2 | -------------------------------------------------------------------------------- /tests/testdata/rctest/emptyfolder/README.md: -------------------------------------------------------------------------------- 1 | This folder is intentionally empty -------------------------------------------------------------------------------- /asciinema/intro.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pshirali/workbench/HEAD/asciinema/intro.gif -------------------------------------------------------------------------------- /docs/source/_static/logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pshirali/workbench/HEAD/docs/source/_static/logo-black.png -------------------------------------------------------------------------------- /docs/source/_static/logo-brown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pshirali/workbench/HEAD/docs/source/_static/logo-brown.png -------------------------------------------------------------------------------- /docs/source/_static/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pshirali/workbench/HEAD/docs/source/_static/logo-white.png -------------------------------------------------------------------------------- /tests/testdata/wbhome/simple/outer/inner/wb.shelf: -------------------------------------------------------------------------------- 1 | echo "INNER" 2 | workbench_new () { 3 | echo "Default-New" $@ 4 | } 5 | -------------------------------------------------------------------------------- /docs/source/_static/padded/logo-white-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pshirali/workbench/HEAD/docs/source/_static/padded/logo-white-padded.png -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/python:3.7.2 6 | steps: 7 | - checkout 8 | - run: make test 9 | -------------------------------------------------------------------------------- /tests/testdata/rctest/pre_exec_hook.rc: -------------------------------------------------------------------------------- 1 | workbench_pre_execute_hook () { 2 | echo "pre_execute_hook" 3 | return 22 # non-obvious returncode; update in test too! 4 | } 5 | -------------------------------------------------------------------------------- /tests/testdata/execmode/wb.shelf: -------------------------------------------------------------------------------- 1 | show_mode () { 2 | echo "$WORKBENCH_EXEC_MODE" 3 | } 4 | workbench_OnActivate () { show_mode; } 5 | workbench_OnRun () { show_mode; } 6 | workbench_OnNew () { show_mode; } 7 | -------------------------------------------------------------------------------- /tests/testdata/wbhome/simple/outer/inner/simple1.bench: -------------------------------------------------------------------------------- 1 | echo "SIMPLE1" 2 | workbench_OnActivate () { 3 | echo "Default-Activated" $@ 4 | } 5 | workbench_OnRun () { 6 | echo "Default-Run" $@ 7 | } 8 | exit () { 9 | echo "Default-Deactivated" 10 | builtin exit 11 | } 12 | -------------------------------------------------------------------------------- /docs/source/devnotes/license.rst: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | 5 | All content, logos, source-code under the WorkBench project is release under 6 | the Apache 2.0 license, unless explicitly stated otherwise. 7 | 8 | A copy of the license can be found in the LICENSE_ file in the WorkBench repo_. 9 | 10 | 11 | .. _LICENSE: https://github.com/pshirali/workbench/blob/master/LICENSE 12 | .. _repo: https://github.com/pshirali/workbench 13 | -------------------------------------------------------------------------------- /docs/source/devnotes/testing.rst: -------------------------------------------------------------------------------- 1 | Testing 2 | ======= 3 | 4 | 5 | WorkBench was built with TDD. Unittests are written in Python3's 6 | unittest framework. They are best run with Python 3.7. 7 | 8 | Tests can be run by cloning the repo and executing ``make test`` 9 | 10 | Code coverage is on the cards using ``bashcov``. This can be taken up 11 | after an enhancement in `bashcov` Issue-47_ is addressed. 12 | 13 | .. _Issue-47: https://github.com/infertux/bashcov/issues/47 14 | -------------------------------------------------------------------------------- /docs/source/first_steps/completion.rst: -------------------------------------------------------------------------------- 1 | Completion 2 | ========== 3 | 4 | 5 | A bash completer for WorkBench is available in the ``completion`` 6 | subdirectory of the WorkBench repo. 7 | 8 | To deploy it: 9 | 10 | 1. Download the file ``completion/wb_complete.bash`` 11 | 2. Add the following line to your ``.bashrc`` or ``.bash_profile`` where 12 | `` is the directory where ``wb_complete.bash`` is located 13 | 14 | .. code:: 15 | 16 | source "/wb_complete.bash" 17 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /completion/wb_complete.bash: -------------------------------------------------------------------------------- 1 | _wb_completion () { 2 | local cur prev 3 | COMPREPLY=() 4 | cur=${COMP_WORDS[COMP_CWORD]} 5 | prev=${COMP_WORDS[COMP_CWORD-1]} 6 | if [[ $COMP_CWORD -eq 1 ]]; then 7 | COMPREPLY=( $(compgen -W "s b a r n -V -E" -- $cur) ) 8 | elif [[ $COMP_CWORD -eq 2 ]]; then 9 | case "$prev" in 10 | "s") COMPREPLY=($(compgen -W "$(wb s)" -- $cur));; 11 | "b") COMPREPLY=($(compgen -W "$(wb b)" -- $cur));; 12 | "a") COMPREPLY=($(compgen -W "$(wb a)" -- $cur));; 13 | "r") COMPREPLY=($(compgen -W "$(wb r)" -- $cur));; 14 | "n") COMPREPLY=($(compgen -W "$(wb n)" -- $cur));; 15 | esac 16 | fi 17 | } 18 | complete -F _wb_completion wb 19 | -------------------------------------------------------------------------------- /docs/source/first_steps/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | 5 | WorkBench is under active development. A package based installer is 6 | currently not available. 7 | 8 | 9 | Install from source 10 | ~~~~~~~~~~~~~~~~~~~ 11 | 12 | 13 | WorkBench (``wb``) is a single bash file. You can use ``curl`` or ``wget`` to fetch 14 | the bleeding edge ``wb`` script from the WorkBench repository. 15 | 16 | 17 | Using curl 18 | ---------- 19 | 20 | .. code:: bash 21 | 22 | curl -fsSL https://raw.githubusercontent.com/pshirali/workbench/master/wb > wb 23 | 24 | Using wget 25 | ---------- 26 | 27 | .. code:: bash 28 | 29 | wget https://raw.githubusercontent.com/pshirali/workbench/master/wb 30 | 31 | 32 | Ensure that ``wb`` is placed in a folder which is in your ``PATH``. 33 | Set the execute bit for ``wb`` by running ``chmod +x `` 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | 3 | .PHONY: test 4 | test: ## Invoke tests 5 | @tests/test_wb.py -v 6 | 7 | .PHONY: docs 8 | docs: ## Open last built html docs 9 | @open docs/build/html/index.html 10 | 11 | .PHONY: install-deps 12 | install-deps: ## Install build tools and dependencies 13 | @pip3 install sphinx 14 | @pip3 install sphinx_rtd_theme 15 | 16 | .PHONY: clean-docs 17 | clean-docs: ## Remove docs/build folder 18 | @echo "Removing docs/build" 19 | @rm -rf docs/build 20 | 21 | .PHONY: clean 22 | clean: clean-docs ## Clean up all built data 23 | @echo "Done." 24 | 25 | .PHONY: build-docs 26 | build-docs: clean-docs ## Build html documentation 27 | @cd docs && make html 28 | 29 | .PHONY: build 30 | build: install-deps build-docs ## Install build dependencies and build 31 | @echo "Done." 32 | 33 | .PHONY: help 34 | help: 35 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-16s\033[0m %s\n", $$1, $$2}' 36 | -------------------------------------------------------------------------------- /docs/source/devnotes/contrib.rst: -------------------------------------------------------------------------------- 1 | Contribution 2 | ============ 3 | 4 | 5 | You are welcome to contribute to the WorkBench project. 6 | 7 | WorkBench has been built with the philosophy of `less-is-more`. It is 8 | nearly feature complete. No big features are planned. Improvements however 9 | are always welcome. 10 | 11 | 12 | Where can I contribute? 13 | ~~~~~~~~~~~~~~~~~~~~~~~ 14 | 15 | You can contribute to: 16 | 17 | 1. Discussing and suggesting improvements to provide hooks or tweaks, 18 | such that WorkBench could be adopted for use in more scenarios. 19 | 2. Testing: WorkBench compatibiltiy tests are work-in-progress. This 20 | involves testing against various bash versions and against other 21 | shells (zsh, ash, etc) 22 | 3. A plan to start a Wiki is on the cards, where you can contribute your 23 | ideas and recipies on the best ways to use WorkBench. 24 | 25 | 26 | How do I start? 27 | ~~~~~~~~~~~~~~~ 28 | 29 | 30 | You must start a discussion by opening a Github Issue first. You'll be 31 | guided on the next steps through the discussion. PR which don't go through 32 | this route will probably be rejected. 33 | -------------------------------------------------------------------------------- /docs/source/using_wb/exitcodes.rst: -------------------------------------------------------------------------------- 1 | Exit Codes 2 | ========== 3 | 4 | 5 | WorkBench exits with different exit-codes when it encounters errors. The table 6 | below lists the error names, exit-codes and a description. 7 | 8 | +----------------+-----------+------------------------------------+ 9 | | Error Name | ExitCode | Description | 10 | +================+===========+====================================+ 11 | | ERR_FATAL | 1 | General/fatal errors. | 12 | +----------------+-----------+------------------------------------+ 13 | | ERR_MISSING | 3 | Resource does not exist. | 14 | +----------------+-----------+------------------------------------+ 15 | | ERR_INVALID | 4 | Failed input validation. | 16 | +----------------+-----------+------------------------------------+ 17 | | ERR_DECLINED | 5 | Opted `No` on confirmation prompt | 18 | +----------------+-----------+------------------------------------+ 19 | | ERR_EXISTS | 6 | Resource already exists. | 20 | +----------------+-----------+------------------------------------+ 21 | -------------------------------------------------------------------------------- /docs/source/first_steps/configuration.rst: -------------------------------------------------------------------------------- 1 | Configuration 2 | ============= 3 | 4 | 5 | WorkBench accepts its configuration from environment variables which 6 | have the prefix ``WORKBENCH_``. WorkBench comes with sane defaults 7 | and external configuration is optional. 8 | 9 | 10 | Configuration using rcfile 11 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 12 | 13 | WorkBench can also source an `rcfile` on invocation. The default location 14 | for the `rcfile` is ``$HOME/.workbenchrc``. If a file at the default 15 | location exists, then it is automatically sourced. 16 | 17 | A custom `rcfile` can be specified using the environment variable 18 | ``WORKBENCH_RC`` pointing to a file that already exists. 19 | 20 | The `rcfile` can be used to define multiple configuration parameters 21 | at once. 22 | 23 | .. note:: 24 | The rcfile overrides environment variables defined in the shell. 25 | 26 | A full list of configurable parameters are available in subsequent chapters. 27 | 28 | 29 | The WorkBench Home directory 30 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 31 | 32 | 33 | WorkBench operates on files inside a directory defined by ``WORKBENCH_HOME``. 34 | If ``WORKBENCH_HOME`` is undefined, the default home directory 35 | ``$HOME/.workbench`` is used. WorkBench automatically creates the 36 | necessary folder(s) on invocation. 37 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to WorkBench's documentation 2 | ==================================== 3 | 4 | | 5 | 6 | .. image:: _static/logo-black.png 7 | :scale: 40% 8 | :align: center 9 | :alt: WorkBench Logo 10 | 11 | | 12 | 13 | 14 | WorkBench is a hierarchical environment manager for \*nix shells. It sources 15 | shell-code distributed across multiple levels of a folder hierarchy and 16 | invokes environments with the combination. Code could thus be implemented 17 | to operate at different scopes, allowing clear overrides at each folder depth 18 | and easy overall maintenance while managing several hundred environments. 19 | 20 | 21 | WorkBench is a minimalistic framework. It is extendable and configurable, 22 | and can adapt to a variety of use-cases. It is implemented as a single 23 | bash script, and designed to work with minimal dependencies even on 24 | vanilla \*nix systems. 25 | 26 | 27 | .. toctree:: 28 | :maxdepth: 2 29 | :caption: Getting Started: 30 | 31 | first_steps/installation 32 | first_steps/configuration 33 | first_steps/completion 34 | 35 | 36 | .. toctree:: 37 | :maxdepth: 2 38 | :caption: Using WorkBench: 39 | 40 | using_wb/subshell 41 | using_wb/concepts 42 | using_wb/usage 43 | using_wb/environ 44 | using_wb/exitcodes 45 | using_wb/security 46 | 47 | 48 | .. toctree:: 49 | :maxdepth: 2 50 | :caption: Developer Notes: 51 | 52 | devnotes/contrib.rst 53 | devnotes/testing.rst 54 | devnotes/license.rst 55 | 56 | 57 | Indices and tables 58 | ================== 59 | 60 | * :ref:`genindex` 61 | * :ref:`modindex` 62 | * :ref:`search` 63 | -------------------------------------------------------------------------------- /demo/demo.bench: -------------------------------------------------------------------------------- 1 | WHITE="\033[0;93m" 2 | RESET="\033[0;0m" 3 | 4 | workbench_OnActivate () { 5 | demo "" 6 | demo " ╔═╗ ┌─────╔═╗" 7 | demo " ╙ W 0 R K B E N C H" 8 | demo "" 9 | demo " Welcome to WorkBench. This is a demo." 10 | demo "" 11 | demo " ─────────────────────────────────────────────────────────────────────────" 12 | demo "" 13 | demo " You are currently able to see this text because you have" 14 | demo " WORKBENCH_DEMO environment variable set." 15 | demo "" 16 | demo " 1. Your current WORKBENCH_HOME is: ${HIGH}${WORKBENCH_HOME}${RESET}" 17 | demo " 2. The text you see here is from ${HIGH}WORKBENCH_HOME/demo.bench${RESET}" 18 | demo " 3. You might have also noticed that the PS1 has changed." 19 | demo " The code for that is in ${HIGH}WORKBENCH_HOME/wb.shelf${RESET}" 20 | demo " 4. Type '${HIGH}exit${RESET}' to exit from this workbench to your parent shell." 21 | demo " 'exit' has been overridden to print some messages before you actually exit." 22 | demo "" 23 | } 24 | 25 | exit () { 26 | if [[ $- == *i* ]]; then # print in interactive (activate) mode 27 | demo 28 | demo "--- [ deactivation demo ] ---------------------------------------------------" 29 | demo " The lines you see here are being printed from WORKBENCH_HOME/demo.bench" 30 | demo " A function named 'exit' has been declared, and implements the code which" 31 | demo " prints these lines." 32 | demo 33 | demo " This is followed by calling 'builtin exit' to actually exit the environment" 34 | demo "-----------------------------------------------------------------------------" 35 | demo 36 | fi 37 | builtin exit 2> /dev/null 38 | } 39 | -------------------------------------------------------------------------------- /docs/source/using_wb/subshell.rst: -------------------------------------------------------------------------------- 1 | Introduction to subshells 2 | ========================= 3 | 4 | 5 | .. note:: 6 | Skip this section if you are already familiar with bash subshells 7 | 8 | 9 | Consider a file ``abcd`` with the contents below: 10 | 11 | .. code:: bash 12 | 13 | export ABCD=10 14 | show_abcd () { 15 | echo "The value of ABCD is ${ABCD}" 16 | } 17 | alias c=clear 18 | 19 | 20 | A bash subshell could be invoked using: 21 | 22 | .. code:: bash 23 | 24 | bash --rcfile ./abcd 25 | 26 | 27 | While the prompt remains the same, a new interactive shell is now active. 28 | In this state, the following behavior can be observed: 29 | 30 | .. code:: bash 31 | 32 | >> echo $ABCD # value from the environemnt variable is printed 33 | 10 34 | 35 | >> show_abcd # a bash function is invoked 36 | The value of ABCD is 10 37 | 38 | >> c # alias for `clear`. Clears the screen. 39 | 40 | >> exit # exits the subshell 41 | 42 | 43 | On `exit` all context from the subshell is lost. It may be observed 44 | that executing the same commands in the parent shell does not result 45 | in the same behavior as what was seen in the subshell. 46 | 47 | 48 | An environment is a subshell initialised with environment variables, 49 | functions or aliases which caters specifically to a project or a task 50 | at hand. 51 | 52 | 53 | By using environments: 54 | 55 | 1. The parent shell's namespace remains free of project-specific declarations 56 | 2. Declarations are local to each environment. Commands and variables by the 57 | same name could be declared in each environment, which perform operations 58 | unique to that environment. 59 | 3. It is easy to exit from the subshell and unload the entire environment 60 | at once. 61 | 62 | -------------------------------------------------------------------------------- /demo/wb.shelf: -------------------------------------------------------------------------------- 1 | # 2 | # ----------------------------------------------------------------------------- 3 | # 4 | # This is the root `wb.shelf` file. Definitions that apply to all 5 | # workbenches are defined in this file. 6 | # 7 | # ----------------------------------------------------------------------------- 8 | 9 | 10 | demo () { [[ -n "${WORKBENCH_DEMO}" ]] && echo -e "$@"; } 11 | log () { echo -e "$@"; } 12 | err () { >&2 log "$@"; } 13 | 14 | 15 | # We'll use these ANSI colors to highlight specific information 16 | HIGH="\033[0;93m" # bright-yellow 17 | RESET="\033[0;0m" 18 | 19 | 20 | # 21 | # Print workbench related information. The 'info' command is available 22 | # in all workbenches. 23 | # 24 | info () { 25 | log 26 | log " ${HIGH}info${RESET} is a function declared in WORKBENCH_HOME/wb.shelf" 27 | log " You can call it from any directory as long as the workbench is active." 28 | log "" 29 | log " Your current workbench: ${HIGH}${WORKBENCH_ENV_NAME}${RESET}" 30 | log " Your WORKBENCH_HOME is: ${HIGH}${WORKBENCH_HOME}${RESET}" 31 | log "" 32 | log " The following shelves and bench have been sourced:" 33 | for part in $(set -f; IFS=:; printf "%s\n" ${WORKBENCH_CHAIN}); do 34 | log " └─ ${HIGH}${part}${RESET}" 35 | done 36 | log "" 37 | } 38 | 39 | 40 | # 41 | # Build a fancy PS1 that applies to all workbenches. This function could 42 | # be enhanced to include git branch, status and other useful bits. 43 | # WorkBench automatically stores the original PS1 in ORIG_PS1. You can 44 | # continue to redefine PS1 in subsequent shelves and benches. 45 | # 46 | # NOTE: Don't assign anything to ORIG_PS1. 47 | # 48 | ps1 () { 49 | 50 | # ANSI colors could be defined outside in global scope too. 51 | # The colors could be reused for other PS1 redeclarations if any 52 | local reset="\[\033[0;0m\]" 53 | local brRed="\[\033[0;91m\]" 54 | local brGreen="\[\033[0;92m\]" 55 | local brBlue="\[\033[0;94m\]" 56 | local brMagenta="\[\033[0;95m\]" 57 | local brYellow="\[\033[0;93m\]" 58 | local brWhite="\[\033[0;97m\]" 59 | 60 | # local gitIdentity=`git config --get user.email` 61 | local userEmail="your-git-config-user-email@goes.here" # dummy string 62 | 63 | local workbenchEnv="${brRed}[ ${WORKBENCH_ENV_NAME} ]${reset}" 64 | local userAtHost="${brYellow}\u${reset} at ${brMagenta}\h${reset}" 65 | local identity="${brBlue}${userEmail}${reset}" 66 | local prompt="${brGreen}\w${brWhite} > ${reset}" 67 | 68 | export PS1="${workbenchEnv} | ${userAtHost} as ${identity}\n${prompt}" 69 | } 70 | 71 | 72 | # Apply PS1 rightaway 73 | ps1 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WorkBench 2 | 3 | [![CircleCI](https://circleci.com/gh/pshirali/workbench.svg?style=shield)](https://circleci.com/gh/pshirali/workbench) 4 | 5 | **Status:** `Alpha` 6 | 7 |

8 | 9 |

10 | 11 | WorkBench is a hierarchical environment manager for \*nix shells. It sources 12 | shell-code distributed across multiple levels of a folder hierarchy and 13 | invokes environments with the combination. Code could thus be implemented 14 | to operate at different scopes, allowing clear overrides at each folder depth 15 | and easy overall maintenance while managing several hundred environments. 16 | 17 | 18 | ![Demo Screencast](asciinema/intro.gif) 19 | 20 | 21 | ## Documentation: [`wb.rtfd.io`](https://wb.rtfd.io) 22 | 23 | Refer to the documentation for installation, conceptual overview, 24 | usage and other reference material. 25 | 26 | ## Another env manager! Why? 27 | 28 | WorkBench is _yet another_ tool to manage environments. It is similar to (and is heavily inspired by) 29 | [Desk](https://github.com/jamesob/desk). 30 | 31 | WorkBench differs from other environment management tools in the following ways: 32 | 33 | 1. Hierarchical by nature. Code, by design can be distributed across files to fit shallow or deep hierarchies. 34 | 1. Minimal features. WorkBench only generates and runs the enviroment. It doesn't lookup or parse env code. 35 | 1. **_Batteries not included_**. You build your own stack of functions to fit your needs. 36 | 1. Configurable invocation modes and entrypoints. Easy to switch contexts via `rcfiles`. 37 | 1. **_Bring your own security_**. Implement your own hook logic before execution. Skip if you don't need it (CI/automated envs) 38 | 1. Scriptable; with distinct exit codes. Write wrappers to re-execute env code in CI or other build environments. 39 | 1. Scalable. Designed to reduce maintenance effort when you scale up to hundreds of environments. Easy to organize. 40 | 41 | WorkBench was built with a _less-is-more_ mindset. Docs were written with a _more-the-merrier_ mindset. :) 42 | 43 | WorkBench does have a learning curve. You will need to go through the documentation to understand its capabilities. 44 | It'd probably be some more time before you appreciate and exploit the hierarchy and flexibility of entrypoints. 45 | Hopefully, by then, it'll grow on you. 46 | 47 | 48 | ## Alternatives 49 | 50 | [Desk](https://github.com/jamesob/desk) is the best alternative. It comes with command & help lookup, and wider shell support out-of-the box. 51 | 52 | Others tools that flirt with environments include [direnv](https://github.com/direnv/direnv), [modules](http://modules.sourceforge.net/), [asdf](https://github.com/asdf-vm/asdf) 53 | 54 | 55 | ## Development status and roadmap 56 | 57 | WorkBench is currently _alpha_. While there really aren't any additional features planned, there may be some tweaks to improve how it interacts 58 | with the environment code. The CLI and the exitCodes are stable. 59 | 60 | WorkBench has _system tests_ written using Python3's unittest framework. 61 | No additional python packages are necessary to run tests. Python 3.7 is preferred. 62 | 63 | Following items will be taken up in the near future: 64 | 65 | 1. Code coverage (using [`bashcov`](https://github.com/infertux/bashcov)) 66 | 1. Wider shell support. Currently WorkBench has been written with `bash` in mind. It may undergo changes to be more POSIX compliant and get 67 | tested against a wider range of shells. 68 | 69 | 70 | ## License 71 | 72 | [`Apache 2.0`](LICENSE) 73 | -------------------------------------------------------------------------------- /demo/go/wb.shelf: -------------------------------------------------------------------------------- 1 | # 2 | # This file contains code which handles Go based projects. 3 | # 4 | # This demo code is not feature rich, and could do with more error 5 | # handling. Hopefully, it should be sufficient to explain how 6 | # WorkBench could be used to handle language virtualenvs. 7 | 8 | # ----------------------------------------------------------------------------- 9 | 10 | # 11 | # REPO_PATH is a folder within which the actual Go projects will reside. 12 | # This could be anywhere on your disk. It has no relation to the contents 13 | # of your WORKBENCH_HOME. All go/ workbenches will have an 14 | # associated folder inside REPO_PATH 15 | # 16 | # For the demo, we'll consider `_gotmp` inside `WORKBENCH_HOME` 17 | # go with the value below 18 | # 19 | REPO_PATH="${WORKBENCH_HOME}/_gotmp" 20 | 21 | # GO_ENV is the folder name within REPO_PATH, which represents the 22 | # go/. Here, we are currently considering just the basename. 23 | # While this shortens the path, it could cause conflicts if you have 24 | # benches at different depths with the same name. 25 | # 26 | # Example: 27 | # go/abcd/viper REPO_PATH/viper 28 | # go/viper REPO_PATH/viper 29 | # 30 | # The fullname `WORKBENCH_ENV_NAME` or any modification thereof could 31 | # also be used. 32 | GO_ENV="$(basename ${WORKBENCH_ENV_NAME})" 33 | 34 | # Logger/Printer 35 | gowiz () { log "${HIGH}GoWiz:${RESET}" $@; } 36 | 37 | 38 | # 39 | # Instead of implementing all the logic in workbench_OnActivate etc., 40 | # we'd instead place it in independent functions, and call these 41 | # from the workbench_* functions. 42 | # 43 | # Nested shelves, or benches could implement their own workbench_ 44 | # functions, which could call go_* with some wrapper code. 45 | # 46 | 47 | go_OnActivate () { 48 | local projectPath="${REPO_PATH}/${GO_ENV}/src/${GO_URL}" 49 | [[ -d "${projectPath}" ]] && cd "${projectPath}" 50 | export GOPATH="${REPO_PATH}/${GO_ENV}" 51 | } 52 | 53 | go_OnNew () { 54 | if [[ -z "$1" ]]; then 55 | log 56 | log " ── [ GoWizard: Create a new Golang project ] ──" 57 | log 58 | log " USAGE:" 59 | log " wb n <..go/projectName> [--clone]" 60 | log 61 | log " ACTIONS:" 62 | log " 1. Create Go folder structure inside REPO_PATH" 63 | log " 2. Clone the repo from the if --clone is supplied" 64 | log " 3. Write a benchFile for the " 65 | log 66 | builtin exit 0 67 | fi 68 | 69 | local url="$1" 70 | [[ -z "${url}" ]] && err "ERROR: Supplying a projectUrl is mandatory!" && exit 1 71 | local clone="$2" 72 | 73 | # 74 | # Ref: https://golang.org/doc/code.html#Workspaces 75 | # 76 | 77 | # [1] Create Go folders 78 | local goPath="${REPO_PATH}/${GO_ENV}" 79 | gowiz "Creating Go folders for ${HIGH}${WORKBENCH_ENV_NAME}${RESET} in ${REPO_PATH}" 80 | mkdir -p "${goPath}" "${goPath}/bin" "${goPath}/src" "${goPath}/pkg" 81 | 82 | # [2] Either create empty directory or clone the repo 83 | if [[ -z "${clone}" ]]; then 84 | gowiz "Creating project folder with URL (${url})" 85 | mkdir -p "${goPath}/src/${url}" 86 | else 87 | gowiz "Creating project folder and cloning repo." 88 | mkdir -p "$(dirname ${goPath}/src/${url})" 89 | cd "$(dirname ${goPath}/src/${url})" 90 | echo "----------------------------------------------------------------" 91 | git clone "https://${url}" 92 | echo "----------------------------------------------------------------" 93 | fi 94 | 95 | # [3] Write contents for the new benchFile 96 | local benchFile="${WORKBENCH_HOME}/${WORKBENCH_ENV_NAME}.${WORKBENCH_BENCH_EXTN}" 97 | gowiz "Writing contents to the bench: ${benchFile}" 98 | if [[ -f "${benchFile}" ]] && [[ ! -s "${benchFile}" ]]; then 99 | echo "GO_URL=${url}" > "${benchFile}" 100 | fi 101 | 102 | gowiz "Done. Run '${HIGH}wb a ${WORKBENCH_ENV_NAME}${RESET}' to activate this workbench." 103 | } 104 | 105 | 106 | workbench_OnActivate () { go_OnActivate $@; } 107 | workbench_OnNew () { go_OnNew $@; } 108 | -------------------------------------------------------------------------------- /docs/source/using_wb/environ.rst: -------------------------------------------------------------------------------- 1 | Environment Variables 2 | ===================== 3 | 4 | 5 | The table below contains a list of environment variables which WorkBench 6 | consumes in its configuration. 7 | 8 | 9 | +-------------------------------+------------------------+--------------------------------------------------------+ 10 | | Environment Variable Name | Default Value | Description | 11 | +===============================+========================+========================================================+ 12 | | WORKBENCH_RC | $HOME/.workbenchrc | Auto-load location for the rcfile | 13 | +-------------------------------+------------------------+--------------------------------------------------------+ 14 | | WORKBENCH_HOME | $HOME/.workbench | Directory containg shelves and benches | 15 | +-------------------------------+------------------------+--------------------------------------------------------+ 16 | | WORKBENCH_ALLOW_INSECURE_PATH | -- | Skips using 'realpath' if set. | 17 | +-------------------------------+------------------------+--------------------------------------------------------+ 18 | | WORKBENCH_GREPPER | egrep | Grep tool used to list env. vars | 19 | +-------------------------------+------------------------+--------------------------------------------------------+ 20 | | WORKBENCH_AUTOCONFIRM | -- | Skip confirmation prompt for `rm` if set | 21 | +-------------------------------+------------------------+--------------------------------------------------------+ 22 | | WORKBENCH_SHELF_FILE | wb.shelf | Filename for the shelf file | 23 | +-------------------------------+------------------------+--------------------------------------------------------+ 24 | | WORKBENCH_BENCH_EXTN | bench | File extension for the bench file | 25 | +-------------------------------+------------------------+--------------------------------------------------------+ 26 | | WORKBENCH_ACTIVATE_CMD | /bin/bash --rcfile | Command to invoke subshell in intereactive mode | 27 | +-------------------------------+------------------------+--------------------------------------------------------+ 28 | | WORKBENCH_COMMAND_CMD | /bin/bash -c | Command to invoke a script in non-interactive mode | 29 | +-------------------------------+------------------------+--------------------------------------------------------+ 30 | | WORKBENCH_ACTIVATE_FUNC | workbench_OnActivate | Entrypoint function name for the `activate` command | 31 | +-------------------------------+------------------------+--------------------------------------------------------+ 32 | | WORKBENCH_RUN_FUNC | workbench_OnRun | Entrypoint function name for the `run` command | 33 | +-------------------------------+------------------------+--------------------------------------------------------+ 34 | | WORKBENCH_NEW_FUNC | workbench_OnNew | Entrypoint function name for the `new` command | 35 | +-------------------------------+------------------------+--------------------------------------------------------+ 36 | 37 | 38 | The table below contains a list of environment variables which are injected as part of the 39 | auto-generated `workbench`. 40 | 41 | 42 | +---------------------------+--------------------------------------------------------------------+ 43 | | Environment Variable Name | Description | 44 | +===========================+====================================================================+ 45 | | WORKBENCH_ENV_NAME | Name of the currently active `bench` | 46 | +---------------------------+--------------------------------------------------------------------+ 47 | | WORKBENCH_EXEC_MODE | The mode in which the workbench was launched. One of 'a', 'c', 'n' | 48 | +---------------------------+--------------------------------------------------------------------+ 49 | | WORKBENCH_CHAIN | A ``:`` separated list of every sourced shelf and bench | 50 | +---------------------------+--------------------------------------------------------------------+ 51 | | ORIG_PS1 | Stores the existing ``PS1`` before redefining it | 52 | +---------------------------+--------------------------------------------------------------------+ 53 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | # import os 16 | # import sys 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'WorkBench' 23 | copyright = '2019, Praveen G Shirali' 24 | author = 'Praveen G Shirali' 25 | 26 | # The short X.Y version 27 | version = '' 28 | # The full version, including alpha/beta/rc tags 29 | release = '0.0.1' 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 'sphinx.ext.githubpages', 43 | ] 44 | 45 | # Add any paths that contain templates here, relative to this directory. 46 | templates_path = ['_templates'] 47 | 48 | # The suffix(es) of source filenames. 49 | # You can specify multiple suffix as a list of string: 50 | # 51 | # source_suffix = ['.rst', '.md'] 52 | source_suffix = '.rst' 53 | 54 | # The master toctree document. 55 | master_doc = 'index' 56 | 57 | # The language for content autogenerated by Sphinx. Refer to documentation 58 | # for a list of supported languages. 59 | # 60 | # This is also used if you do content translation via gettext catalogs. 61 | # Usually you set "language" from the command line for these cases. 62 | language = None 63 | 64 | # List of patterns, relative to source directory, that match files and 65 | # directories to ignore when looking for source files. 66 | # This pattern also affects html_static_path and html_extra_path. 67 | exclude_patterns = [] 68 | 69 | # The name of the Pygments (syntax highlighting) style to use. 70 | pygments_style = None 71 | 72 | 73 | # -- Options for HTML output ------------------------------------------------- 74 | 75 | # The theme to use for HTML and HTML Help pages. See the documentation for 76 | # a list of builtin themes. 77 | # 78 | html_theme = 'sphinx_rtd_theme' 79 | 80 | # Theme options are theme-specific and customize the look and feel of a theme 81 | # further. For a list of options available for each theme, see the 82 | # documentation. 83 | # 84 | html_theme_options = { 85 | "logo_only": True 86 | } 87 | html_logo = "_static/padded/logo-white-padded.png" 88 | 89 | # Add any paths that contain custom static files (such as style sheets) here, 90 | # relative to this directory. They are copied after the builtin static files, 91 | # so a file named "default.css" will overwrite the builtin "default.css". 92 | html_static_path = ['_static'] 93 | 94 | # Custom sidebar templates, must be a dictionary that maps document names 95 | # to template names. 96 | # 97 | # The default sidebars (for documents that don't match any pattern) are 98 | # defined by theme itself. Builtin themes are using these templates by 99 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 100 | # 'searchbox.html']``. 101 | # 102 | # html_sidebars = {} 103 | 104 | 105 | # -- Options for HTMLHelp output --------------------------------------------- 106 | 107 | # Output file base name for HTML help builder. 108 | htmlhelp_basename = 'WorkBenchdoc' 109 | 110 | 111 | # -- Options for LaTeX output ------------------------------------------------ 112 | 113 | latex_elements = { 114 | # The paper size ('letterpaper' or 'a4paper'). 115 | # 116 | # 'papersize': 'letterpaper', 117 | 118 | # The font size ('10pt', '11pt' or '12pt'). 119 | # 120 | # 'pointsize': '10pt', 121 | 122 | # Additional stuff for the LaTeX preamble. 123 | # 124 | # 'preamble': '', 125 | 126 | # Latex figure (float) alignment 127 | # 128 | # 'figure_align': 'htbp', 129 | } 130 | 131 | # Grouping the document tree into LaTeX files. List of tuples 132 | # (source start file, target name, title, 133 | # author, documentclass [howto, manual, or own class]). 134 | latex_documents = [ 135 | (master_doc, 'WorkBench.tex', 'WorkBench Documentation', 136 | 'Praveen G Shirali', 'manual'), 137 | ] 138 | 139 | 140 | # -- Options for manual page output ------------------------------------------ 141 | 142 | # One entry per manual page. List of tuples 143 | # (source start file, name, description, authors, manual section). 144 | man_pages = [ 145 | (master_doc, 'workbench', 'WorkBench Documentation', 146 | [author], 1) 147 | ] 148 | 149 | 150 | # -- Options for Texinfo output ---------------------------------------------- 151 | 152 | # Grouping the document tree into Texinfo files. List of tuples 153 | # (source start file, target name, title, author, 154 | # dir menu entry, description, category) 155 | texinfo_documents = [ 156 | (master_doc, 'WorkBench', 'WorkBench Documentation', 157 | author, 'WorkBench', 'One line description of project.', 158 | 'Miscellaneous'), 159 | ] 160 | 161 | 162 | # -- Options for Epub output ------------------------------------------------- 163 | 164 | # Bibliographic Dublin Core info. 165 | epub_title = project 166 | 167 | # The unique identifier of the text. This can be a ISBN number 168 | # or the project homepage. 169 | # 170 | # epub_identifier = '' 171 | 172 | # A unique identification for the text. 173 | # 174 | # epub_uid = '' 175 | 176 | # A list of files that should not be packed into the epub file. 177 | epub_exclude_files = ['search.html'] 178 | 179 | 180 | # -- Extension configuration ------------------------------------------------- 181 | -------------------------------------------------------------------------------- /docs/source/using_wb/concepts.rst: -------------------------------------------------------------------------------- 1 | WorkBench Concepts 2 | ================== 3 | 4 | 5 | Introduction 6 | ~~~~~~~~~~~~ 7 | 8 | 9 | WorkBench makes it easy to work with a large number of custom shell 10 | environment scripts, each of which could be tailor-made for a project or task. 11 | 12 | WorkBench sources shell code spread across different depths of a directory 13 | tree to construct an environment automatically. Code could thus be implemented 14 | in parts, residing in files at different directory depths, and without 15 | any hardcoded references. 16 | 17 | WorkBench operates only on files present inside a directory as defined by 18 | ``WORKBENCH_HOME``. It uses two abstract terms to refer to 19 | parts of a to-be-assembled environment; namely ``Shelf`` and ``Bench``. 20 | 21 | 22 | Shelves and Benches 23 | ~~~~~~~~~~~~~~~~~~~ 24 | 25 | **EXAMPLE**: Consider a ``WORKBENCH_HOME`` with the following structure: 26 | 27 | .. code:: 28 | 29 | WORKBENCH_HOME 30 | ├── ash.bench # BENCH 31 | ├── bar/ 32 | │   ├── baz/ 33 | │   │   ├── maple.bench # BENCH 34 | │   │   └── wb.shelf # SHELF 35 | │   └── birch.bench # BENCH 36 | ├── foo/ 37 | │   ├── pine.bench # BENCH 38 | │   └── wb.shelf # SHELF 39 | └── wb.shelf # SHELF 40 | 41 | Shelf 42 | ----- 43 | 44 | A ``shelf`` is ``WORKBENCH_HOME``, or any subdirectory inside it, 45 | which contains the file as defined by ``WORKBENCH_SHELF_FILE``. The default 46 | value for ``WORKBENCH_SHELF_FILE`` is ``wb.shelf``. A ``shelf`` is always a 47 | path relative to ``WORKBENCH_HOME``. Shelf names end with a trailing ``/`` 48 | as they represent the directory containing ``WORKBENCH_SHELF_FILE`` and not 49 | the file itself. 50 | 51 | In the example above, the file ``wb.shelf`` is present at three locations. 52 | Hence, there are three shelves here. 53 | 54 | .. code:: 55 | 56 | / 57 | foo/ 58 | bar/baz/ 59 | 60 | 61 | The table below maps the name of the `shelf` to the underlying resource file: 62 | 63 | +---------------+-----------------------------------------------+ 64 | | Shelf Name | Underlying resource filename | 65 | +===============+===============================================+ 66 | | / | WORKBENCH_HOME/wb.shelf | 67 | +---------------+-----------------------------------------------+ 68 | | foo/ | WORKBENCH_HOME/foo/wb.shelf | 69 | +---------------+-----------------------------------------------+ 70 | | bar/baz/ | WORKBENCH_HOME/bar/baz/wb.shelf | 71 | +---------------+-----------------------------------------------+ 72 | 73 | The subdirectory ``bar/`` is not a `shelf` because it doesn't 74 | contain ``wb.shelf``. 75 | 76 | 77 | Bench 78 | ----- 79 | 80 | A ``bench`` is a file anywhere inside ``WORKBENCH_HOME`` with the 81 | extension as defined by ``WORKBENCH_BENCH_EXTN``. The default value for 82 | ``WORKBENCH_BENCH_EXTN`` is ``bench``. The extension separator ``.`` is 83 | assumed automatically and is not part of the value. Bench names are 84 | representative of files. They do not include the trailing 85 | ``.`` 86 | 87 | In the example above, there are four files with a ``.bench`` extension. 88 | Hence, four benches. 89 | 90 | .. code:: 91 | 92 | ash 93 | bar/baz/maple 94 | bar/birch 95 | foo/pine 96 | 97 | 98 | The table below maps the name of the `bench` to the underlying resource file: 99 | 100 | +---------------+-----------------------------------------------+ 101 | | Bench Name | Underlying resource filename | 102 | +===============+===============================================+ 103 | | ash | WORKBENCH_HOME/ash.bench | 104 | +---------------+-----------------------------------------------+ 105 | | bar/baz/maple | WORKBENCH_HOME/bar/baz/maple.bench | 106 | +---------------+-----------------------------------------------+ 107 | | bar/birch | WORKBENCH_HOME/bar/birch.bench | 108 | +---------------+-----------------------------------------------+ 109 | | foo/pine | WORKBENCH_HOME/foo/pine.bench | 110 | +---------------+-----------------------------------------------+ 111 | 112 | 113 | Analogy 114 | ~~~~~~~ 115 | 116 | Analogous to a real workbench, the top of a `bench` is where the work 117 | gets done. A discerning artisan might place minimal tools required for the 118 | task at hand on the `bench`, while rest of the tools might be placed in 119 | `shelves`, each of which ordered based on the frequency in which they get 120 | used; frequently used tools being closer than infrequent ones. 121 | 122 | The abstract `shelf` (a place to stow tools) may also be imagined as a 123 | pegboard where tools are hung for easy access. An artisan can locate any 124 | tool quickly, use it and put it back. 125 | 126 | In WorkBench, the `shelf` hierarchy is provided by the possible presence 127 | of the ``WORKBENCH_SHELF_FILE`` at different directory depths leading upto 128 | the ``Bench``. 129 | 130 | Code that is declared in a ``Shelf`` at the root; that is ``WORKBENCH_HOME`` 131 | will be sourced by every `workbench`. Code that is applicable only to a 132 | specific set of environments could be defined in ``Shelves`` in a subdirectory 133 | at the appropriate depth. Thus declarations & implementations common 134 | to multiple environments get organised into ``Shelves``, while declarations 135 | which uniquely associate with one environment get placed in a ``Bench``. 136 | 137 | A pegboard approach could also be implemented by declaring functions in 138 | various ``Shelves`` but not calling them. The ``Bench`` would call those 139 | functions with various parameters for the task at hand. 140 | 141 | 142 | Benefits 143 | ~~~~~~~~ 144 | 145 | 146 | 1. Overall there is less code to maintain. 147 | 2. It is easy to influence control on a whole group of environments by moving 148 | code to a ``Shelf`` at the appropriate subdirectory 149 | 3. Redeclaration results in overriding. Code in a `shelf` at a deeper depth 150 | overrides those at lower depths (closer to ``WORKBENCH_HOME``). Code in 151 | a `bench` overrides all `shelves`. The workbench `tree` could be designed 152 | to be shallow, or deeply nested to cater to the amount of overriding 153 | required. 154 | 4. The hierarchical structure lends itself to organising and managing a tree 155 | of hundreds of `benches` easily. 156 | -------------------------------------------------------------------------------- /docs/source/using_wb/security.rst: -------------------------------------------------------------------------------- 1 | Security 2 | ======== 3 | 4 | 5 | WorkBench is a bash script capable of executing shell code from your system. 6 | This page discloses some of the inner-workings of WorkBench for user 7 | awareness. It also covers guidelines and best-practices that you should 8 | follow to securely use WorkBench. 9 | 10 | It is vital that you understand the contents of this page before you use 11 | WorkBench. A discerning user might find the contents here a tad verbose. 12 | However, it is in the best interest of a potential user. 13 | 14 | This document assumes that you are already operate a secure system where the 15 | statements below (but not limited to) are true: 16 | 17 | 1. You trust the OS binaries that are installed. 18 | 2. Only you have access to the contents of your `user` directory. (`$HOME`) 19 | 3. You own and understand the contents of your shell's rcfiles. (like `.bashrc`). 20 | 21 | The rest of the this page discusses how WorkBench fits in, and the baggage 22 | that it brings with it. 23 | 24 | 25 | Single User Context 26 | ~~~~~~~~~~~~~~~~~~~ 27 | 28 | 29 | WorkBench is designed for a single-user. You should use WorkBench on systems 30 | where you (as a \*nix user), and ONLY YOU own and are in are in 31 | complete control of: 32 | 33 | 1. WorkBench (the tool), and the location where it is deployed. 34 | 2. The location(s) and contents of all `WORKBENCH_RC` files 35 | 3. The location(s) and contents of all `WORKBENCH_HOME` folders (the entire tree) 36 | 37 | Every time you run `wb`, you are not only executing the code in `wb`, but 38 | also the contents of the `rcfile`. The default location ``$HOME/.workbenchrc`` 39 | is tried if `WORKBENCH_RC` is not defined. The `rcfile` here is a shell 40 | script. The code within it will execute even without the `execute` permission 41 | set on the file (similar to your `.bashrc`, `.bash_profile`) 42 | 43 | Depending on values defined against `WORKBENCH_SHELF_FILE` and 44 | `WORKBENCH_BENCH_EXTN`, all files matching the filename and extension 45 | respectively within your `WORKBENCH_HOME` are assumed to be shell scripts. 46 | Code from these files will be sourced when you invoke `wb a`, `wb r` or `wb n` 47 | commands. As above, they too don't require the `execute` permission to be 48 | set on them. 49 | 50 | WorkBench is not in control of any files or commands that may be sourced or 51 | executed within `shelves` or `benches`. It is possible that code 52 | (content within the `shelf` or `bench`) might source files outside of 53 | `WORKBENCH_HOME`, or outside of your `user` directory too. 54 | 55 | The name of the `entrypoint` function that WorkBench executes can be 56 | redefined using `WORKBENCH_ACTIVATE_FUNC`, `WORKBENCH_RUN_FUNC` and 57 | `WORKBENCH_NEW_FUNC` respectively. Depending on the command invoked, the 58 | control will land on one of these functions. 59 | 60 | The values for these variables can be replaced by any executable binary or 61 | an existing definition in your current shell (parent shell). 62 | 63 | **Example:** 64 | 65 | .. code:: 66 | 67 | WORKBENCH_RUN_FUNC=echo wb r Hello World 68 | 69 | Will print "Hello World" in the last line of the command's output. 70 | 71 | 72 | Guidelines 73 | ---------- 74 | 75 | 1. Ensure/change the ownership of ``wb``, `rcfiles` and all contents of 76 | `WORKBENCH_HOME` to you (as a user). Ensure that `write` and `execute` 77 | permissions are not available for `group` and `all`. 78 | 79 | **Ideal permissions:** 80 | 81 | .. code-block:: none 82 | 83 | chmod 0700 84 | chmod 0600 85 | chomd 0600 86 | 87 | 2. Do not introduce new content into `WORKBENCH_HOME` that you haven't 88 | personally written or reviewed. 89 | 90 | **For example:** 91 | 92 | (a) Do not extract archives inside `WORKBENCH_HOME`. 93 | (b) Do not ``git clone`` repositories into your `WORKBENCH_HOME`. 94 | 95 | You must treat the contents of `WORKBENCH_HOME` in the same light as 96 | your `rcfiles`, `dotfiles` etc. 97 | 98 | 99 | Detecting changes in your WORKBENCH_HOME 100 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 101 | 102 | WorkBench provides a function ``workbench_pre_execute_hook`` which allows 103 | you to implement your own `pre` checks before executing a `workbench`. This 104 | is an ideal place to implement checks to track change to `WORKBENCH_HOME`. 105 | 106 | You can implement ``workbench_pre_execute_hook`` as a function inside your 107 | `WORKBENCH_RC`. If the function returns with a non-zero return code, 108 | WorkBench will exit with that code. 109 | 110 | Perhaps the easiest way to achieve this would be to turn your `WORKBENCH_HOME` 111 | into a Git repo and let Git track your changes. (Example: ``git status -s``) 112 | 113 | 114 | Canonical paths and directory traversal 115 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 116 | 117 | 118 | WorkBench uses the ``realpath`` (GNU) utility to convert all relative paths to 119 | absolute paths before operating on them. WorkBench ensures that every `shelf` 120 | and `bench` that gets sourced as part of building a `workbench`, also reside 121 | within ``WORKBENCH_HOME``. 122 | 123 | .. important:: 124 | 125 | 1. ``WORKBENCH_RC`` and ``WORKBENCH_HOME`` are excluded from checks. 126 | It is highly recommended that they reside inside your ``HOME`` 127 | directory, but this is not enforced. 128 | 2. WorkBench does not detect `source` statements inside the code residing 129 | in a `shelf` or `bench`. Placing such `source` statements is discouraged. 130 | If you do, then you should ensure that you `source` it from locations 131 | within ``WORKBENCH_HOME``. 132 | 133 | It is possible that ``realpath`` might not be present on every OS, and you 134 | might have to install it before using WorkBench. 135 | 136 | WorkBench also provides a way to disable this feature. You can do so by 137 | setting ``WORKBENCH_ALLOW_INSECURE_PATH`` to any value to disable directory 138 | traversal checks. 139 | 140 | 141 | What is a directory traversal attack? How is it harmful? 142 | -------------------------------------------------------- 143 | 144 | 145 | Directory traversal attack is a way by which software is made to expose 146 | or operate on files outside a directory boundary. It takes the form of an 147 | `attack` when it is used with malicious intent. WorkBench implements checks 148 | largely to prevent inadvertent sourcing of content. 149 | 150 | A directory traversal attack involves a `path` derived from user input which 151 | includes ``../``. This indicates the parent of the intended directory. 152 | With directory traversal checks disabled, one could supply a command like: 153 | ``wb r ../benchName`` to access a `shelf` and a `bench` that is located at 154 | the parent directory of ``WORKBENCH_HOME``. The input could include multiple 155 | ``../`` to craft a `path` that points to any other file on your drive. 156 | 157 | .. note:: 158 | 159 | WorkBench strips preceeding ``/`` from `shelf` and `bench` names, 160 | and makes them relative to `WORKBENCH_HOME`. This handles the 161 | case of input `shelf` or `bench` names supplied as absolute paths. 162 | 163 | 164 | Temp files 165 | ~~~~~~~~~~ 166 | 167 | 168 | WorkBench creates temp files with the auto-generated `workbench` contents 169 | when the commands ``wb a``, ``wb r``, ``wb n`` are executed without the 170 | ``--dump`` switch. The temp files are created using ``mktemp`` utility. 171 | This creates a file within ``/tmp`` with the content that you see in the 172 | ``--dump`` switch. The temp files have a default permission `0600` which 173 | makes them accessible to only you, the user. WorkBench deletes the temp 174 | file after the command completes execution. 175 | -------------------------------------------------------------------------------- /docs/source/using_wb/usage.rst: -------------------------------------------------------------------------------- 1 | Usage Guide 2 | =========== 3 | 4 | 5 | WorkBench has a minimal set of commands. They are also short (usually one 6 | character). 7 | 8 | .. note:: 9 | The following convention denotes ``OR``. 10 | Example: ``wb a|b|c`` means ``wb a`` OR ``wb b`` OR ``wb c`` 11 | 12 | 13 | View version and env -- [``wb -V``, ``wb -E``] 14 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 15 | 16 | ``wb -V`` prints the version of WorkBench being used. 17 | 18 | ``wb -E`` lists all environment variables starting with ``WORKBENCH_``. 19 | These environment variables may be defined in your current shell, or 20 | may be defined in a ``WORKBENCH_RC`` file. 21 | 22 | If you use an `rcfile` with WorkBench, the values you set in the `rcfile` 23 | will apply over everything else. The `rcfile` is sourced on every ``wb`` 24 | invocation regardless of the command. 25 | 26 | 27 | Operating on Shelves and Benches -- [``wb s``, ``wb b``] 28 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 29 | 30 | The following operations can be performed on `shelves` and `benches`:. 31 | 32 | List 33 | ---- 34 | 35 | WorkBenches can be listed using ``wb s|b`` 36 | 37 | 38 | Print path to the underlying file 39 | --------------------------------- 40 | 41 | ``wb s|b ``, where `` is either a `` or `` 42 | prints the absolute path to the underlying resource file associated with 43 | that shelf or bench. 44 | 45 | The path is generated and displayed for non-existent shelves and benches as 46 | well. A non-zero exit-code is returned if a shelf or bench doesn't exist. 47 | 48 | 49 | Run a command against the underlying file 50 | ----------------------------------------- 51 | 52 | ``wb s|b [options] [[arg]..]`` 53 | 54 | Runs `` [[arg]..] `` 55 | 56 | Examples: 57 | 58 | .. code:: 59 | 60 | wb s cat # view the file WORKBENCH_HOME/.../ 61 | wb b vim # edit the file WORKBENCH_HOME/.../ in ViM 62 | 63 | 64 | Commands execute only when a or exist on disk. 65 | It is possible to create a new `shelf` or `bench` inline, just before 66 | running a command on it by adding the ``--new`` switch. 67 | 68 | .. code:: 69 | 70 | wb s --new vim 71 | 72 | WorkBench prompts for confirmation if the `` is ``rm``. The 73 | ``--yes`` switch can be used to indicate `Yes` to skip the prompt. Alternatively, 74 | ``WORKBENCH_AUTOCONFIRM`` can be set to any non-empty value to disable 75 | this prompt and assume `Yes` always. 76 | 77 | 78 | Auto-generated `workbench` and Entrypoints 79 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 80 | 81 | 82 | A `workbench` is the auto-generated code composed by WorkBench (the tool), 83 | when the command ``wb a|r|n `` is executed. 84 | 85 | The switch ``--dump`` can be used to print the auto-generated code on `stdout` 86 | instead of executing it. The ``--dump`` switch does not validate the presence 87 | of a ``. This switch can be used to review the generated code. 88 | 89 | The auto-generated `workbench` has the following high-level sections: 90 | 91 | .. code:: 92 | 93 | ┌───────────────┐ 94 | │ INIT │ <---- Initial declarations are done here 95 | ├───────────────┤ 96 | │ SOURCE │ <---- Shelves and bench are `sourced` here 97 | ├───────────────┤ 98 | │ ENTRYPOINT │ <---- Entrypoint function is called with `args` 99 | └───────────────┘ 100 | 101 | The `INIT` section of the `workbench` contains basic/no-op implementations 102 | for the default functions. `Shelves` and `Bench` are expected to define their 103 | own functions with an actual implementation to override those in `INIT`. 104 | 105 | The `INIT` section defines the following variables: 106 | 107 | 1. ``WORKBENCH_ENV_NAME``: Stores the `benchName` as the environment name 108 | 2. ``ORIG_PS1``: Stores the current ``PS1``, while ``PS1`` is reset to 109 | prefix the current `benchName` 110 | 3. ``WORKBENCH_CHAIN``: Stores a ``:`` separated list of each sourced `shelf` 111 | and `bench` in the order in which they were sourced. 112 | 113 | An entrypoint is a shell functions invoked after sourcing all the `shelves` 114 | and the `bench`. Each WorkBench execution command has a different 115 | `entrypoint` function associated with it. Any trailing arguments passed 116 | to the WorkBench's execution command are passed on to the `entrypoint`. 117 | 118 | Entrypoint function names are configurable. The table below lists the 119 | environment variables which define the `entrypoints` and the default 120 | function names associated with each of them. 121 | 122 | +------------+---------------------------+------------------------+---------+ 123 | | Type | Environment Variable Name | Default Function Name | Command | 124 | +============+===========================+========================+=========+ 125 | | entrypoint | WORKBENCH_ACTIVATE_FUNC | workbench_OnActivate | a | 126 | +------------+---------------------------+------------------------+---------+ 127 | | entrypoint | WORKBENCH_RUN_FUNC | workbench_OnRun | r | 128 | +------------+---------------------------+------------------------+---------+ 129 | | entrypoint | WORKBENCH_NEW_FUNC | workbench_OnNew | n | 130 | +------------+---------------------------+------------------------+---------+ 131 | 132 | The `entrypoint` is invoked by calling the entrypoint environment variable. 133 | Thus the value of the entrypoint environment variable can be redefined in 134 | the `shelf` or the `bench` to point to a non-default function as well. 135 | 136 | Entrypoint Example 137 | ------------------ 138 | 139 | When ``wb r arg1 arg2`` is executed, then the function that 140 | maps to ``WORKBENCH_RUN_FUNC`` becomes the actual entrypoint. 141 | The default entrypoint function name is ``workbench_OnRun`` and the 142 | `INIT` section has an implemetation for it. 143 | 144 | A `shelf` or `bench` could redeclare ``workbench_OnRun`` multiple times; 145 | in files at different depths. The last declared implementation will be the one 146 | that executes with arguments ``arg`` ``arg2`` 147 | 148 | It is also possible that ``WORKBENCH_RUN_FUNC`` could be assigned a new 149 | value like ``my_custom_func`` anywhere in the `shelves` or the `bench`. 150 | The last declared value of ``WORKBENCH_RUN_FUNC`` is now the new 151 | entrypoint function, and the last declared implementation of the 152 | function ``my_custom_func`` is the one that executes with 153 | arguments ``arg1`` ``arg2`` 154 | 155 | 156 | Executing `workbench` environments -- [``wb a``, ``wb r``, ``wb n``] 157 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 158 | 159 | 160 | The `workbench` stores the execution command in the varible 161 | ``WORKBENCH_EXEC_MODE``. `Shelf` and `Bench` code could take decisions 162 | based on this value. 163 | 164 | A no-op function ``workbench_pre_execute_hook`` executes just before 165 | a `workbench` is built. This function could be implemented by the 166 | ``WORKBENCH_RC`` with logic that decides whether to go ahead with 167 | execution. Refer to the `Security` chapter for more details. 168 | 169 | 170 | Activate -- [``wb a``] 171 | ---------------------- 172 | 173 | The `activate` command is equivalent of ``bash --rcfile ``. It 174 | spawns a subshell with the auto-generated `workbench`, with 175 | ``WORKBENCH_ACTIVATE_FUNC`` as the entrypoint. 176 | 177 | Nested `activations` are prevented by checking if ``WORKBENCH_ENV_NAME`` has 178 | already been set. 179 | 180 | Deactivating a `workbench` is done by simply running `exit`. 181 | 182 | Occasionally, there may be cases where some code needs to be executed when 183 | an `exit` is issued. This can be achieved by redeclaring the `exit` function, 184 | calling user-defined code, followed by calling `builtin exit`. 185 | 186 | **Example:** 187 | 188 | .. code:: 189 | 190 | exit () { 191 | 192 | builtin exit $? 2> /dev/null 193 | } 194 | 195 | 196 | Run -- [``wb r``] 197 | ----------------- 198 | 199 | The `run` command is the equivalent of ``bash -c ``. It 200 | executes the `workbench` non-interactively, with ``WORKBENCH_RUN_FUNC`` 201 | as the entrypoint. The `run` command is used to invoke one-off commands 202 | which may be defined in the `workbench`. 203 | 204 | For example, a `workbench` could declare subcommands like ``start``, ``stop``, 205 | ``build``, ``deploy`` etc, as independent functions. The entrypoint function 206 | defined by ``WORKBENCH_RUN_FUNC`` could parse arguments and dispatch them 207 | to respective subcommands. 208 | 209 | Thus, for the same `workbench`, the `activate` and `run` commands could be 210 | used to trigger different functionality. 211 | 212 | 213 | New -- [``wb n``] 214 | ----------------- 215 | 216 | The `new` command is a variant of the `run` command. It's execution is 217 | similar to that of the `run` command (non-interactive), but with 218 | ``WORKBENCH_NEW_FUNC`` as the entrypoint. 219 | 220 | When the command ``wb n `` is invoked, WorkBench creates 221 | all intermediate `shelf` files (if they don't already exist) followed by 222 | the `bench`. The `bench` must either not exist, or must be a zero-byte file. 223 | 224 | The last declared function as defined by ``WORKBENCH_NEW_FUNC`` is then 225 | called, which is expected to write contents into the new `bench`. 226 | 227 | Consider a programming language like Python, Go etc. All projects of a 228 | language would require a common set of steps to build up a workspace for 229 | the language. For Python, tools like `virtualenv`, with `virtualenvwrapper` 230 | are already available. Similar tools exist for other languages too. 231 | 232 | It is easy to implement code in a `shelf` to define the behavior for all 233 | projects for a particular language/group. The code could wrap around an 234 | existing tool (like `virtualenv`) or provide all functionality by itself. 235 | 236 | The aspect that varies between each project of a language might be: (a) Name, 237 | (b) Project URL, may be (c) language version etc. But, such values are few. 238 | The `shelf's` implementation of ``WORKBENCH_NEW_FUNC`` could request this 239 | information for a new project and dump the metadata into the `bench`. 240 | The `bench` could therefore be minimal; may be an `env` file with key-values. 241 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Praveen G Shirali 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /asciinema/intro.json: -------------------------------------------------------------------------------- 1 | {"version": 2, "width": 89, "height": 20, "timestamp": 1551274947, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}} 2 | [0.015332, "o", "\u001b[?1034hbash-3.2$ "] 3 | [0.680558, "o", "w"] 4 | [0.850393, "o", "b"] 5 | [0.986685, "o", " "] 6 | [1.241278, "o", "|"] 7 | [1.325358, "o", " "] 8 | [1.644395, "o", "m"] 9 | [1.708247, "o", "o"] 10 | [1.764467, "o", "r"] 11 | [1.820284, "o", "e"] 12 | [2.149255, "o", "\r\n"] 13 | [2.156666, "o", "\u001b[?1h\u001b=\r"] 14 | [2.185682, "o", "\r\n ╔═╗ ┌─────╔═╗\r\n ╙ W 0 R K B E N C H Version 0.0.1\r\n\r\n\r\n ══ USAGE ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─\r\n\r\n [[ENV=]...] wb [args]\r\n\r\n\r\n ══ ENV CONFIGURATION ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─\r\n\r\n WORKBENCH_RC=\r\n Source a supplied rcfile before execution. If unspecified,\r\n then the default location '$HOME/.workbenchrc' is tried.\r\n\r\n\r\n ══ COMMANDS (Use '-h' for additional help/options [+]) ─ ─ ─ ─ ─ ─ ─ ─\r\n\r\n:\u001b[K"] 15 | [3.432218, "o", "\r\u001b[K"] 16 | [3.432629, "o", "\r\n -V Show version and quit\r\n -E Show WORKBENCH environment variables and quit\r\n\r\n s List shelves. Operate on a shelf file. [+]\r\n b List benches. Operate on a bench file. [+]\r\n\r\n a List benches. Activate a workbench. [+]\r\n r List benches. Run a command from a workbench. [+]\r\n n List benches. Create a new bench. [+]\r\n\r\n\r\n ══ DESCRIPTION ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─\r\n\r\n WorkBench is a hierarchical environment manager for the shell.\r\n You can use it to build project-specific virtualenvs, each having\r\n its own set of env variables, aliases and functions.\r\n\r\n WorkBench builds an environment (called 'workbench') by overlaying\r\n:\u001b[K"] 17 | [7.425108, "o", "\r\u001b[K\u001b[?1l\u001b>"] 18 | [7.426171, "o", "bash-3.2$ "] 19 | [8.430884, "o", "w"] 20 | [8.591902, "o", "b"] 21 | [8.78143, "o", " "] 22 | [9.75891, "o", "-"] 23 | [9.922942, "o", "V"] 24 | [10.778877, "o", "\r\n"] 25 | [10.812794, "o", "0.0.1\r\n"] 26 | [10.813377, "o", "bash-3.2$ "] 27 | [11.319867, "o", "w"] 28 | [11.456938, "o", "b"] 29 | [11.580751, "o", " "] 30 | [11.986028, "o", "s"] 31 | [12.248985, "o", "\r\n"] 32 | [12.29734, "o", "/\r\ngo/\r\n"] 33 | [12.298308, "o", "bash-3.2$ "] 34 | [13.464212, "o", "w"] 35 | [13.611667, "o", "b"] 36 | [13.723699, "o", " "] 37 | [13.839609, "o", "b"] 38 | [14.134756, "o", "\r\n"] 39 | [14.179095, "o", "demo\r\n"] 40 | [14.180081, "o", "bash-3.2$ "] 41 | [15.207742, "o", "w"] 42 | [15.341387, "o", "b"] 43 | [15.472724, "o", " "] 44 | [15.763616, "o", "n"] 45 | [15.831653, "o", " "] 46 | [16.068628, "o", "g"] 47 | [16.180868, "o", "o"] 48 | [16.47684, "o", "/"] 49 | [16.636553, "o", "v"] 50 | [16.73664, "o", "i"] 51 | [16.796703, "o", "p"] 52 | [16.872603, "o", "e"] 53 | [16.94164, "o", "r"] 54 | [16.988807, "o", " "] 55 | [17.143531, "o", "g"] 56 | [17.211603, "o", "i"] 57 | [17.30957, "o", "t"] 58 | [17.381611, "o", "h"] 59 | [17.491635, "o", "u"] 60 | [17.623647, "o", "b"] 61 | [17.738619, "o", "."] 62 | [17.864554, "o", "c"] 63 | [17.904597, "o", "o"] 64 | [17.969055, "o", "m"] 65 | [18.100484, "o", "/"] 66 | [18.21956, "o", "s"] 67 | [18.475382, "o", "p"] 68 | [18.758474, "o", "f"] 69 | [18.995409, "o", "1"] 70 | [19.075459, "o", "3"] 71 | [19.282801, "o", "/"] 72 | [19.428591, "o", "v"] 73 | [19.528601, "o", "i"] 74 | [19.588438, "o", "p"] 75 | [19.668911, "o", "e"] 76 | [19.739149, "o", "r"] 77 | [19.816448, "o", " "] 78 | [20.082167, "o", "-"] 79 | [20.181443, "o", "-"] 80 | [20.345499, "o", "c"] 81 | [20.49759, "o", "l"] 82 | [20.686438, "o", "o"] 83 | [20.822849, "o", "n"] 84 | [20.914444, "o", "e"] 85 | [21.272634, "o", "\r\n"] 86 | [21.379697, "o", "\u001b[0;93mGoWiz:\u001b[0;0m Creating Go folders for \u001b[0;93mgo/viper\u001b[0;0m in /Users/praveen/workbench/demo/_gotmp\r\n"] 87 | [21.382825, "o", "\u001b[0;93mGoWiz:\u001b[0;0m Creating project folder and cloning repo.\r\n"] 88 | [21.390852, "o", "----------------------------------------------------------------\r\n"] 89 | [21.40177, "o", "Cloning into 'viper'...\r\n"] 90 | [23.598192, "o", "remote: Enumerating objects: 824, done.\u001b[K\r\n"] 91 | [23.6088, "o", "Receiving objects: 0% (1/824) \r"] 92 | [23.609213, "o", "Receiving objects: 1% (9/824) \r"] 93 | [23.609833, "o", "Receiving objects: 2% (17/824) \r"] 94 | [23.610343, "o", "Receiving objects: 3% (25/824) \r"] 95 | [23.611019, "o", "Receiving objects: 4% (33/824) \r"] 96 | [23.611611, "o", "Receiving objects: 5% (42/824) \r"] 97 | [23.6119, "o", "Receiving objects: 6% (50/824) \r"] 98 | [23.612239, "o", "Receiving objects: 7% (58/824) \r"] 99 | [23.612561, "o", "Receiving objects: 8% (66/824) \r"] 100 | [23.612966, "o", "Receiving objects: 9% (75/824) \r"] 101 | [23.613299, "o", "Receiving objects: 10% (83/824) \r"] 102 | [23.61361, "o", "Receiving objects: 11% (91/824) \r"] 103 | [23.613954, "o", "Receiving objects: 12% (99/824) \r"] 104 | [23.614181, "o", "Receiving objects: 13% (108/824) \r"] 105 | [23.614485, "o", "Receiving objects: 14% (116/824) \r"] 106 | [23.614729, "o", "Receiving objects: 15% (124/824) \r"] 107 | [23.614963, "o", "Receiving objects: 16% (132/824) \r"] 108 | [23.615232, "o", "Receiving objects: 17% (141/824) \r"] 109 | [23.615494, "o", "Receiving objects: 18% (149/824) \r"] 110 | [23.615732, "o", "Receiving objects: 19% (157/824) \r"] 111 | [23.911067, "o", "Receiving objects: 20% (165/824) \r"] 112 | [23.911477, "o", "Receiving objects: 21% (174/824) \r"] 113 | [23.912235, "o", "Receiving objects: 22% (182/824) \r"] 114 | [23.912767, "o", "Receiving objects: 23% (190/824) \r"] 115 | [23.913237, "o", "Receiving objects: 24% (198/824) \r"] 116 | [23.924355, "o", "Receiving objects: 25% (206/824) \r"] 117 | [23.92472, "o", "Receiving objects: 26% (215/824) \r"] 118 | [23.925303, "o", "Receiving objects: 27% (223/824) \r"] 119 | [23.925988, "o", "Receiving objects: 28% (231/824) \r"] 120 | [23.926394, "o", "Receiving objects: 29% (239/824) \r"] 121 | [25.234691, "o", "Receiving objects: 29% (244/824), 68.01 KiB | 39.00 KiB/s \r"] 122 | [25.235188, "o", "Receiving objects: 30% (248/824), 68.01 KiB | 39.00 KiB/s \r"] 123 | [25.238715, "o", "Receiving objects: 31% (256/824), 68.01 KiB | 39.00 KiB/s \r"] 124 | [25.24098, "o", "Receiving objects: 32% (264/824), 68.01 KiB | 39.00 KiB/s \r"] 125 | [25.244312, "o", "Receiving objects: 33% (272/824), 68.01 KiB | 39.00 KiB/s \r"] 126 | [25.321082, "o", "Receiving objects: 34% (281/824), 68.01 KiB | 39.00 KiB/s \r"] 127 | [25.32961, "o", "Receiving objects: 35% (289/824), 68.01 KiB | 39.00 KiB/s \r"] 128 | [25.332649, "o", "Receiving objects: 36% (297/824), 68.01 KiB | 39.00 KiB/s \r"] 129 | [25.535712, "o", "Receiving objects: 37% (305/824), 68.01 KiB | 39.00 KiB/s \r"] 130 | [25.547509, "o", "Receiving objects: 38% (314/824), 68.01 KiB | 39.00 KiB/s \r"] 131 | [25.666975, "o", "Receiving objects: 39% (322/824), 68.01 KiB | 39.00 KiB/s \r"] 132 | [25.667786, "o", "Receiving objects: 40% (330/824), 68.01 KiB | 39.00 KiB/s \r"] 133 | [25.66886, "o", "Receiving objects: 41% (338/824), 68.01 KiB | 39.00 KiB/s \r"] 134 | [25.755021, "o", "Receiving objects: 42% (347/824), 212.01 KiB | 96.00 KiB/s \r"] 135 | [25.755891, "o", "Receiving objects: 43% (355/824), 212.01 KiB | 96.00 KiB/s \rReceiving objects: 44% (363/824), 212.01 KiB | 96.00 KiB/s \r"] 136 | [25.756519, "o", "Receiving objects: 45% (371/824), 212.01 KiB | 96.00 KiB/s \r"] 137 | [25.760099, "o", "Receiving objects: 46% (380/824), 212.01 KiB | 96.00 KiB/s \r"] 138 | [25.760326, "o", "Receiving objects: 47% (388/824), 212.01 KiB | 96.00 KiB/s \r"] 139 | [25.760413, "o", "Receiving objects: 48% (396/824), 212.01 KiB | 96.00 KiB/s \r"] 140 | [25.761025, "o", "Receiving objects: 49% (404/824), 212.01 KiB | 96.00 KiB/s \rReceiving objects: 50% (412/824), 212.01 KiB | 96.00 KiB/s \r"] 141 | [25.7626, "o", "Receiving objects: 51% (421/824), 212.01 KiB | 96.00 KiB/s \r"] 142 | [25.883193, "o", "Receiving objects: 52% (429/824), 212.01 KiB | 96.00 KiB/s \r"] 143 | [25.971125, "o", "Receiving objects: 53% (437/824), 212.01 KiB | 96.00 KiB/s \r"] 144 | [25.971915, "o", "Receiving objects: 54% (445/824), 212.01 KiB | 96.00 KiB/s \r"] 145 | [25.977373, "o", "Receiving objects: 55% (454/824), 212.01 KiB | 96.00 KiB/s \r"] 146 | [26.099575, "o", "Receiving objects: 56% (462/824), 212.01 KiB | 96.00 KiB/s \r"] 147 | [26.100738, "o", "Receiving objects: 57% (470/824), 212.01 KiB | 96.00 KiB/s \r"] 148 | [26.187017, "o", "Receiving objects: 58% (478/824), 212.01 KiB | 96.00 KiB/s \r"] 149 | [26.188434, "o", "Receiving objects: 59% (487/824), 212.01 KiB | 96.00 KiB/s \r"] 150 | [26.188998, "o", "Receiving objects: 60% (495/824), 212.01 KiB | 96.00 KiB/s \r"] 151 | [26.192576, "o", "Receiving objects: 61% (503/824), 212.01 KiB | 96.00 KiB/s \r"] 152 | [26.312945, "o", "Receiving objects: 62% (511/824), 340.01 KiB | 124.00 KiB/s \r"] 153 | [26.315781, "o", "Receiving objects: 63% (520/824), 340.01 KiB | 124.00 KiB/s \r"] 154 | [26.316238, "o", "Receiving objects: 64% (528/824), 340.01 KiB | 124.00 KiB/s \rReceiving objects: 65% (536/824), 340.01 KiB | 124.00 KiB/s \r"] 155 | [26.316879, "o", "Receiving objects: 66% (544/824), 340.01 KiB | 124.00 KiB/s \r"] 156 | [26.3174, "o", "Receiving objects: 67% (553/824), 340.01 KiB | 124.00 KiB/s \r"] 157 | [26.317767, "o", "Receiving objects: 68% (561/824), 340.01 KiB | 124.00 KiB/s \r"] 158 | [26.318083, "o", "Receiving objects: 69% (569/824), 340.01 KiB | 124.00 KiB/s \r"] 159 | [26.318398, "o", "Receiving objects: 70% (577/824), 340.01 KiB | 124.00 KiB/s \rReceiving objects: 71% (586/824), 340.01 KiB | 124.00 KiB/s \r"] 160 | [26.318663, "o", "Receiving objects: 72% (594/824), 340.01 KiB | 124.00 KiB/s \rReceiving objects: 73% (602/824), 340.01 KiB | 124.00 KiB/s \r"] 161 | [26.404143, "o", "Receiving objects: 74% (610/824), 340.01 KiB | 124.00 KiB/s \r"] 162 | [26.40528, "o", "Receiving objects: 75% (618/824), 340.01 KiB | 124.00 KiB/s \r"] 163 | [26.405727, "o", "Receiving objects: 76% (627/824), 340.01 KiB | 124.00 KiB/s \r"] 164 | [26.511938, "o", "Receiving objects: 77% (635/824), 340.01 KiB | 124.00 KiB/s \r"] 165 | [26.529713, "o", "Receiving objects: 78% (643/824), 340.01 KiB | 124.00 KiB/s \r"] 166 | [26.617155, "o", "Receiving objects: 78% (649/824), 340.01 KiB | 124.00 KiB/s \r"] 167 | [26.617482, "o", "Receiving objects: 79% (651/824), 340.01 KiB | 124.00 KiB/s \r"] 168 | [26.620312, "o", "Receiving objects: 80% (660/824), 340.01 KiB | 124.00 KiB/s \r"] 169 | [26.621737, "o", "Receiving objects: 81% (668/824), 340.01 KiB | 124.00 KiB/s \r"] 170 | [26.622679, "o", "Receiving objects: 82% (676/824), 340.01 KiB | 124.00 KiB/s \r"] 171 | [26.623278, "o", "Receiving objects: 83% (684/824), 340.01 KiB | 124.00 KiB/s \r"] 172 | [26.743024, "o", "Receiving objects: 84% (693/824), 340.01 KiB | 124.00 KiB/s \r"] 173 | [26.747359, "o", "Receiving objects: 85% (701/824), 340.01 KiB | 124.00 KiB/s \r"] 174 | [26.748244, "o", "Receiving objects: 86% (709/824), 340.01 KiB | 124.00 KiB/s \r"] 175 | [26.749372, "o", "Receiving objects: 87% (717/824), 340.01 KiB | 124.00 KiB/s \r"] 176 | [26.833164, "o", "Receiving objects: 88% (726/824), 468.01 KiB | 143.00 KiB/s \r"] 177 | [26.833742, "o", "Receiving objects: 89% (734/824), 468.01 KiB | 143.00 KiB/s \rReceiving objects: 90% (742/824), 468.01 KiB | 143.00 KiB/s \r"] 178 | [26.834401, "o", "Receiving objects: 91% (750/824), 468.01 KiB | 143.00 KiB/s \r"] 179 | [26.834617, "o", "Receiving objects: 92% (759/824), 468.01 KiB | 143.00 KiB/s \r"] 180 | [26.83617, "o", "Receiving objects: 93% (767/824), 468.01 KiB | 143.00 KiB/s \r"] 181 | [26.836718, "o", "remote: Total 824 (delta 0), reused 0 (delta 0), pack-reused 824\u001b[K\r\nReceiving objects: 94% (775/824), 468.01 KiB | 143.00 KiB/s \rReceiving objects: 95% (783/824), 468.01 KiB | 143.00 KiB/s \rReceiving objects: 96% (792/824), 468.01 KiB | 143.00 KiB/s \rReceiving objects: 97% (800/824), 468.01 KiB | 143.00 KiB/s \r"] 182 | [26.837173, "o", "Receiving objects: 98% (808/824), 468.01 KiB | 143.00 KiB/s \r"] 183 | [26.837497, "o", "Receiving objects: 99% (816/824), 468.01 KiB | 143.00 KiB/s \r"] 184 | [26.837863, "o", "Receiving objects: 100% (824/824), 468.01 KiB | 143.00 KiB/s \rReceiving objects: 100% (824/824), 485.06 KiB | 150.00 KiB/s, done.\r\n"] 185 | [26.838638, "o", "Resolving deltas: 0% (0/508) \r"] 186 | [26.839374, "o", "Resolving deltas: 1% (6/508) \r"] 187 | [26.840057, "o", "Resolving deltas: 2% (14/508) \rResolving deltas: 3% (17/508) \r"] 188 | [26.840413, "o", "Resolving deltas: 4% (21/508) \r"] 189 | [26.842548, "o", "Resolving deltas: 8% (42/508) \r"] 190 | [26.846759, "o", "Resolving deltas: 15% (79/508) \r"] 191 | [26.849958, "o", "Resolving deltas: 25% (128/508) \r"] 192 | [26.850377, "o", "Resolving deltas: 26% (136/508) \r"] 193 | [26.851238, "o", "Resolving deltas: 29% (151/508) \r"] 194 | [26.851297, "o", "Resolving deltas: 30% (154/508) \r"] 195 | [26.852724, "o", "Resolving deltas: 37% (189/508) \r"] 196 | [26.853492, "o", "Resolving deltas: 46% (238/508) \r"] 197 | [26.853672, "o", "Resolving deltas: 47% (241/508) \r"] 198 | [26.853971, "o", "Resolving deltas: 50% (254/508) \r"] 199 | [26.854354, "o", "Resolving deltas: 52% (267/508) \r"] 200 | [26.854651, "o", "Resolving deltas: 53% (270/508) \r"] 201 | [26.8549, "o", "Resolving deltas: 55% (280/508) \r"] 202 | [26.855288, "o", "Resolving deltas: 59% (302/508) \r"] 203 | [26.855687, "o", "Resolving deltas: 60% (309/508) \rResolving deltas: 61% (312/508) \r"] 204 | [26.855943, "o", "Resolving deltas: 64% (326/508) \r"] 205 | [26.856209, "o", "Resolving deltas: 66% (337/508) \r"] 206 | [26.856326, "o", "Resolving deltas: 67% (341/508) \r"] 207 | [26.856736, "o", "Resolving deltas: 70% (356/508) \r"] 208 | [26.856844, "o", "Resolving deltas: 71% (361/508) \r"] 209 | [26.857298, "o", "Resolving deltas: 75% (385/508) \r"] 210 | [26.857488, "o", "Resolving deltas: 76% (388/508) \r"] 211 | [26.857771, "o", "Resolving deltas: 78% (397/508) \rResolving deltas: 79% (402/508) \r"] 212 | [26.858005, "o", "Resolving deltas: 81% (412/508) \r"] 213 | [26.858288, "o", "Resolving deltas: 82% (418/508) \rResolving deltas: 83% (423/508) \r"] 214 | [26.858675, "o", "Resolving deltas: 86% (441/508) \r"] 215 | [26.858931, "o", "Resolving deltas: 88% (449/508) \rResolving deltas: 89% (453/508) \r"] 216 | [26.859175, "o", "Resolving deltas: 90% (458/508) \r"] 217 | [26.859478, "o", "Resolving deltas: 91% (467/508) \r"] 218 | [26.859726, "o", "Resolving deltas: 92% (471/508) \rResolving deltas: 93% (473/508) \r"] 219 | [26.860079, "o", "Resolving deltas: 96% (488/508) \r"] 220 | [26.860307, "o", "Resolving deltas: 97% (496/508) \r"] 221 | [26.860573, "o", "Resolving deltas: 98% (502/508) \r"] 222 | [26.860905, "o", "Resolving deltas: 100% (508/508) \r"] 223 | [26.861392, "o", "Resolving deltas: 100% (508/508), done.\r\n"] 224 | [26.893439, "o", "----------------------------------------------------------------\r\n"] 225 | [26.893703, "o", "\u001b[0;93mGoWiz:\u001b[0;0m Writing contents to the bench: /Users/praveen/workbench/demo/go/viper.bench\r\n"] 226 | [26.894393, "o", "\u001b[0;93mGoWiz:\u001b[0;0m Done. Run '\u001b[0;93mwb a go/viper\u001b[0;0m' to activate this workbench.\r\n"] 227 | [26.898256, "o", "bash-3.2$ "] 228 | [28.768206, "o", "w"] 229 | [28.918923, "o", "b"] 230 | [29.122079, "o", " "] 231 | [29.243255, "o", "a"] 232 | [29.467892, "o", "\r\n"] 233 | [29.518553, "o", "demo\r\ngo/viper\r\n"] 234 | [29.51961, "o", "bash-3.2$ "] 235 | [30.40593, "o", "w"] 236 | [30.600801, "o", "b"] 237 | [30.765777, "o", " "] 238 | [30.89485, "o", "a"] 239 | [31.003143, "o", " "] 240 | [31.483825, "o", "g"] 241 | [31.549365, "o", "o"] 242 | [32.601303, "o", "/"] 243 | [32.819794, "o", "v"] 244 | [32.919754, "o", "i"] 245 | [32.983656, "o", "p"] 246 | [33.095732, "o", "e"] 247 | [33.163696, "o", "r"] 248 | [33.340883, "o", "\r\n"] 249 | [33.440194, "o", "\u001b[?1034h\u001b[0;91m[ go/viper ]\u001b[0;0m | \u001b[0;93mpraveen\u001b[0;0m at \u001b[0;95mPikesPeak\u001b[0;0m as \u001b[0;94myour-git-config-user-email@goes.here\u001b[0;0m\r\r\n\u001b[0;92m~/workbench/demo/_gotmp/viper/src/github.com/spf13/viper\u001b[0;97m > \u001b[0;0m"] 250 | [35.135665, "o", "e"] 251 | [35.297912, "o", "c"] 252 | [35.345768, "o", "h"] 253 | [35.453805, "o", "o"] 254 | [35.510033, "o", " "] 255 | [35.741602, "o", "$"] 256 | [36.018694, "o", "G"] 257 | [36.117596, "o", "O"] 258 | [36.281584, "o", "P"] 259 | [36.357595, "o", "A"] 260 | [36.465657, "o", "T"] 261 | [36.53365, "o", "H"] 262 | [36.874545, "o", "\r\n"] 263 | [36.874894, "o", "/Users/praveen/workbench/demo/_gotmp/viper\r\n"] 264 | [36.875775, "o", "\u001b[0;91m[ go/viper ]\u001b[0;0m | \u001b[0;93mpraveen\u001b[0;0m at \u001b[0;95mPikesPeak\u001b[0;0m as \u001b[0;94myour-git-config-user-email@goes.here\u001b[0;0m\r\r\n\u001b[0;92m~/workbench/demo/_gotmp/viper/src/github.com/spf13/viper\u001b[0;97m > \u001b[0;0m"] 265 | [37.857774, "o", "i"] 266 | [37.916486, "o", "n"] 267 | [38.001567, "o", "f"] 268 | [38.076386, "o", "o"] 269 | [38.55941, "o", "\r\n"] 270 | [38.55974, "o", "\r\n"] 271 | [38.560247, "o", " \u001b[0;93minfo\u001b[0;0m is a function declared in WORKBENCH_HOME/wb.shelf\r\n You can call it from any directory as long as the workbench is active.\r\n\r\n"] 272 | [38.560553, "o", " Your current workbench: \u001b[0;93mgo/viper\u001b[0;0m\r\n"] 273 | [38.560639, "o", " Your WORKBENCH_HOME is: \u001b[0;93m/Users/praveen/workbench/demo\u001b[0;0m\r\n"] 274 | [38.560709, "o", "\r\n"] 275 | [38.561341, "o", " The following shelves and bench have been sourced:\r\n"] 276 | [38.563859, "o", " └─ \u001b[0;93m/Users/praveen/workbench/demo/wb.shelf\u001b[0;0m\r\n"] 277 | [38.564232, "o", " └─ \u001b[0;93m/Users/praveen/workbench/demo/go/wb.shelf\u001b[0;0m\r\n └─ \u001b[0;93m/Users/praveen/workbench/demo/go/viper.bench\u001b[0;0m\r\n\r\n"] 278 | [38.565469, "o", "\u001b[0;91m[ go/viper ]\u001b[0;0m | \u001b[0;93mpraveen\u001b[0;0m at \u001b[0;95mPikesPeak\u001b[0;0m as \u001b[0;94myour-git-config-user-email@goes.here\u001b[0;0m\r\r\n\u001b[0;92m~/workbench/demo/_gotmp/viper/src/github.com/spf13/viper\u001b[0;97m > \u001b[0;0m"] 279 | [40.278387, "o", "e"] 280 | [40.603316, "o", "x"] 281 | [40.703345, "o", "i"] 282 | [40.826585, "o", "t"] 283 | [41.032359, "o", "\r\n"] 284 | [41.032841, "o", "exit\r\n"] 285 | [41.0411, "o", "bash-3.2$ "] 286 | [42.027321, "o", "\r\n"] 287 | -------------------------------------------------------------------------------- /wb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ----------------------------------------------------------------------------- 4 | # 5 | # Copyright 2019 Praveen G Shirali 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | # ----------------------------------------------------------------------------- 20 | 21 | 22 | log () { echo -e "$@"; } 23 | errlog () { >&2 log "$@"; } 24 | err () { >&2 log "ERROR:" "$@"; } 25 | 26 | readonly _WORKBENCH_VERSION=0.1.0 27 | readonly _PROG="${0##*/}" 28 | 29 | WORKBENCH_BENCH_EXTN="${WORKBENCH_BENCH_EXTN:-bench}" 30 | WORKBENCH_SHELF_FILE="${WORKBENCH_SHELF_FILE:-wb.shelf}" 31 | WORKBENCH_GREPPER="${WORKBENCH_GREPPER:-egrep}" 32 | 33 | WORKBENCH_ACTIVATE_CMD="${WORKBENCH_ACTIVATE_CMD:-/bin/bash --rcfile}" 34 | WORKBENCH_COMMAND_CMD="${WORKBENCH_COMMAND_CMD:-/bin/bash -c}" 35 | 36 | WORKBENCH_ACTIVATE_FUNC="${WORKBENCH_ACTIVATE_FUNC:-workbench_OnActivate}" 37 | WORKBENCH_RUN_FUNC="${WORKBENCH_RUN_FUNC:-workbench_OnRun}" 38 | WORKBENCH_NEW_FUNC="${WORKBENCH_NEW_FUNC:-workbench_OnNew}" 39 | 40 | ERR_FATAL=1 41 | ERR_MISSING=3 42 | ERR_INVALID=4 43 | ERR_DECLINED=5 44 | ERR_EXISTS=6 45 | 46 | 47 | # The `workbench_pre_execute_hook` is a function that gets called just 48 | # before a workbench executes. This allows code in the 49 | # `workbench_pre_execute_hook` to perform any user-defined-checks 50 | # For example: detect changes in WORKBENCH_HOME. 51 | # 52 | # The function in WorkBench is a no-op. The WORKBENCH_RC is expected to 53 | # override this function. WorkBench will exit with the returnCode from 54 | # `workbench_pre_execute_hook` if the returnCode is non-zero. 55 | # 56 | workbench_pre_execute_hook () { :; } 57 | 58 | 59 | _wb_check_realpath () { 60 | realpath --help 2> /dev/null 1> /dev/null 61 | if [[ $? -ne 0 ]] && [[ -z "${WORKBENCH_ALLOW_INSECURE_PATH}" ]]; then 62 | errlog " ┌──────────────────────────────────────────────────────────┐" 63 | errlog " │ │" 64 | errlog " │ *** WARNING *** │" 65 | errlog " │ │" 66 | errlog " │ The CLI utility 'realpath' (part of GNU Coreutils) was │" 67 | errlog " │ not found on this system. Please install it. │" 68 | errlog " │ │" 69 | errlog " │ Run: 'realpath --help' to verify installation │" 70 | errlog " │ │" 71 | errlog " │ For information on security implications, please refer │" 72 | errlog " │ to the 'Using WorkBench > Security' section of │" 73 | errlog " │ WorkBench's documentation at: https://wb.rtfd.io │" 74 | errlog " │ │" 75 | errlog " └──────────────────────────────────────────────────────────┘" 76 | exit 1 77 | fi 78 | } 79 | _wb_realpath () { 80 | realpath --help 2> /dev/null 1> /dev/null 81 | if [[ $? -ne 0 ]]; then 82 | echo "$1" 83 | else 84 | [[ -n "$1" ]] && realpath -m "$1" 85 | fi 86 | } 87 | _wb_check_valid_abspath () { 88 | if [[ "${1:0:1}" != "/" ]]; then 89 | err "Invalid absolute path: '$1'. Does not begin with '/'" 90 | return $ERR_INVALID 91 | fi 92 | } 93 | _wb_print_path_if_child () { 94 | if [[ -z "${WORKBENCH_ALLOW_INSECURE_PATH}" ]]; then 95 | realpath --help 2> /dev/null 1> /dev/null 96 | if [[ $? -eq 0 ]]; then 97 | local ret 98 | local toCheck="$(_wb_realpath $1)" 99 | local prefix="$(_wb_realpath $2)" 100 | _wb_check_valid_abspath "$toCheck" 101 | ret=$? 102 | [[ $ret -ne 0 ]] && return $ret 103 | _wb_check_valid_abspath "$prefix" 104 | ret=$? 105 | [[ $ret -ne 0 ]] && return $ret 106 | local prefixLen=${#prefix} 107 | local toCheckPrefix="${toCheck:0:$prefixLen}" 108 | if [[ "$toCheckPrefix" != "$prefix" ]]; then 109 | err "Item '${toCheck}' lies outside '${prefix}'. Disallowed." 110 | return $ERR_INVALID 111 | fi 112 | echo "${toCheck}" 113 | else 114 | # This is a strict check on each call: `_wb_print_path_if_child` 115 | # A similar check is done using `_wb_check_realpath` on launch 116 | # for convenience. But it could be bypassed. 117 | err "FATAL: 'realpath' not found. Can't compose absolute paths." 118 | err " WORKBENCH_ALLOW_INSECURE_PATH not set either." 119 | exit $ERR_FATAL 120 | fi 121 | else 122 | echo "$1" 123 | fi 124 | } 125 | 126 | _wb_consume_rcfile () { 127 | local wbrc="${HOME}/.workbenchrc" 128 | if [[ -z "${WORKBENCH_RC}" ]] && [[ -n "${HOME}" ]] && [[ -f "${wbrc}" ]] 129 | then 130 | WORKBENCH_RC="${wbrc}" 131 | fi 132 | if [[ -n ${WORKBENCH_RC} ]]; then 133 | if [[ -f "${WORKBENCH_RC}" ]]; then 134 | source "${WORKBENCH_RC}" 135 | else 136 | err "Can't find WORKBENCH_RC: '${WORKBENCH_RC}'" 137 | exit $ERR_MISSING 138 | fi 139 | fi 140 | } 141 | 142 | _wb_init_workbench_home () { 143 | [[ -z "${WORKBENCH_HOME}" ]] && WORKBENCH_HOME="${HOME}/.workbench" 144 | mkdir -p "${WORKBENCH_HOME}" 145 | if [[ ! -d "${WORKBENCH_HOME}" ]]; then 146 | err "WORKBENCH_HOME (${WORKBENCH_HOME}) does not exist. Quitting!" 147 | exit $ERR_MISSING 148 | fi 149 | } 150 | 151 | _wb_confirm () { 152 | if [[ -z "${WORKBENCH_AUTOCONFIRM}" ]]; then 153 | read -e -r -p "$@ [y/N] " ans 154 | if [[ "$ans" =~ ^([yY][eE][sS]|[yY])+$ ]]; then 155 | return 0 156 | else 157 | return $ERR_DECLINED 158 | fi 159 | else 160 | return 0 161 | fi 162 | } 163 | 164 | _wb_list () { 165 | # $1 = shelf|bench 166 | local findByName 167 | local substitute 168 | case "$1" in 169 | shelf) 170 | findByName="${WORKBENCH_SHELF_FILE}" 171 | substitute="${WORKBENCH_SHELF_FILE}" 172 | ;; 173 | bench) 174 | findByName="*.${WORKBENCH_BENCH_EXTN}" 175 | substitute=".${WORKBENCH_BENCH_EXTN}" 176 | ;; 177 | esac 178 | 179 | find "${WORKBENCH_HOME}" -type f -name "${findByName}" | while read i; do 180 | 181 | local relPath="$(echo ${i#${WORKBENCH_HOME}} | sed 's|^/*||g')" 182 | local relName="$(echo ${relPath/%${substitute}/})" 183 | [[ -z "${relName}" ]] && local relName="/" 184 | log "${relName}" 185 | 186 | done | sort 187 | } 188 | 189 | _wb_file_for_shelf () { 190 | local len=${#1} 191 | local stripName 192 | ((len--)) 193 | if [[ "${1:$len:1}" != "/" ]]; then 194 | err "Shelf name must end with a '/'. Got '$1'" 195 | return $ERR_INVALID 196 | fi 197 | stripName="$(echo $1 | sed 's|^/*||g' | sed 's|/*$||g')" 198 | [[ -n "${stripName}" ]] && stripName="${stripName}/" 199 | local shelfFile="${WORKBENCH_HOME}/${stripName}${WORKBENCH_SHELF_FILE}" 200 | _wb_print_path_if_child "${shelfFile}" "${WORKBENCH_HOME}" 201 | return $? 202 | } 203 | 204 | _wb_file_for_bench () { 205 | local len=${#1} 206 | ((len--)) 207 | if [[ "${1:$len:1}" == "/" ]]; then 208 | err "Bench name must NOT end with a '/'. Got '$1'" 209 | return $ERR_INVALID 210 | fi 211 | local stripName="$(echo $1 | sed 's|^/*||g')" 212 | local benchFile="${WORKBENCH_HOME}/${stripName}.${WORKBENCH_BENCH_EXTN}" 213 | _wb_print_path_if_child "${benchFile}" "${WORKBENCH_HOME}" 214 | return $? 215 | } 216 | 217 | _wb_do_vars () { set | ${WORKBENCH_GREPPER} "^WORKBENCH_\w+=" | sort; } 218 | 219 | _wb_help_operate () { 220 | local cmd="$1" 221 | local single="$2" 222 | local plural="$3" 223 | cat < [[arg]..] 228 | 229 | 230 | ══ OPTIONS ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ 231 | 232 | -n, --new Don't error on names which don't exist. 233 | Use this switch to create new files. 234 | -y, --yes Don't prompt for confirmation on 'rm' 235 | Sets WORKBENCH_AUTOCONFIRM to true. 236 | 237 | 238 | ══ DESCRIPTION ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ 239 | 240 | ${_PROG} ${cmd} 241 | Lists ${plural} 242 | 243 | ${_PROG} ${cmd} <${single}Name> 244 | Prints the absolute path to the underlying resource file 245 | for the <${single}Name>. A non-zero exitCode is returned 246 | if the <${single}Name> does not exist. 247 | 248 | ${_PROG} ${cmd} [options] <${single}Name> [[arg]..] 249 | Execute a command (with optional args) on the underlying 250 | resource file for the <${single}Name>. 251 | 252 | Example: 253 | ${_PROG} ${cmd} [options] <${single}Name> ls -l 254 | ${_PROG} ${cmd} [options] <${single}Name> less 255 | ${_PROG} ${cmd} [options] <${single}Name> vim 256 | etc. 257 | 258 | A missing <${single}Name> results in an error. 259 | You can pass '-n' or '--new' to create create the underlying 260 | folder structure and resource file. 261 | 262 | If the first command in [[arg]..] is 'rm', then an interactive 263 | confirmation prompt is presented. This can be skipped by 264 | passing '-y' or '--yes'. 265 | 266 | It can also be skipped automatically by setting: 267 | WORKBENCH_AUTOCONFIRM to any non-empty value. 268 | 269 | EOF 270 | } 271 | _wb_do_operate () { 272 | local resource="$1"; shift # shelf|bench 273 | if [[ "$1" = "-h" ]] || [[ "$1" = "--help" ]]; then 274 | case "$resource" in 275 | shelf) _wb_help_operate "s" "shelf" "shelves" && exit 0;; 276 | bench) _wb_help_operate "b" "bench" "benches" && exit 0;; 277 | esac 278 | fi 279 | local allowNew 280 | local yes 281 | while [[ -n "$1" ]] 282 | do case "$1" in 283 | -n|--new) allowNew="1"; shift;; 284 | -y|--yes) yes="1"; shift;; 285 | *) break;; 286 | esac 287 | done 288 | if [[ -z "$@" ]]; then 289 | _wb_list "${resource}" 290 | else 291 | local resourceName="$1"; shift 292 | local resourceFile 293 | local exitCode 294 | resourceFile="$(_wb_file_for_${resource} ${resourceName})" 295 | exitCode="$?" 296 | [[ "${exitCode}" != "0" ]] && exit "${exitCode}" 297 | if [[ -z "$@" ]]; then 298 | if [[ -f "${resourceFile}" ]]; then 299 | log "${resourceFile}" 300 | exit 0 301 | else 302 | errlog "${resourceFile}" 303 | exit $ERR_MISSING 304 | fi 305 | else 306 | if [[ -n "${allowNew}" ]]; then 307 | mkdir -p "$(dirname ${resourceFile})" 308 | touch "${resourceFile}" 309 | fi 310 | if [[ -f "${resourceFile}" ]]; then 311 | [[ -n "${yes}" ]] && WORKBENCH_AUTOCONFIRM=1 312 | if [[ "$1" == "rm" ]]; then 313 | _wb_confirm "Execute '$@' on '${resourceFile}' ?" 314 | exitCode="$?" 315 | [[ "${exitCode}" != "0" ]] && exit "${exitCode}" 316 | fi 317 | "$@" "${resourceFile}" 318 | else 319 | err "${resource} '${resourceName}' does not exist!" 320 | exit $ERR_MISSING 321 | fi 322 | fi 323 | fi 324 | } 325 | 326 | _wb_compose_initcode () { 327 | local resourceName="$1" 328 | local resourceFile="$2" 329 | local resourceDir="$(dirname ${resourceFile})" 330 | cat < [--dump] [[arg]..] 422 | 423 | a Activate a workbench in a new interactive subshell 424 | Entrypoint: 425 | └─ WORKBENCH_ACTIVATE_FUNC=${WORKBENCH_ACTIVATE_FUNC} 426 | 427 | r Run a command from a workbench in the current shell 428 | Entrypoint: 429 | └─ WORKBENCH_RUN_FUNC=${WORKBENCH_RUN_FUNC} 430 | 431 | n Create a new bench by executing shell code from: 432 | Entrypoint: 433 | └─ WORKBENCH_NEW_FUNC=${WORKBENCH_NEW_FUNC} 434 | 435 | NOTE: You need to supply a new with 436 | the 'n' command. The command displays existing 437 | benches to help you choose your new 438 | by modifying off an existing name. 439 | 440 | Arguments [[arg]..] are passed as-is to the entrypoint function. 441 | 442 | 443 | ══ OPTIONS ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ 444 | 445 | -d, --dump Dump the auto-generated init script on stdout. 446 | This switch does not validate the presence 447 | of on the disk. Use this switch to 448 | review the workbench code. 449 | 450 | EOF 451 | } 452 | _wb_do_execute () { 453 | local cmd="$1"; shift # command: a|r|n 454 | [[ "$1" = "-h" ]] || [[ "$1" = "--help" ]] && _wb_help_execute && exit 0 455 | local dumpCode 456 | case "$1" in 457 | -d|--dump) dumpCode="1"; shift;; 458 | esac 459 | if [[ -z "$@" ]]; then 460 | _wb_list "bench" 461 | else 462 | local resourceName="$1"; shift 463 | local resourceFile 464 | local exitCode 465 | resourceFile="$(_wb_file_for_bench ${resourceName})" 466 | exitCode="$?" 467 | [[ "${exitCode}" != "0" ]] && exit "${exitCode}" 468 | 469 | if [[ "${dumpCode}" != "1" ]]; then 470 | if [[ "${cmd}" == "n" ]]; then 471 | if [[ -s "${resourceFile}" ]]; then 472 | err "Bench '${resourceName}' exists: '${resourceFile}'" 473 | err "Cannot create a new workbench." 474 | exit $ERR_EXISTS 475 | fi 476 | elif [[ ! -f "${resourceFile}" ]]; then 477 | err "Bench '${resourceName}' does not exist. Quitting!" 478 | exit $ERR_MISSING 479 | fi 480 | fi 481 | 482 | local executor 483 | case "${cmd}" in 484 | a) executor="${WORKBENCH_ACTIVATE_CMD}";; 485 | r) executor="${WORKBENCH_COMMAND_CMD}";; 486 | n) executor="${WORKBENCH_COMMAND_CMD}";; 487 | esac 488 | 489 | workbench_pre_execute_hook 490 | local exitCode=$? 491 | [[ $exitCode -ne 0 ]] && exit $exitCode 492 | 493 | resourceName="$(echo ${resourceName} | sed 's|^/*||g')" 494 | if [[ ${dumpCode} == "1" ]]; then 495 | _wb_compose_code "${resourceName}" "${resourceFile}" \ 496 | "${cmd}" "$@" 497 | else 498 | local tmpFile 499 | tmpFile=`mktemp` 500 | if [[ "$?" != "0" ]]; then 501 | err "Failed to create temp file using 'mktemp'. Quitting!" 502 | exit $ERR_FATAL 503 | fi 504 | chmod +x "${tmpFile}" 505 | if [[ "$?" != "0" ]]; then 506 | err "Failed to set exec permission on workbench. Quitting!" 507 | exit $ERR_FATAL 508 | fi 509 | _wb_compose_code "${resourceName}" "${resourceFile}" \ 510 | "${cmd}" "$@" > "${tmpFile}" 511 | ${executor} "${tmpFile}" 512 | rm -f "${tmpFile}" 513 | fi 514 | fi 515 | } 516 | 517 | _wb_show_help () { 518 | cat <]...] ${_PROG} [args] 527 | 528 | 529 | ══ ENV CONFIGURATION ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ 530 | 531 | WORKBENCH_RC= 532 | Source a supplied rcfile before execution. If unspecified, 533 | then the default location '\$HOME/.workbenchrc' is tried. 534 | 535 | 536 | ══ COMMANDS (Use '-h' for additional help/options [+]) ─ ─ ─ ─ ─ ─ ─ ─ 537 | 538 | 539 | -V Show version and quit 540 | -E Show WORKBENCH environment variables and quit 541 | 542 | s List shelves. Operate on a shelf file. [+] 543 | b List benches. Operate on a bench file. [+] 544 | 545 | a List benches. Activate a workbench. [+] 546 | r List benches. Run a command from a workbench. [+] 547 | n List benches. Create a new bench. [+] 548 | 549 | 550 | ══ DESCRIPTION ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ 551 | 552 | WorkBench is a hierarchical environment manager for the shell. 553 | You can use it to build project-specific virtualenvs, each having 554 | its own set of env variables, aliases and functions. 555 | 556 | WorkBench builds an environment (called 'workbench') by overlaying 557 | shell code from files residing at different directory depths (called 558 | 'shelves') within a WORKBENCH_HOME directory, followed by a file 559 | which overrides all shelves (called 'bench'). 560 | 561 | Refer to the documentation for more details: 562 | URL: https://wb.readthedocs.io 563 | 564 | 565 | ══ LICENSE ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ 566 | 567 | Apache 2.0 -- https://www.apache.org/licenses/LICENSE-2.0 568 | 569 | Copyright 2018-19 Praveen G Shirali 570 | https://github.com/pshirali/workbench 571 | 572 | 573 | EOF 574 | } 575 | main () { 576 | _wb_consume_rcfile 577 | _wb_check_realpath 578 | WORKBENCH_HOME=$(_wb_realpath "${WORKBENCH_HOME}") 579 | WORKBENCH_HOME="$(echo ${WORKBENCH_HOME} | sed 's|/*$||g')" 580 | _wb_init_workbench_home 581 | if [[ -z "$1" ]] || [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then 582 | _wb_show_help 583 | exit 0 584 | fi 585 | 586 | case "$1" in 587 | -V) log "${_WORKBENCH_VERSION}";; 588 | -E) _wb_do_vars;; 589 | 590 | s) shift; _wb_do_operate "shelf" "$@";; 591 | b) shift; _wb_do_operate "bench" "$@";; 592 | 593 | a) shift; _wb_do_execute "a" "$@";; 594 | r) shift; _wb_do_execute "r" "$@";; 595 | n) shift; _wb_do_execute "n" "$@";; 596 | 597 | *) err "Unknown command '$1'. Run '${_PROG} -h' for help." && exit 1;; 598 | esac 599 | } 600 | 601 | 602 | main "$@" 603 | -------------------------------------------------------------------------------- /tests/test_wb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import subprocess 4 | import unittest 5 | import shutil 6 | 7 | from os import makedirs 8 | from os.path import abspath, dirname, join, basename, exists, isdir 9 | 10 | WB_DIR=abspath(join(dirname(__file__), "..")) 11 | TESTDATA=join(WB_DIR, "tests", "testdata") 12 | WB=join(WB_DIR, "wb") 13 | 14 | ERR_FATAL=1 15 | ERR_MISSING=3 16 | ERR_INVALID=4 17 | ERR_DECLINED=5 18 | ERR_EXISTS=6 19 | 20 | EXECUTOR="bash" 21 | EXECUTOR_VERSION_FLAG=" --version" 22 | 23 | # 24 | # Unset all env vars which might affect wb's functionality. Values for 25 | # these might get picked up from the env in which the tests are run. 26 | # 27 | CMD_PREFIX=( 28 | "HOME={td}/emptyrc " 29 | "WORKBENCH_RC= " 30 | "WORKBENCH_HOME= " 31 | "WORKBENCH_BENCH_EXTN= " 32 | "WORKBENCH_SHELF_FILE= " 33 | "WORKBENCH_GREPPER= " 34 | "WORKBENCH_ACTIVATE_CMD= " 35 | "WORKBENCH_COMMAND_CMD= " 36 | "WORKBENCH_ACTIVATE_FUNC= " 37 | "WORKBENCH_RUN_FUNC= " 38 | "WORKBENCH_NEW_FUNC= " 39 | "WORKBENCH_AUTOCONFIRM= " 40 | "WORKBENCH_ALLOW_INSECURE_PATH= " 41 | ) 42 | 43 | 44 | def run(cmd, **kwargs): 45 | if not isinstance(cmd, str): 46 | raise ValueError("Expected command to be a string") 47 | replace = kwargs.pop("replace", {}) 48 | wb_data = dict(td=TESTDATA, wb="{} {}".format(EXECUTOR, WB)) 49 | wb_data.update(replace) 50 | cmd = CMD_PREFIX.format(**wb_data) + cmd.format(**wb_data) 51 | run_args = dict( 52 | shell=True, cwd=WB_DIR, timeout=5, 53 | stdout=subprocess.PIPE, 54 | stderr=subprocess.PIPE, 55 | ) 56 | run_args.update(kwargs) 57 | cp = subprocess.run(cmd, **run_args) 58 | if not isinstance(cp.stdout, str): 59 | cp.stdout = cp.stdout.decode('utf-8') 60 | if not isinstance(cp.stderr, str): 61 | cp.stderr = cp.stderr.decode('utf-8') 62 | return cp 63 | 64 | 65 | def get_shelf_filename(home, shelf_name): 66 | # https://docs.python.org/3.7/library/os.path.html#os.path.join 67 | # If a component is an absolute path, all previous components are thrown 68 | # away and joining continues from the absolute path component. 69 | if shelf_name == "/": 70 | shelf_name = "" 71 | return join(home, shelf_name, "wb.shelf") 72 | 73 | 74 | def get_bench_filename(home, bench_name): 75 | return join(home, "{}{}".format(bench_name, ".bench")) 76 | 77 | 78 | GET_FILENAME = { 79 | "s": get_shelf_filename, 80 | "b": get_bench_filename, 81 | } 82 | 83 | 84 | def show_bash(): 85 | print("----- [ bash ] -----") 86 | print(run("{}{}".format(EXECUTOR, EXECUTOR_VERSION_FLAG)).stdout) 87 | print("-----") 88 | 89 | def show_python(): 90 | print("----- [ python ] -----") 91 | import sys 92 | print(sys.version) 93 | print("-----") 94 | 95 | def check_realpath(): 96 | o = run("realpath --help") 97 | if o.returncode != 0: 98 | print("'realpath' from GNU Coreutils is missing. Can't run tests.") 99 | exit(1) 100 | 101 | show_python() 102 | show_bash() 103 | check_realpath() 104 | 105 | 106 | # ----------------------------------------------------------------------------- 107 | # 108 | # TESTING NOTES: 109 | # 110 | # Assert in the following order: 111 | # Success Cases: (1) stderr=="", (2) returncode==0, success condition 112 | # Failure Cases: (1) stdout=="" (if req) (2) returncode !=0 113 | # stderr output is not guaranteed to be stable and is 114 | # not part of the CLI's guarantee. Hence it is not 115 | # tested for string formatting. 116 | # 117 | # ----------------------------------------------------------------------------- 118 | # 119 | # Tests start here 120 | # 121 | # ----------------------------------------------------------------------------- 122 | 123 | 124 | class TestWbRCFile(unittest.TestCase): 125 | 126 | def test_consume_from_workbenchrc_default(self): 127 | """ 128 | If WORKBENCH_RC is not defined, and a file $HOME/.workbenchrc 129 | exists, then source it automatically 130 | """ 131 | o = run("HOME={td}/rctest {wb} -E") 132 | self.assertEqual(o.stderr, "") 133 | self.assertEqual(o.returncode, 0) 134 | self.assertIn("WORKBENCH_TEST=___default___", o.stdout.split('\n')) 135 | 136 | def test_consume_from_user_supplied_rc(self): 137 | """ 138 | If a WORKBENCH_RC is defined, then source only supplied file. 139 | """ 140 | o = run("WORKBENCH_RC={td}/rctest/custom.rc {wb} -E") 141 | self.assertEqual(o.stderr, "") 142 | self.assertEqual(o.returncode, 0) 143 | self.assertIn("WORKBENCH_TEST=___custom___", o.stdout.split('\n')) 144 | 145 | def test_workbench_on_home_folder_with_no_workbenchrc(self): 146 | """ 147 | WorkBench must not error if $HOME does not have a .workbenchrc 148 | """ 149 | o = run("HOME={td}/rctest/emptyfolder {wb} -E") 150 | self.assertEqual(o.stderr, "") 151 | self.assertEqual(o.returncode, 0) 152 | self.assertNotIn("WORKBENCH_TEST=", o.stdout.split('\n')) 153 | 154 | def test_non_existent_workbench_rc_file_raises_error(self): 155 | """ 156 | WorkBench must error if WORKBENCH_RC points to a file that 157 | doesn't exist. 158 | """ 159 | filename = "this-file-does-not-exist.rc" 160 | o = run("WORKBENCH_RC={filename} {wb} -E", 161 | replace=dict(filename=filename)) 162 | self.assertEqual(o.stdout, "") 163 | # errmsg = "ERROR: Can't find WORKBENCH_RC: '{}'".format(filename) 164 | # self.assertEqual(o.stderr.strip(), errmsg) 165 | self.assertEqual(o.returncode, ERR_MISSING) 166 | 167 | def test_list_workbench_env_vars(self): 168 | """ 169 | wb -E must only list env var entries ^WORKBENCH_* 170 | """ 171 | o = run("HOME={td}/rctest/emptyfolder {wb} -E") 172 | self.assertEqual(o.stderr, "") 173 | self.assertEqual(o.returncode, 0) 174 | for entry in o.stdout.strip().split('\n'): 175 | self.assertTrue(entry.startswith("WORKBENCH_")) 176 | 177 | 178 | class TestWbShelfAndBenchOps(unittest.TestCase): 179 | 180 | # LIST SHELVES AND BENCHES 181 | 182 | def test_list_simple_benches(self): 183 | """ 184 | 'wb b' lists all benches in WORKBENCH_HOME 185 | """ 186 | o = run("WORKBENCH_HOME={td}/wbhome/simple {wb} b") 187 | self.assertEqual(o.stderr, "") 188 | self.assertEqual(o.returncode, 0) 189 | benches = set(o.stdout.strip().split('\n')) 190 | self.assertEqual(benches, set([ 191 | "outer/inner/simple1", 192 | "outer/inner/simple2" 193 | ])) 194 | 195 | def test_list_simple_shelves(self): 196 | """ 197 | 'wb s' lists all shelves in WORKBENCH_HOME 198 | """ 199 | o = run("WORKBENCH_HOME={td}/wbhome/simple {wb} s") 200 | self.assertEqual(o.stderr, "") 201 | self.assertEqual(o.returncode, 0) 202 | shelves = set(o.stdout.strip().split('\n')) 203 | self.assertEqual(shelves, set([ 204 | "/", 205 | "outer/", 206 | "outer/inner/" 207 | ])) 208 | 209 | # SHOW PATH TO SHELF AND BENCH FILES 210 | 211 | def _test_print_path_to_file(self, wb_cmd, name, missing=False): 212 | """ 213 | wb s shelfName 214 | wb b benchName 215 | Prints absolute path to the underlying shelf/bench file 216 | """ 217 | root_folder = join(TESTDATA, "wbhome", "simple") 218 | filename = GET_FILENAME[wb_cmd](root_folder, name) 219 | 220 | o = run("WORKBENCH_HOME={td}/wbhome/simple {wb} {wb_cmd} {name}", 221 | replace=dict(wb_cmd=wb_cmd, name=name)) 222 | if missing is False: 223 | self.assertEqual(o.stdout.strip(), filename) 224 | self.assertEqual(o.stderr.strip(), "") 225 | self.assertEqual(o.returncode, 0) 226 | else: 227 | self.assertEqual(o.stdout.strip(), "") 228 | self.assertEqual(o.stderr.strip(), filename) 229 | self.assertEqual(o.returncode, ERR_MISSING) 230 | 231 | def test_show_simple_bench_file(self): 232 | self._test_print_path_to_file("b", "outer/inner/simple1") 233 | self._test_print_path_to_file("b", "outer/inner/simple2") 234 | self._test_print_path_to_file("b", "valid/but/missing/bench", 235 | missing=True) 236 | 237 | def test_show_simple_shelf_path(self): 238 | self._test_print_path_to_file("s", "/") 239 | self._test_print_path_to_file("s", "outer/") 240 | self._test_print_path_to_file("s", "outer/inner/") 241 | self._test_print_path_to_file("s", "valid/but/missing/shelf/", 242 | missing=True) 243 | 244 | # RUN COMMAND ON A SHELF AND BENCH FILE 245 | 246 | def _test_command_on_file(self, wb_cmd, name): 247 | root_folder = join(TESTDATA, "wbhome", "simple") 248 | filename = GET_FILENAME[wb_cmd](root_folder, name) 249 | 250 | cmd = "head -n 1" 251 | with open(filename) as f: 252 | lines = f.read().split('\n') 253 | self.assertTrue(len(lines) > 0) 254 | self.assertTrue(lines[0] != "") 255 | expected = lines[0] 256 | 257 | o = run("WORKBENCH_HOME={td}/wbhome/simple {wb} {wb_cmd} {name} {cmd}", 258 | replace=dict(wb_cmd=wb_cmd, name=name, cmd=cmd)) 259 | self.assertEqual(o.stderr, "") 260 | self.assertEqual(o.returncode, 0) 261 | self.assertEqual(expected, o.stdout.strip()) 262 | 263 | def test_run_command_on_bench_file(self): 264 | self._test_command_on_file("b", "outer/inner/simple1") 265 | 266 | def test_run_command_on_shelf_file(self): 267 | self._test_command_on_file("s", "outer/inner/") 268 | 269 | def test_list_errors_on_bad_inputs(self): 270 | bad_inputs = [ 271 | ("s", "invalid-shelf-missing-tailing-slash", ERR_INVALID), 272 | ("b", "invalid-bench-having-tailing-slash/", ERR_INVALID), 273 | ] 274 | for (wb_cmd, name, errCode) in bad_inputs: 275 | o = run("WORKBENCH_HOME={td}/wbhome/simple {wb} {wb_cmd} {name}", 276 | replace=dict(wb_cmd=wb_cmd, name=name)) 277 | self.assertEqual(o.returncode, errCode) 278 | 279 | # CONFIRMATION 280 | 281 | def _cleanup_before_test(self, rm_test_dir): 282 | if exists(rm_test_dir) and isdir(rm_test_dir): 283 | shutil.rmtree(rm_test_dir) 284 | 285 | def _cleanup_after_test(self, rm_test_dir): 286 | shutil.rmtree(rm_test_dir) 287 | 288 | def _create_new_through_testcode(self, filename): 289 | """ 290 | wb s|b has a switch -n|--new which creates new shelf/bench if 291 | one doesn't exist. When -n is not passed, we need our testcode 292 | to create these dirs & files for tests to be performed on them 293 | """ 294 | makedirs(dirname(filename), exist_ok=True) 295 | with open(filename, "w") as f: 296 | f.write("some-dummy-data") 297 | 298 | def _rm_kwargs(self, prefix, wb_cmd, name, stdin=""): 299 | kw = dict( 300 | replace=dict(prefix=prefix, wb_cmd=wb_cmd, name=name) 301 | ) 302 | if stdin: 303 | kw["encoding"] = "ascii" 304 | kw["input"] = "{}\n".format(stdin) 305 | return kw 306 | 307 | def _test_rm_confirmation(self, prefix, wb_cmd, name, new_flag=False): 308 | """ 309 | Test wb s|b rm 310 | When the command is 'rm', an interactive prompt is presented. 311 | Removal occurs only when confirmed. 312 | """ 313 | rm_test_dir = join(TESTDATA, "wbhome/rm_test") 314 | try: 315 | self._cleanup_before_test(rm_test_dir) 316 | filename = GET_FILENAME[wb_cmd[0]](rm_test_dir, name) 317 | 318 | if new_flag is False: 319 | self._create_new_through_testcode(filename) 320 | 321 | # --- Test with "n" (No) on prompt ---------------------------- 322 | # Expect ERR_DECLINED. Expect file to remain 323 | kw = self._rm_kwargs(prefix, wb_cmd, name, stdin="n") 324 | 325 | o = run("WORKBENCH_HOME={td}/wbhome/rm_test " 326 | "{prefix} {wb} {wb_cmd} {name} rm", 327 | **kw) 328 | self.assertEqual(o.stdout, "") 329 | self.assertEqual(o.stderr, "") 330 | self.assertEqual(o.returncode, ERR_DECLINED) 331 | self.assertTrue(exists(filename)) 332 | 333 | # --- Test with "y" (Yes) on prompt --------------------------- 334 | # Expect succcess. Expect file to get deleted 335 | kw = self._rm_kwargs(prefix, wb_cmd, name, stdin="y") 336 | o = run("WORKBENCH_HOME={td}/wbhome/rm_test " 337 | "{prefix} {wb} {wb_cmd} {name} rm", 338 | **kw) 339 | 340 | self.assertEqual(o.stdout, "") 341 | self.assertEqual(o.stderr, "") 342 | self.assertEqual(o.returncode, 0) 343 | self.assertTrue(not exists(filename)) 344 | 345 | finally: 346 | self._cleanup_after_test(rm_test_dir) 347 | 348 | def _test_rm_auto_confirmation(self, prefix, wb_cmd, name, new_flag=False): 349 | """ 350 | wb s|b -y rm 351 | OR 352 | WORKBENCH_AUTOCONFIRM=1 wb s|b rm 353 | 354 | Will assume Yes always and will not provide an interactive prompt 355 | """ 356 | rm_test_dir = join(TESTDATA, "wbhome/rm_test") 357 | try: 358 | self._cleanup_before_test(rm_test_dir) 359 | filename = GET_FILENAME[wb_cmd[0]](rm_test_dir, name) 360 | 361 | if new_flag is False: 362 | self._create_new_through_testcode(filename) 363 | 364 | kw = self._rm_kwargs(prefix, wb_cmd, name) 365 | o = run("WORKBENCH_HOME={td}/wbhome/rm_test " 366 | "{prefix} {wb} {wb_cmd} {name} rm", 367 | **kw) 368 | 369 | self.assertEqual(o.stderr, "") 370 | self.assertEqual(o.returncode, 0) 371 | self.assertTrue(not exists(filename)) 372 | 373 | finally: 374 | self._cleanup_after_test(rm_test_dir) 375 | 376 | def test_confirmation_prompt_on_rm(self): 377 | inputs = [ 378 | ("WORKBENCH_AUTOCONFIRM=", "s", "remove_me/"), 379 | ("WORKBENCH_AUTOCONFIRM=", "b", "remove_me/bench"), 380 | # Get WorkBench to create new resources if they don't exist 381 | ("WORKBENCH_AUTOCONFIRM=", "s -n", "remove_me/"), 382 | ("WORKBENCH_AUTOCONFIRM=", "b -n", "remove_me/bench"), 383 | ] 384 | for (prefix, wb_cmd, name) in inputs: 385 | new_flag = True if "-n" in wb_cmd else False 386 | self._test_rm_confirmation(prefix, wb_cmd, name, new_flag=new_flag) 387 | 388 | def test_autoconfirmation_on_rm(self): 389 | inputs = [ 390 | ("WORKBENCH_AUTOCONFIRM=", "s -y", "remove_me/"), 391 | ("WORKBENCH_AUTOCONFIRM=", "b -y", "remove_me/bench"), 392 | 393 | # set confirmation via WORKBENCH_AUTOCONFIRM=1 394 | ("WORKBENCH_AUTOCONFIRM=1", "s", "remove_me/"), 395 | ("WORKBENCH_AUTOCONFIRM=1", "b", "remove_me/bench"), 396 | 397 | # Get WorkBench to create new resources if they don't exist 398 | ("WORKBENCH_AUTOCONFIRM=", "s -n -y", "remove_me/"), 399 | ("WORKBENCH_AUTOCONFIRM=", "b -n -y", "remove_me/bench"), 400 | ] 401 | for (prefix, wb_cmd, name) in inputs: 402 | new_flag = True if "-n" in wb_cmd else False 403 | self._test_rm_auto_confirmation(prefix, wb_cmd, name, 404 | new_flag=new_flag) 405 | 406 | 407 | class TestWbExecute(unittest.TestCase): 408 | 409 | def _test_list_benches(self, c): 410 | o = run("WORKBENCH_ENV_NAME= " 411 | "WORKBENCH_HOME={td}/wbhome/simple {wb} {c}", 412 | replace=dict(c=c)) 413 | self.assertEqual(o.stderr, "") 414 | self.assertEqual(o.returncode, 0) 415 | benches = set(o.stdout.strip().split('\n')) 416 | self.assertEqual(benches, set([ 417 | "outer/inner/simple1", 418 | "outer/inner/simple2" 419 | ])) 420 | 421 | def test_wb_execute_commands_without_args_list_workbenches(self): 422 | """ 423 | wb a|r|n 424 | Lists benches 425 | """ 426 | for c in ["a", "r", "n"]: 427 | self._test_list_benches(c) 428 | 429 | def test_cant_create_new_bench_when_one_already_exists(self): 430 | """ 431 | wb n when already exists raises error 432 | """ 433 | o = run("WORKBENCH_ENV_NAME= WORKBENCH_HOME={td}/wbhome/simple " 434 | "{wb} n outer/inner/simple1") 435 | self.assertEqual(o.stdout, "") 436 | self.assertEqual(o.returncode, ERR_EXISTS) 437 | 438 | def _test_dump_verify_magic_string(self): 439 | """ 440 | wb a|r|n --dump 441 | Expect 'Auto-generated by WorkBench' in output 442 | """ 443 | o = run("WORKBENCH_ENV_NAME= WORKBENCH_HOME={td}/wbhome/simple " 444 | "{wb} {c} outer/inner/simple1", replace=dict(c=c)) 445 | self.assertEqual(o.stderr, "") 446 | self.assertEqual(o.returncode, 0) 447 | lines = o.stdout.strip().split('\n') 448 | for line in lines: 449 | if "Auto-generated by WorkBench" in line: 450 | break 451 | self.fail("\"Auto-generated by WorkBench\" not found in dump") 452 | 453 | def _test_dump_works_on_all_execute_commands(self): 454 | """ 455 | wb a|c|n --dump 456 | """ 457 | for c in ["a", "r", "n"]: 458 | self._test_dump_verify_magic_string(c) 459 | 460 | def test_dump_contents(self): 461 | mandatory = [ 462 | "workbench_OnNew () {", 463 | "workbench_OnActivate () {", 464 | "workbench_OnRun () {", 465 | "export WORKBENCH_SHELF_FILE=", 466 | "export WORKBENCH_BENCH_EXTN=", 467 | "export WORKBENCH_ENV_NAME=", 468 | "export ORIG_PS1=", 469 | "export PS1=", 470 | "export WORKBENCH_CHAIN=", 471 | "export WORKBENCH_ACTIVATE_FUNC=", 472 | "export WORKBENCH_RUN_FUNC=", 473 | "export WORKBENCH_NEW_FUNC=", 474 | "export WORKBENCH_EXEC_MODE=", 475 | ] 476 | o = run("WORKBENCH_ENV_NAME= WORKBENCH_HOME={td}/wbhome/simple " 477 | "{wb} r --dump outer/inner/simple1") 478 | self.assertEqual(o.stderr, "") 479 | self.assertEqual(o.returncode, 0) 480 | 481 | lines = o.stdout.strip().split('\n') 482 | for m in mandatory: 483 | found = False 484 | for line in lines: 485 | if line.startswith(m): 486 | found = True 487 | break 488 | if not found: 489 | self.fail("Did not find '{}' in dump".format(m)) 490 | 491 | def test_workbench_chain_contents(self): 492 | o = run("WORKBENCH_ENV_NAME= WORKBENCH_HOME={td}/wbhome/simple " 493 | "WORKBENCH_RUN_FUNC=echo " 494 | "{wb} r outer/inner/simple1 \$WORKBENCH_CHAIN") 495 | self.assertEqual(o.stderr, "") 496 | self.assertEqual(o.returncode, 0) 497 | hm = join(TESTDATA, "wbhome", "simple") 498 | stdout = o.stdout.strip().split()[-1] # tail -n 1 499 | pt = [p[len(hm):] for p in stdout.split(":") if p.startswith(hm)] 500 | self.assertEqual(pt, [ 501 | "/wb.shelf", 502 | "/outer/wb.shelf", 503 | "/outer/inner/wb.shelf", 504 | "/outer/inner/simple1.bench" 505 | ]) 506 | 507 | def test_workbench_chain_contents_for_different_cases(self): 508 | wb_data = [ 509 | # bench, expected output 510 | ( 511 | "noshelf/noshelf", 512 | ["/noshelf/noshelf.bench"] 513 | ), 514 | ( 515 | "shelfbench/bench", 516 | ["/shelfbench/wb.shelf", "/shelfbench/bench.bench"]), 517 | ( 518 | "skipshelf/skip/bench", 519 | ["/skipshelf/wb.shelf", "/skipshelf/skip/bench.bench"] 520 | ), 521 | ] 522 | for (bench, source_list) in wb_data: 523 | o = run("WORKBENCH_ENV_NAME= WORKBENCH_HOME={td}/wbhome/chain " 524 | "WORKBENCH_RUN_FUNC=echo " 525 | "{wb} r {bench} \$WORKBENCH_CHAIN", 526 | replace=dict(bench=bench)) 527 | self.assertEqual(o.stderr, "") 528 | self.assertEqual(o.returncode, 0) 529 | 530 | hm = join(TESTDATA, "wbhome", "chain") 531 | stdout = o.stdout.strip().split()[-1] # tail -n 1 532 | pt = [p[len(hm):] for p in stdout.split(":") if p.startswith(hm)] 533 | self.assertEqual(pt, source_list) 534 | 535 | def test_ISSUE_11_workbench_shelf_sourced_twice(self): 536 | """ 537 | https://github.com/pshirali/workbench/issues/11 538 | Dont source wb.shelf twice if bench is in WORKBENCH_HOME 539 | """ 540 | o = run("WORKBENCH_ENV_NAME= " 541 | "WORKBENCH_HOME={td}/wbhome/chain/shelfbench " 542 | "WORKBENCH_RUN_FUNC=echo " 543 | "{wb} r bench \$WORKBENCH_CHAIN") 544 | self.assertEqual(o.stderr, "") 545 | self.assertEqual(o.returncode, 0) 546 | hm = join(TESTDATA, "wbhome", "chain", "shelfbench") 547 | stdout = o.stdout.strip().split()[-1] # tail -n 1 548 | pt = [p[len(hm):] for p in stdout.split(":") if p.startswith(hm)] 549 | self.assertEqual(pt, ["/wb.shelf", "/bench.bench"]) 550 | 551 | def test_workbench_chain_contents_on_new_strip_colon(self): 552 | """ 553 | 'wb n ' will allow workbench_OnNew from a shelf 554 | to execute. This is a case where the benchFile for 555 | doesn't exist yet. In this scenario WORKBENCH_CHAIN must not have 556 | a trailing colon. 557 | """ 558 | o = run("WORKBENCH_ENV_NAME= " 559 | "WORKBENCH_HOME={td}/wbhome/chain/skipshelf " 560 | "WORKBENCH_NEW_FUNC=echo " 561 | "{wb} n does-not-exist \$WORKBENCH_CHAIN") 562 | self.assertEqual(o.stderr, "") 563 | self.assertEqual(o.returncode, 0) 564 | hm = join(TESTDATA, "wbhome", "chain", "skipshelf") 565 | stdout = o.stdout.strip().split()[-1] # tail -n 1 566 | 567 | self.assertNotEqual(stdout[-1], ":") 568 | pt = [p[len(hm):] for p in stdout.split(":") if p.startswith(hm)] 569 | self.assertEqual(pt, ["/wb.shelf"]) 570 | 571 | def test_invoke_run_on_bench(self): 572 | """ 573 | wb r 574 | """ 575 | o = run("WORKBENCH_ENV_NAME= WORKBENCH_HOME={td}/wbhome/simple " 576 | "{wb} r outer/inner/simple1") 577 | self.assertEqual(o.stderr, "") 578 | self.assertEqual(o.returncode, 0) 579 | stdout = [s.strip() for s in o.stdout.split('\n') if s.strip()] 580 | self.assertEqual(stdout, 581 | ["ROOT", "OUTER", "INNER", "SIMPLE1", "Default-Run"] 582 | ) 583 | 584 | def test_invoke_activate_on_bench(self): 585 | """ 586 | wb a 587 | Activate -> exit 588 | 589 | NOTE: No stdout here. 590 | """ 591 | o = run("WORKBENCH_ENV_NAME= WORKBENCH_HOME={td}/wbhome/simple " 592 | "{wb} a outer/inner/simple1", 593 | input="exit\n", encoding="ascii") 594 | self.assertEqual(o.stderr, "") 595 | self.assertEqual(o.returncode, 0) 596 | 597 | def test_workbench_run_override_and_deactivate_on_exit(self): 598 | """ 599 | Test with wb a 600 | 'exit' has been overridden to execute additional code before 601 | builtin exit is triggered 602 | 603 | Specifically designed for contents of outer/inner/simple1 604 | Test this with `wb r` in non-interative manner to verify stdout 605 | """ 606 | o = run("WORKBENCH_ENV_NAME= " 607 | "WORKBENCH_RUN_FUNC=exit " 608 | "WORKBENCH_HOME={td}/wbhome/simple " 609 | "{wb} r outer/inner/simple1 1 2 3") 610 | self.assertEqual(o.stderr, "") 611 | self.assertEqual(o.returncode, 0) 612 | stdout = [s.strip() for s in o.stdout.split('\n') if s.strip()] 613 | self.assertEqual( 614 | stdout, 615 | ["ROOT", "OUTER", "INNER", "SIMPLE1", "Default-Deactivated"] 616 | ) 617 | 618 | def test_workbench_new_get_invoked(self): 619 | """ 620 | wb n 621 | Creates a new bench and calls 'workbench_OnNew' as the entrypoint 622 | 623 | 'workbench_OnNew' is written to 'wbhome/rm_test_new/wb.bench' 624 | The bench that is executed is 'wbhome/rm_test_new/nested/new_one.bench' 625 | The 'workbench_OnNew' from the last sourced file (rm_test_new) will 626 | take effect. 627 | """ 628 | rm_test_dir = join(TESTDATA, "wbhome/rm_test_new") 629 | try: 630 | if exists(rm_test_dir) and isdir(rm_test_dir): 631 | shutil.rmtree(rm_test_dir) 632 | shelf_file = get_shelf_filename(rm_test_dir, "") 633 | makedirs(rm_test_dir, exist_ok=True) 634 | with open(shelf_file, "w") as f: 635 | f.write("workbench_OnNew () { echo \"Default-New\" $@; }\n") 636 | 637 | o = run("WORKBENCH_ENV_NAME= " 638 | "WORKBENCH_HOME={td}/wbhome/rm_test_new " 639 | "{wb} n nested/new_one 1 2 3") 640 | self.assertEqual(o.stderr, "") 641 | self.assertEqual(o.returncode, 0) 642 | self.assertEqual(o.stdout.strip(), "Default-New 1 2 3") 643 | finally: 644 | shutil.rmtree(rm_test_dir) 645 | 646 | def test_realpath_prevents_sourcing_outside_of_workbench_home(self): 647 | """ 648 | With 'realpath' is found on the system, paths which try to source 649 | code from outside WORKBENCH_HOME using ../ will result in an error 650 | """ 651 | o = run("WORKBENCH_ENV_NAME= WORKBENCH_HOME={td}/wbhome/simple " 652 | "{wb} r ../outer/inner/simple1") 653 | self.assertEqual(o.stdout, "") 654 | self.assertEqual(o.returncode, ERR_INVALID) 655 | 656 | def test_disable_realpath_using_env_var(self): 657 | """ 658 | 'realpath' can be disabled by setting WORKBENCH_ALLOW_INSECURE_PATH 659 | (Not recommended for general use) 660 | """ 661 | o = run("WORKBENCH_ENV_NAME= WORKBENCH_HOME={td}/wbhome/simple/outer " 662 | "WORKBENCH_ALLOW_INSECURE_PATH=1 " 663 | "{wb} r ../outer/inner/simple1") 664 | self.assertEqual(o.stderr, "") 665 | self.assertEqual(o.returncode, 0) 666 | stdout = [s.strip() for s in o.stdout.split('\n') if s.strip()] 667 | 668 | # here "ROOT" comes from wbhome/simple/wb.shelf. It lies one level 669 | # below WORKBENCH_HOME; yet, has been sourced 670 | self.assertEqual(stdout, 671 | ["OUTER", "ROOT", "OUTER", "INNER", "SIMPLE1", "Default-Run"] 672 | ) 673 | 674 | def test_pre_execute_hook_execution(self): 675 | o = run("WORKBENCH_RC={td}/rctest/pre_exec_hook.rc " 676 | "WORKBENCH_HOME={td}/wbhome/chain/noshelf " 677 | "{wb} r noshelf") 678 | self.assertEqual(o.stderr, "") 679 | self.assertEqual(o.returncode, 22) 680 | self.assertEqual(o.stdout.strip(), "pre_execute_hook") 681 | 682 | def test_workbench_exec_mode(self): 683 | """ 684 | WORKBENCH_EXEC_MODE takes the value "a" | "r" | "n" 685 | depending on the command with which the workbench was 686 | invoked. 687 | """ 688 | for cmd in ["a", "r", "n"]: 689 | o = run("WORKBENCH_ACTIVATE_CMD='{executor} -c' " 690 | "WORKBENCH_HOME={td}/execmode {wb} {cmd} execmode", 691 | replace=dict(cmd=cmd, executor=EXECUTOR)) 692 | self.assertEqual(o.stderr, "") 693 | self.assertEqual(o.returncode, 0) 694 | self.assertEqual(o.stdout.strip(), cmd) 695 | 696 | 697 | # ----------------------------------------------------------------------------- 698 | # 699 | 700 | if __name__ == "__main__": 701 | unittest.main() 702 | --------------------------------------------------------------------------------