├── .dockerignore ├── .editorconfig ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── bin └── msu ├── deps └── install-deps.sh ├── docs ├── api.md ├── hack.md ├── installation.md ├── man │ ├── man1 │ │ └── msu.1.txt │ └── man3 │ │ ├── msu-console.3.txt │ │ ├── msu-core.3.txt │ │ ├── msu-core_utils.3.txt │ │ ├── msu-format.3.txt │ │ └── msu.3.txt └── showcase.md ├── get.sh ├── install.sh ├── lib ├── aliases.sh ├── console.sh ├── core.sh ├── core_utils.sh ├── format.sh ├── get-latest-version.py ├── load.sh ├── metadata.sh └── msu.sh ├── package.json ├── release.sh └── test ├── misc └── test.docs.sh ├── test.aliases.sh ├── test.core.sh ├── test.core_utils.sh ├── test.get.sh ├── test.install.sh ├── test.metadata.sh └── test.msu.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | #.git/ 2 | deps/ 3 | dist/ 4 | releases/ 5 | .editorconfig 6 | .gitignore 7 | .gitmodules 8 | .travis.yml 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = "space" 5 | indent_size = 2 6 | charset = "utf-8" 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = "lf" 10 | 11 | [Makefile] 12 | indent_style = "tab" 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # for local testing/trials 2 | try.sh 3 | lib/tmp_ 4 | _test* 5 | 6 | # swap/tmp files from vim 7 | *.swp 8 | 9 | # generated output 10 | dist/ 11 | 12 | # release assets 13 | releases/ 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/bats"] 2 | path = deps/bats 3 | url = https://github.com/sstephenson/bats.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: xenial 3 | language: go 4 | go: 5 | - 1.3 6 | install: make deps 7 | script: make test.bare 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # Change Log 3 | 4 | All notable changes to this project will be documented in this file. 5 | This project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | 8 | ## Unreleased 9 | 10 | Added: 11 | 12 | * Support installing modules from GitLab using shorthand 13 | * Support versions (git tags) when installing remote modules 14 | 15 | Fixed: 16 | 17 | * Reduce noise during installation 18 | 19 | 20 | ## [0.3.0][0.3.0] - 19/03/2016 21 | 22 | Added: 23 | 24 | * check dependencies aggressively, on installation 25 | 26 | Removed: 27 | 28 | * the `fs` and `net` modules are removed, to keep `msu` minimal 29 | 30 | 31 | ## [0.2.0][0.2.0] - 26/02/2016 32 | 33 | Added: 34 | 35 | * add command, `nuke`, for nuking `msu` (issue #19) 36 | * add support for SSH in installing modules from private, remote repos (issue #18) 37 | * allow assuming "yes" in a `yes_no` question, using `${MSU_ASSUME_YES}` 38 | 39 | Fixed: 40 | 41 | * fix using backspace as first key-press in password prompt 42 | * fix unnecessary logging to a file named `log` when using `msu` in a shebang 43 | * fix listing external modules, when directory at `${MSU_EXTERNAL_LIB}` does **not** exist 44 | * fix listing internal modules i.e. only list the `*.sh` files 45 | 46 | 47 | ## [0.1.0][0.1.0] - 13/02/2016 48 | 49 | Added: 50 | 51 | * add the alias `msu.reload` for reloading aliases 52 | * add support for use in shebang e.g `#!/usr/bin/env msu` 53 | 54 | Changed: 55 | 56 | * prefix all aliases added by msu library with `msu` 57 | 58 | Fixed: 59 | 60 | * Fix path to executable, if executed directly (not through symlink) 61 | 62 | 63 | ## [0.0.0][0.0.0] - 24/01/2016 64 | 65 | This is the very first version of `msu`. 66 | 67 | 68 | 69 | [0.0.0]:https://github.com/GochoMugo/msu/releases/tag/0.0.0 70 | [0.1.0]:https://github.com/GochoMugo/msu/releases/tag/0.1.0 71 | [0.2.0]:https://github.com/GochoMugo/msu/releases/tag/0.2.0 72 | [0.3.0]:https://github.com/GochoMugo/msu/releases/tag/0.3.0 73 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | ARG WORKDIR=/opt/gochomugo/msu 4 | 5 | WORKDIR ${WORKDIR} 6 | VOLUME ${WORKDIR}/dist/ 7 | 8 | RUN apt-get -y update && \ 9 | apt-get -y install asciidoc cabal-install git make sudo wget 10 | 11 | RUN cabal update --verbose=0 && \ 12 | cabal install shellcheck 13 | 14 | ENV BATS_VERSION v0.4.0 15 | RUN mkdir ${WORKDIR}/deps && \ 16 | git clone --depth=1 --branch=${BATS_VERSION} https://github.com/sstephenson/bats.git ${WORKDIR}/deps/bats 17 | 18 | ADD . ${WORKDIR}/ 19 | 20 | CMD ["make", "test.bare"] 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2016 GochoMugo 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated 7 | documentation files (the "Software"), to deal in the Software 8 | without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, 10 | and/or sell copies of the Software, and to permit persons to 11 | whom the Software is furnished to do so, subject to the 12 | following conditions: 13 | 14 | The above copyright notice and this permission notice shall 15 | be included in all copies or substantial portions of the 16 | Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 19 | KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 20 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 21 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 22 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 23 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 24 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Developers and CI 2 | # 3 | # Copyright (c) 2015 Gocho Mugo 4 | # Licensed under the MIT License 5 | 6 | # Image tag 7 | DOCKER_IMAGE=gochomugo/msu:dev 8 | 9 | 10 | ### test 11 | 12 | # Run tests (DEFAULT, alias: test.image) 13 | test: image test.image 14 | 15 | # Run linting tests (alias: test.image.lint) 16 | test.lint: image test.image.lint 17 | 18 | # Run unit tests (alias: test.image.unit) 19 | test.unit: image test.image.unit 20 | 21 | # Run tests on documentation (alias: test.image.doc) 22 | test.doc: image test.image.doc 23 | 24 | 25 | ### test.image 26 | 27 | # Run tests in container 28 | test.image: test.image.lint test.image.unit test.image.doc 29 | 30 | # Run linting tests in container 31 | test.image.lint: 32 | @echo "**** test.image.lint" 33 | @docker run --rm --tty $(DOCKER_IMAGE) make test.bare.lint 34 | 35 | # Run unit tests in container 36 | test.image.unit: 37 | @echo "**** test.image.unit" 38 | @docker run --rm --tty $(DOCKER_IMAGE) make test.bare.unit 39 | 40 | # Run tests on documentation in bare-metal mode 41 | test.image.doc: 42 | @echo "**** test.image.doc" 43 | @docker run --rm --tty $(DOCKER_IMAGE) make test.bare.doc 44 | 45 | 46 | ### test.bare 47 | 48 | # Run tests in bare-metal mode 49 | test.bare: test.bare.lint test.bare.unit test.bare.doc 50 | 51 | # Run linting tests in bare-metal mode 52 | test.bare.lint: ./*.sh lib/*.sh 53 | @echo "**** test.bare.lint" 54 | @PATH="${HOME}/.cabal/bin:${PATH}" shellcheck $? 55 | 56 | # Run unit tests in bare-metal mode 57 | test.bare.unit: test/test.*.sh 58 | @make doc.bare 59 | @echo "**** test.bare.unit" 60 | @./deps/bats/bin/bats $? 61 | 62 | # Run tests on documentation in bare-metal mode 63 | test.bare.doc: doc.bare 64 | @echo "**** test.bare.doc" 65 | @bash test/misc/test.docs.sh 66 | 67 | 68 | ### deps 69 | 70 | # Install dependencies 71 | deps: 72 | ./deps/install-deps.sh 73 | @echo ' >>> updating package index, using cabal' 74 | cabal update --verbose=0 75 | @echo ' >>> installing shellcheck, using cabal' 76 | cabal install shellcheck 77 | @echo ' >>> installing bats, using git submodule' 78 | git submodule init 79 | git submodule update 80 | 81 | 82 | ### doc 83 | 84 | # Generate documentation (alias: doc.image) 85 | doc: doc.image 86 | 87 | # Generate documentation in container 88 | doc.image: 89 | @echo "**** doc.image" 90 | @mkdir -p dist/ 91 | @docker run \ 92 | --env CHOWN_UID=$$(id --user) \ 93 | --env CHOWN_GID=$$(id --group) \ 94 | --rm \ 95 | --tty \ 96 | --volume $$(pwd)/dist:/opt/gochomugo/msu/dist \ 97 | $(DOCKER_IMAGE) \ 98 | make doc.image_ 99 | doc.image_: doc.bare 100 | @chown -R ${CHOWN_UID}:${CHOWN_GID} dist/ 101 | 102 | # Generate documentation in bare-metal mode 103 | doc.bare: clean.bare.doc 104 | @echo "**** doc.bare" 105 | @echo "a2x --doctype manpage --format manpage" 106 | @for file in $$(ls docs/man/**/*.txt) ; do \ 107 | echo " $${file}" ; \ 108 | mkdir -p "dist/$$(dirname $${file})" ; \ 109 | a2x \ 110 | --destination-dir "dist/$$(dirname $${file})" \ 111 | --doctype manpage \ 112 | --format manpage \ 113 | $${file} ; \ 114 | done 115 | 116 | 117 | ### release 118 | 119 | # Draft release 120 | release: test clean.bare doc 121 | @./bin/msu execute release.sh 122 | 123 | 124 | ### clean 125 | 126 | # Clean up 127 | clean: clean.image 128 | 129 | # Clean up docker image 130 | clean.image: 131 | @echo "**** clean.image" 132 | @docker rmi --force $(DOCKER_IMAGE) 133 | 134 | # Clean up in bare-metal mode 135 | clean.bare: clean.bare.test clean.bare.doc 136 | 137 | # Clean up working directory of test outputs 138 | clean.bare.test: 139 | @echo "**** clean.bare.test" 140 | @rm -rf lib/tmp_* _test* 141 | 142 | # Clean up generated docs 143 | clean.bare.doc: 144 | @echo "**** clean.bare.doc" 145 | @rm -rf dist/docs 146 | 147 | 148 | ### image 149 | 150 | # Build image; usually for testing or building docs. 151 | image: 152 | @echo "**** image" 153 | @docker build --tag $(DOCKER_IMAGE) . 154 | 155 | 156 | .PHONY: \ 157 | clean clean.bare clean.bare.doc clean.bare.test clean.image \ 158 | deps \ 159 | doc doc.bare doc.image \ 160 | image \ 161 | release \ 162 | test test.lint test.unit \ 163 | test.bare test.bare.lint test.bare.unit \ 164 | test.image test.image.lint test.image.unit 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # msu 3 | 4 | > A small Shell framework that makes writing bash scripts less sucky 5 | 6 | [![Terminal Lover](https://img.shields.io/badge/terminal-lover-blue.svg?style=flat-square)](https://github.com/GochoMugo) [![Travis](https://img.shields.io/travis/GochoMugo/msu.svg?style=flat-square)](https://travis-ci.org/GochoMugo/msu) 7 | 8 | * Introductory blog posts: [hack with msu](http://www.gmugo.in/musings/hack-with-msu/), [very first post](https://www.gmugo.in/musings/msu-introduction/) 9 | * [Project Principles](#principles) 10 | * [Showcase of modules using `msu`][showcase] 11 | * [Documentation](#documentation) 12 | 13 | 14 | ## features: 15 | 16 | > **`msu` wishlist** 17 | 18 | * [x] [automated/manual installation][installation] 19 | * [x] [small but comprehensive internal library][api] 20 | * [x] support external modules 21 | * [x] auto-loading aliases modules 22 | * [x] install module from github/bitbucket 23 | * [x] [highly tested](https://travis-ci.org/GochoMugo/msu) 24 | * [x] self upgrade 25 | * [ ] load/unload aliases 26 | * [ ] error catching/handling 27 | * [ ] bash completion 28 | * [ ] compatibility for other shell types e.g. zsh 29 | * [x] manpages 30 | 31 | 32 | ## installation: 33 | 34 | See the [installation instructions][installation]. 35 | 36 | 37 | ## documentation: 38 | 39 | You can always browse msu documentation using `man`: 40 | 41 | ```bash 42 | $ man 1 msu # command 43 | $ man 3 msu # library 44 | ``` 45 | 46 | More documentation is placed in the 47 | [`docs/`](https://github.com/GochoMugo/msu/tree/master/docs/) directory: 48 | 49 | * [installation][installation] 50 | * [API][api] 51 | * [showcase][showcase] 52 | * [hacking on msu][hacking] 53 | 54 | 55 | 56 | ## principles: 57 | 58 | 1. **Little added complexity.** `msu` should **not** warrant the user to learn scripting all over again. An existing script should be converted into a module with less effort. 59 | 1. **Minimal**. `msu` core should be as little as possible. How? Use common algorithms and data structures. Avoid doing something too fancy. 60 | 1. **Highly configurable**. Using environment variables and command-line switches, `msu` should be configurable in all its operations, including installation. 61 | 62 | 63 | ## license: 64 | 65 | __The MIT License (MIT)__ 66 | 67 | Copyright © 2015-2016 GochoMugo 68 | 69 | 70 | [installation]:https://github.com/GochoMugo/msu/tree/master/docs/installation.md "msu installation" 71 | [api]:https://github.com/GochoMugo/msu/tree/master/docs/api.md "msu API" 72 | [showcase]:https://github.com/GochoMugo/msu/blob/master/docs/showcase.md "showcase of modules using msu" 73 | [hacking]:https://github.com/GochoMugo/msu/blob/master/docs/hack.md "Hacking on msu" 74 | -------------------------------------------------------------------------------- /bin/msu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | . $(dirname ${BASH_SOURCE[0]})/../lib/msu.sh 3 | -------------------------------------------------------------------------------- /deps/install-deps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Installing dependencies on different systems 4 | 5 | 6 | echo 7 | echo " we are now installing dependencies" 8 | echo " using \`sudo' where seems necessary" 9 | echo " therefore, you might be prompted for your password" 10 | echo " for root privileges to be effected." 11 | echo 12 | 13 | 14 | # check if command is available 15 | function has() { 16 | command -v "${1}" > /dev/null 2>&1 17 | } 18 | 19 | # informing user, when we are unaware of how to install a dependency 20 | # ${1} - main command used to install packages in a distro 21 | # ${@:2} - commands that are required 22 | function missing_stub() { 23 | echo 24 | echo " ! Ensure you have the following installed: ${@:2}" 25 | echo " ! If it's possible to install it using \'${1}\'," 26 | echo " ! open up an issue, or even better, send" 27 | echo " ! a pull request to the repo." 28 | echo 29 | } 30 | 31 | # Debian-based distro 32 | has "apt-get" && { 33 | # we are running our tests on a ubuntu machine 34 | # we need to ensure our tests does not hang waiting for user input 35 | APT_FLAGS= 36 | if [[ -n "${CI}" ]] ; then APT_FLAGS="-y -q --install-recommends" ; fi 37 | echo " >>> updating package index, using apt-get" 38 | # shellcheck disable=SC2086 39 | sudo apt-get ${APT_FLAGS} update 40 | echo " >>> installing asciidoc, cabal-install, using apt-get" 41 | # shellcheck disable=SC2086 42 | sudo apt-get install ${APT_FLAGS} asciidoc cabal-install 43 | missing_stub "apt-get" "hub" 44 | exit 45 | } 46 | 47 | 48 | # Fedora 49 | has "yum" && { 50 | echo " >>> updating package index, using yum" 51 | sudo yum update 52 | echo " >>> installing cabal-install, using yum" 53 | sudo yum install cabal-install 54 | missing_stub "yum" "a2x" "hub" 55 | exit 56 | } 57 | 58 | 59 | # openSUSE:Tumbleweed 60 | has "zypper" && { 61 | echo " >>> installing cabal-install, using zypper" 62 | sudo zypper in cabal-install 63 | missing_stub "zypper" "a2x" "hub" 64 | exit 65 | } 66 | 67 | 68 | # Mac OS X with homebrew 69 | has "brew" && { 70 | echo " >>> installing cabal-install, using zypper" 71 | brew install cabal-install 72 | missing_stub "brew" "a2x" "hub" 73 | exit 74 | } 75 | 76 | 77 | # Mac OS X with MacPorts 78 | has "port" && { 79 | echo " >>> installing hs-cabal-install, using port" 80 | port install hs-cabal-install 81 | missing_stub "port" "a2x" "hub" 82 | exit 83 | } 84 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | 2 | # API 3 | 4 | > API documentation of internal modules 5 | 6 | This documentation is available in the manpages. To view the documentation, 7 | use: 8 | 9 | ```bash 10 | $ man 3 msu 11 | ``` 12 | 13 | -------------------------------------------------------------------------------- /docs/hack.md: -------------------------------------------------------------------------------- 1 | 2 | # Hacking on `msu` 3 | 4 | > You believe in the project, and now you want to make it better, with **code** 5 | 6 | 7 | ## dependencies: 8 | 9 | We are using: 10 | 11 | * [shellcheck](https://github.com/koalaman/shellcheck) - static analysis 12 | * [bats](https://github.com/sstephenson/bats) - test runner 13 | * [a2x](http://linux.die.net/man/1/a2x) - asciidoc converter 14 | * [hub](http://hub.github.com/) - Github terminal client 15 | 16 | To ensure reproducibility, we are using git submodules. 17 | 18 | 19 | ## prepare environment: 20 | 21 | Installing (some of) the dependencies: 22 | 23 | ```bash 24 | $ make deps 25 | ``` 26 | 27 | 28 | ## running tests: 29 | 30 | Running tests fully: 31 | 32 | ```bash 33 | $ make test 34 | ``` 35 | 36 | Some of the different types of tests can be invoked separately. 37 | 38 | ```bash 39 | $ make test.lint # run static analysis 40 | $ make test.unit # run unit tests 41 | $ make test.doc # run tests on documentation 42 | ``` 43 | 44 | 45 | ## building docs: 46 | 47 | You can build the manpages using: 48 | 49 | ```bash 50 | $ make doc 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | 2 | # installation 3 | 4 | ## pre-requisites: 5 | 6 | The following commands are required: 7 | 8 | * [git](http://git-scm.com) - for installs 9 | * [python](https://www.python.org/) - for self-upgrading 10 | * python packages: 11 | * [requests](http://docs.python-requests.org/en/latest/) 12 | * [semver](https://pypi.python.org/pypi/semver) 13 | 14 | 15 | ## methods: 16 | 17 | There are different ways of installing `msu`. 18 | 19 | The preferred way is to use the **OFFICIAL RELEASE**: 20 | 21 | 1. get the latest tarball at https://github.com/GochoMugo/msu/releases/latest: 22 | 23 | ```bash 24 | ⇒ tar xvf msu-x.x.x.tar.gz 25 | ⇒ cd msu-x.x.x/ 26 | ⇒ ./install.sh 27 | ``` 28 | 29 |
30 | 31 | The following methods use the master branch instead of official releases. Also, 32 | the manpages are **not** installed in these cases. They include: 33 | 34 | 1. **automated install** 35 | 36 | ```bash 37 | ⇒ wget -qO- https://git.io/vTE0s | bash 38 | ``` 39 | 40 | This downloads the `get.sh` script in the repo and runs it using **bash**. 41 | It's easier and faster. 42 | 43 | 1. **manual install** 44 | 45 | ```bash 46 | ⇒ git clone https://github.com/GochoMugo/msu.git 47 | ⇒ cd msu/ 48 | ⇒ ./install.sh 49 | ``` 50 | 51 | You clone this repo and run the `install.sh` script. This method is 52 | preferable if you will be hacking on the project. 53 | 54 | 1. **zip download install** 55 | 56 | ```bash 57 | ⇒ wget "https://github.com/GochoMugo/msu/archive/master.zip" 58 | ⇒ unzip msu-master.zip -d msu # you can use `7z` (or other alternatives) 59 | ⇒ cd msu 60 | ⇒ ./install.sh 61 | ``` 62 | 63 | **Tip:** 7zip is preferred here due to its high compression ratio capabilities 64 | you can use any other software you prefer. 65 | 66 | 67 | ## default setup: 68 | 69 | The **default** setup installs the library at `${HOME}/lib` and the executable 70 | at `${HOME}/bin`. The manpages are installed at `${HOME}/share/man/`. 71 | 72 | **benefits:** 73 | 74 | * allows installing without requiring `sudo` 75 | 76 | 77 | **caveats:** 78 | 79 | * installs the `msu` for the current user only. Other users on the system will have to install on their own. 80 | 81 | 82 | ## custom setup: 83 | 84 | Installation has been made simple by using environment variables. To change 85 | the directory to put the library in, say `/usr/lib/`, you use the `${LIB}` 86 | variable. 87 | 88 | To change the directory to put the executable in, say `/usr/bin/`, you use 89 | the `${BIN}` variable. 90 | 91 | To change the directory to put the manpages in, say `/usr/share/man`, you 92 | use the `${MAN}` variable. 93 | 94 | By default, the latest build of `msu` is downloaded and installed. To 95 | install a specific build version, use the `${BUILD}` variable, say 96 | `9482e161c974bc0cebf823fc0fe8a3caed14a8a0`. This is useful for debugging 97 | purposes and rollbacks. 98 | 99 | > Use of `${BUILD}` is **only** applicable to the automated installs. 100 | 101 | As an example, *(using all variables. you can use one or more variables)* 102 | 103 | ```bash 104 | ⇒ wget -qO- https://git.io/vTE0s | LIB=/usr/lib BIN=/usr/bin/ MAN=/usr/share/man BUILD=9482e161c974bc0cebf823fc0fe8a3caed14a8a0 bash # automated install 105 | ⇒ LIB=/usr/lib BIN=/usr/bin/ MAN=/usr/share/man ./install.sh # manual install 106 | ``` 107 | 108 | > Ensure you have enough **permissions** to write to the `${LIB}`, `${BIN}` 109 | > and `${MAN}` directories. 110 | -------------------------------------------------------------------------------- /docs/man/man1/msu.1.txt: -------------------------------------------------------------------------------- 1 | MSU(1) 2 | ====== 3 | :doctype: manpage 4 | 5 | 6 | NAME 7 | ---- 8 | msu - my shell utilities 9 | 10 | 11 | SYNOPSIS 12 | -------- 13 | *msu* require|install|uninstall 'MODULE'... 14 | 15 | *msu* execute|install-many|uninstall-many 'PATH' 16 | 17 | *msu* run 'MODULE'.'FUNC' 18 | 19 | *msu* list ['SCOPE'] 20 | 21 | *msu* upgrade ['VERSION'] 22 | 23 | *msu* nuke 24 | 25 | *msu* version ['MODULE'] 26 | 27 | *msu* help 28 | 29 | 30 | DESCRIPTION 31 | ----------- 32 | The msu(1) command adds useful utilities for use in your shell. 33 | *msu* stands for 'My' 'Shell' 'Utilities'. 34 | 35 | 36 | COMMANDS 37 | -------- 38 | *re, require* 'MODULE'...:: 39 | Loads the module 'MODULE' into a new bash environment, 40 | making the modules' functions and variables available. This 41 | allows interacting with the low-level library functions directly. 42 | This environment should 'sourced' into the current environment. 43 | 44 | For example, 45 | 46 | $ . msu require console net 47 | $ success 'a success message' 48 | 49 | *r, run* 'MODULE'.'FUNC':: 50 | Runs the function 'FUNC' in the module 'MODULE'. Any extra 51 | arguments are passed to the function as is. 52 | 53 | For example, 54 | 55 | $ msu run console.success 'a success message' 56 | 57 | *x, execute* 'PATH':: 58 | Executes the file at path 'PATH' by simply running the code 59 | through bash, with the core library loaded before-hand. This 60 | emulates the action of running a function with its body being 61 | the contents of the file. This command should be considered 62 | DANGEROUS as it is able to execute commands in a file without the 63 | execution bit set. 64 | 65 | *i, install* 'MODULE'...:: 66 | Installs the module 'MODULE' into the directory at `${MSU_EXTERNAL_LIB}`. 67 | The module can be located either locally or remotely. If remote, 68 | a prefix shorthand is required to determine where to fetch the 69 | module. 70 | 71 | Available shorthands include: 72 | 73 | gh: - from Github, e.g. 'gh:GochoMugo/transfer' 74 | ghs: - from Github, using SSH e.g. 'ghs:GochoMugo/transfer' 75 | bt: - from Bitbucket, e.g. 'bt:GochoMugo/transfer' 76 | bts: - from Bitbucket, using SSH e.g. 'bts:GochoMugo/transfer' 77 | gl: - for GitLab, e.g. 'gl:GochoMugo/transfer' 78 | gls: - for GitLab, using SSH e.g. 'gls:GochoMugo/transfer' 79 | 80 | The shorthands are case-insensitive. 81 | 82 | If the remote repository is private, SSH should be used instead. This 83 | allows avoiding being prompted for password and the request being 84 | rejected in cases where 2FA is enabled for the user. 85 | 86 | You can specify a version, using a git tag. For example, 87 | 'gh:GochoMugo/transfer#v0.0.0' would fetch the remote module 88 | at version 'v0.0.0', provided the git tag ('v0.0.0'; exactly as 89 | specified) exists. 90 | 91 | *im, install-many* 'PATH':: 92 | Installs modules listed in a file at path 'PATH'. The list is a 93 | simple array of module names, delimited with newlines. This is 94 | useful in bootstrapping new machines. 95 | 96 | *u, uninstall* 'MODULE'...:: 97 | Uninstalls the module 'MODULE' by removing its contents from 98 | `${MSU_EXTERNAL_LIB}`. 99 | 100 | *um, uninstall-many* 'PATH':: 101 | Uninstalls modules listed in a file at path 'PATH'. The list 102 | is a simple array of module names, delimited with newlines. This 103 | is useful in un-bootstrapping machines (as a reverse of 104 | *install-many*). 105 | 106 | *ls, list* ['SCOPE']:: 107 | Lists either internal and external modules installed. Passing 'SCOPE' 108 | as '-i'/'--internal' lists only internal modules while '-e'/'--external' 109 | lists only external modules. 110 | 111 | *up, upgrade* ['VERSION']:: 112 | Upgrades msu to the version 'VERSION', if passed. Otherwise upgrades 113 | to the latest version. If 'VERSION' equals 'HEAD', the master branch 114 | is used directly. This request hits the network to check for new 115 | versions and execute downloads if necessary. 116 | 117 | *nk, nuke*:: 118 | Nukes msu entirely. This involves removing the msu executable, internal 119 | and external libraries, load strings added to ~/.bashrc and temporary 120 | installation locations. 121 | 122 | *h, help*:: 123 | Shows basic help information, shortened for easier recap. 124 | 125 | *v, version* ['MODULE']:: 126 | Shows version information for the module 'MODULE', if passed. Otherwise 127 | shows version information for msu itself. 128 | 129 | 130 | ENVIRONMENT VARIABLES 131 | --------------------- 132 | *`${MSU_EXTERNAL_LIB}`*:: 133 | Represents the path to the directory holding the external modules. 134 | It is where modules are installed to, with the 'install' and 135 | 'install-many' commands. It defaults to `${HOME}/.msu`. 136 | 137 | 138 | ALIASES 139 | ------- 140 | On startup, aliases are loaded. Aliases added by msu are prefixed with 141 | 'msu'. Remember any alias (including those added by msu itself) can be 142 | overridden. See msu(3) for more details on how aliases work. 143 | 144 | One of the most important aliases is *msu.reload*. 145 | 146 | *msu.reload*:: 147 | Reloads aliases from internal and external modules. This allows you to 148 | load new aliases after installing modules. 149 | 150 | Other aliases are listed in man(3). 151 | 152 | 153 | SHEBANG 154 | ------- 155 | 156 | msu can be used in a shebang line, which in almost all cases would be 157 | '#!/usr/bin/env msu'. The shell script (using the shebang), would receive 158 | the path to itself as `${1}` and have the other arguments follow as normal, 159 | in `${2}`, `${3}`, ... This means that the script can accept command-line 160 | arguments. 161 | 162 | 163 | CAVEATS 164 | ------- 165 | The command *upgrade* uses python behind the covers to upgrade msu(1) to 166 | the latest version automatically (without specifying 'VERSION'). Therefore, 167 | the command python(1) must be available. Also these python packages need 168 | to be installed: requests, semver. 169 | 170 | The commands *install*, *install-many* and *upgrade* use git for 171 | cloning repositories and generating module metadata. Therefore, the 172 | command git(1) must be available. 173 | 174 | 175 | 176 | RESOURCES 177 | --------- 178 | Source code: https://github.com/GochoMugo/msu 179 | 180 | Issue tracker: https://github.com/GochoMugo/msu/issues 181 | 182 | 183 | AUTHOR 184 | ------ 185 | *msu* is developed and maintained by Gocho Mugo. 186 | 187 | 188 | COPYING 189 | ------- 190 | THE MIT LICENSE (MIT) 191 | 192 | Copyright \(C) 2015-2016 Gocho Mugo 193 | -------------------------------------------------------------------------------- /docs/man/man3/msu-console.3.txt: -------------------------------------------------------------------------------- 1 | MSU-CONSOLE(3) 2 | ============== 3 | :doctype: manpage 4 | 5 | 6 | NAME 7 | ---- 8 | msu-console - interacting with the user through the console/terminal. 9 | 10 | 11 | SYNOPSIS 12 | -------- 13 | The primary use of this module is for console operations, such as logging 14 | and prompting user for input. 15 | 16 | msu_require "console" 17 | 18 | 19 | DESCRIPTION 20 | ----------- 21 | *write_text('TEXT' [, 'COLOR_INDEX'=0])*:: 22 | Writes the text 'TEXT' to the console using the coloer indexed with the 23 | index 'COLOR_INDEX'. 24 | 25 | The color indices: 26 | 27 | 0 - blue 28 | 1 - green 29 | 2 - red 30 | 31 | *log(['MESSAGE']...)*:: 32 | Writes the messages 'MESSAGE'... to stdout, usually in blue color. This is 33 | used for normal informational messages. 34 | 35 | *success(['MESSAGE']...)*:: 36 | Writes the messages 'MESSAGE'..., usually in green color. This is used 37 | for success messages. 38 | 39 | *error(['MESSAGE']...)*:: 40 | Writes the messages 'MESSAGE'..., usually in red color. This is used for 41 | error messages. 42 | 43 | *tick(['LABEL'])*:: 44 | Writes the label 'LABEL' using the green color and a tick prefixing it. 45 | 46 | *cross(['LABEL'])*:: 47 | Writes the label 'LABEL' using the red color and a cross prefixing it. 48 | 49 | *list(['LABEL'])*:: 50 | Writes the label 'LABEL' using the blue color and a right-arrow prefixing 51 | it. 52 | 53 | *ask('QUESTION', 'DESTINATION_VARIABLE' [, 'CLEAR'=0])*:: 54 | Asks the question 'QUESTION' to the user. The answer is assigned to the 55 | variable 'DESTINATION_VARIABLE'. If 'CLEAR' is set to 1, the input is 56 | hidden, as in the manner of a password. Otherwise, the text is show 57 | plainly. 58 | 59 | *yes_no('QUESTION' [, 'DEFAULT'="N"])*:: 60 | Asks the yes/no questions 'QUESTION' to the user. Returns 0 for a 61 | YES and 1 for a NO. If default is set to "Y", then Yes will be the 62 | default answer. Otherwise, defaults to No. If the variable 63 | `${MSU_ASSUME_YES}` has a non-zero length, YES is assumed, without 64 | asking the question. 65 | 66 | 67 | RESOURCES 68 | --------- 69 | Source code: https://github.com/GochoMugo/msu 70 | 71 | Issue tracker: https://github.com/GochoMugo/msu/issues 72 | 73 | 74 | AUTHOR 75 | ------ 76 | *msu* is developed and maintained by Gocho Mugo. 77 | 78 | 79 | COPYING 80 | ------- 81 | THE MIT LICENSE (MIT) 82 | 83 | Copyright \(C) 2015-2016 Gocho Mugo 84 | -------------------------------------------------------------------------------- /docs/man/man3/msu-core.3.txt: -------------------------------------------------------------------------------- 1 | MSU-CORE(3) 2 | =========== 3 | :doctype: manpage 4 | 5 | 6 | NAME 7 | ---- 8 | msu-core - this is the core library for msu. 9 | 10 | 11 | SYNOPSIS 12 | -------- 13 | This module is automatically loaded into each module, internal or external. 14 | It is readily available for use by module code without need to 15 | 'msu_require' them. 16 | 17 | 18 | DESCRIPTION 19 | ----------- 20 | *`${MSU_LIB}`*:: 21 | Absolute path to the directory holding the internal library. It is mostly 22 | useful for internal modules. 23 | 24 | *`${MSU_EXTERNAL_LIB}`*:: 25 | Absolute path to the directory holding the external modules. Again, it is 26 | mostly useful for internal modules. 27 | 28 | *msu_require(MODULE)*:: 29 | Loads the module 'MODULE' into scope. 30 | 31 | For example, 32 | 33 | msu_require "console" 34 | 35 | *msu_run(MODULE.FUNC, [PARAMS]...)*:: 36 | Run the function with name 'FUNC' in module 'MODULE'. Parameters 'PARAMS' 37 | are passed to the function. 38 | 39 | For example, 40 | 41 | msu_run console.log "log to console" 42 | 43 | 44 | RESOURCES 45 | --------- 46 | Source code: https://github.com/GochoMugo/msu 47 | 48 | Issue tracker: https://github.com/GochoMugo/msu/issues 49 | 50 | 51 | AUTHOR 52 | ------ 53 | *msu* is developed and maintained by Gocho Mugo. 54 | 55 | 56 | COPYING 57 | ------- 58 | THE MIT LICENSE (MIT) 59 | 60 | Copyright \(C) 2015-2016 Gocho Mugo 61 | -------------------------------------------------------------------------------- /docs/man/man3/msu-core_utils.3.txt: -------------------------------------------------------------------------------- 1 | MSU-CORE_UTILS(3) 2 | ================= 3 | :doctype: manpage 4 | 5 | 6 | NAME 7 | ---- 8 | msu-core_utils - utilities for use with msu core. 9 | 10 | 11 | SYNOPSIS 12 | -------- 13 | These utilities help build up the msu ecosystem by offering functions 14 | very common in working with msu. 15 | 16 | msu_require "core_utils" 17 | 18 | 19 | DESCRIPTION 20 | ----------- 21 | *upgrade(['VERSION'])*:: 22 | Upgrades msu to version 'VERSION', if it is passed. Otherwise, the 23 | function defaults to upgrading to the latest version. If 'VERSION' 24 | equals 'HEAD' (case-insensitive), the master branch of the source 25 | code repo is used instead.'HEAD' is useful in obtaining changes 26 | before new releases are made. This hits the network to check for 27 | new releases and download tarballs. 28 | 29 | *install(['MODULE']...)*:: 30 | Installs the modules 'MODULE'... into the directory pointed to by 31 | `${MSU_EXTERNAL_LIB}`. To install remote modules, we require 32 | prefix shorthands: 33 | 34 | gh: - for public Github repos 35 | ghs: - for private GitHub repos 36 | bt: - for public Bitbucket repos 37 | bts: - for private Bitbucket repos 38 | gl: - for public GitLab repos 39 | gls: - for private GitLab repos 40 | 41 | *install-many('PATH')*:: 42 | Using a file at path 'PATH' containing a '\n'-delimited list of modules, 43 | installs the listed modules. Shorthands can be used in this list. This 44 | is useful in bootstrapping new machines. 45 | 46 | *uninstall(['MODULE']...)*:: 47 | Uninstalls the modules 'MODULE'... from the directory pointed to by 48 | `${MSU_EXTERNAL_LIB}`. This is almost synonymous to `rm -rf 'MODULE'`. 49 | 50 | *uninstall-many('PATH')*:: 51 | Using a file at path 'PATH' containing a '\n'-delimited list of modules, 52 | uninstall the listed modules. As expected, shorthands can not be used 53 | here. 54 | 55 | *has_command('COMMAND')*:: 56 | Return 0 if 'COMMAND' is available on the system. Otherwise, return a 57 | non-zero return code. 58 | 59 | *is_superuser()*:: 60 | Return 0 if we are running as super-user (root). Otherwise, return 1. 61 | 62 | *list_modules(['SCOPE'])*:: 63 | List installed modules, both internal and external modules. Using 'SCOPE', 64 | we can limit the listed modules. If 'SCOPE' equals '-i'/'--internal', 65 | only internal modules are listed. If 'SCOPE' equals '-e'/'--external', 66 | only external modules are listed. 67 | 68 | 69 | RESOURCES 70 | --------- 71 | Source code: https://github.com/GochoMugo/msu 72 | 73 | Issue tracker: https://github.com/GochoMugo/msu/issues 74 | 75 | 76 | AUTHOR 77 | ------ 78 | *msu* is developed and maintained by Gocho Mugo. 79 | 80 | 81 | COPYING 82 | ------- 83 | THE MIT LICENSE (MIT) 84 | 85 | Copyright \(C) 2015-2016 Gocho Mugo 86 | -------------------------------------------------------------------------------- /docs/man/man3/msu-format.3.txt: -------------------------------------------------------------------------------- 1 | MSU-FORMAT(3) 2 | ============= 3 | :doctype: manpage 4 | 5 | 6 | NAME 7 | ---- 8 | msu-format - formatting symbols 9 | 10 | 11 | SYNOPSIS 12 | -------- 13 | Formatting symbols, quite useful when working with the 'console' module. 14 | 15 | msu_require "format" 16 | 17 | 18 | DESCRIPTION 19 | ----------- 20 | I am a lazy fucker, so you will need to read through the file yourself. 21 | 22 | 23 | RESOURCES 24 | --------- 25 | Source code: https://github.com/GochoMugo/msu 26 | 27 | Issue tracker: https://github.com/GochoMugo/msu/issues 28 | 29 | 30 | AUTHOR 31 | ------ 32 | *msu* is developed and maintained by Gocho Mugo. 33 | 34 | 35 | COPYING 36 | ------- 37 | THE MIT LICENSE (MIT) 38 | 39 | Copyright \(C) 2015-2016 Gocho Mugo 40 | -------------------------------------------------------------------------------- /docs/man/man3/msu.3.txt: -------------------------------------------------------------------------------- 1 | MSU(3) 2 | ====== 3 | :doctype: manpage 4 | 5 | 6 | NAME 7 | ---- 8 | msu - Bash interface to msu 9 | 10 | 11 | SYNOPSIS 12 | -------- 13 | Understanding this documentation is important in designing new modules. 14 | It documents the internal modules msu(1) ships with. 15 | 16 | Conventions used when documenting symbols: 17 | 18 | *`${VARIABLE}`*:: 19 | Represents the variable with the name 'VARIABLE'. This is used for 20 | both bash and environment variables, such as `${MSU_EXTERNAL_LIB}`. 21 | 22 | *function_name(parameter1...)*:: 23 | Represents the function with the name 'function_name' and formal 24 | parameters 'parameter1'.... Do remember that invoking the function 25 | is done by 'function_name "parameter 1"'. 26 | 27 | 28 | DESCRIPTION 29 | ----------- 30 | This documentation is on internal msu(3) modules. These modules include: 31 | 32 | *core*:: 33 | The most low-level module in charge of getting msu get off the ground. 34 | See *msu-core(3)*. 35 | 36 | *core_utils*:: 37 | Utilties that make msu more useful and manageable to use. These utilities 38 | help build up the msu ecosystem. See *msu-core_utils(3)*. 39 | 40 | *console*:: 41 | Reading and writing to console/terminal. Function include logging and 42 | prompting user for input. They facilitate interaction between user and msu. 43 | See *msu-console(3)*. 44 | 45 | *format*:: 46 | Formatting symbols useful in terminal output. Such symbols include ticks 47 | and arrows. See *msu-format(3)*. 48 | 49 | 50 | MODULES 51 | ------- 52 | There are *internal* and *external* modules. Internal modules are 53 | shipped with `msu`. External modules are those you install on your own. 54 | 55 | All internal modules can be found in the 'lib' directory in the source code 56 | repo. These modules offer the `msu` backbone and useful utilities. 57 | 58 | Anybody can install their own external modules. On installation, these 59 | modules are placed at `${MSU_EXTERNAL_LIB}`, which by default is 60 | `${HOME}/.msu`. You can install external modules, by simple copying the 61 | module contents to the external library directory (`${MSU_EXTERNAL_LIB}`). 62 | *msu install* can be used to do this copying in a better way. 63 | 64 | $ msu install my-module 65 | 66 | You can also install remote modules from github, using prefix shorthands. 67 | Available shorthands include: 68 | 69 | gh: - for Github 70 | bt: - for Bitbucket 71 | 72 | For example if https://github.com/GochoMugo/submarine is a module you want 73 | to install: 74 | 75 | $ msu install gh:GochoMugo/submarine 76 | 77 | 'NOTE:' the prefix shorthand, `gh:` part, is case-insensitive. 78 | 79 | Internal modules *always* take precedence over external modules. 80 | 81 | A valid module can be a single script, say `sample.sh`, or a directory 82 | with many scripts, say 83 | 84 | .... 85 | my-module/ 86 | |-- aliases.sh # place your aliases here 87 | |-- script.sh 88 | `-- inner-dir 89 | |-- script.sh 90 | `-- another-script.sh 91 | .... 92 | 93 | 'NOTE:' Nested directories are allowed. 94 | 95 | A valid module filename has the extension *.sh* and has no *.* (dot) 96 | except that preceding the extension. 97 | 98 | Examples: 99 | 100 | * `foobar.sh` - valid 101 | * `foo-bar.sh` - valid 102 | * `foo_bar.sh` - valid 103 | * `foo.bar.sh` - *invalid* 104 | 105 | 106 | DEPENDENCIES 107 | ------------ 108 | If a module defines the `${DEPS}` variable, `msu` will check if the 109 | dependencies are installed. Note that `msu` will *not error* if one or 110 | more of the dependencies are missing, it will just give a **warning** to 111 | the user. This is by design! 112 | 113 | Example: 114 | 115 | DEPS="git node npm" 116 | 117 | 118 | ALIASES 119 | ------- 120 | msu loads aliases into the shell, if sourced during load process. msu 121 | exports its own aliases, for its internal library functions. These specific 122 | aliases are documented alongisde the functions. 123 | 124 | External modules can also add aliases by having an 'aliases.sh' file in 125 | the root of the module's directory. The aliases in the file are automatically 126 | loaded after the internal module ones. 127 | 128 | A top-most aliases file at `${MSU_EXTERNAL_LIB}/aliases.sh` can be used to 129 | override any other aliases as it is loaded last. 130 | 131 | A sample aliases file: 132 | 133 | alias my-alias="msu run my-module.func" 134 | alias another-alias="msu run my-module.another_func" 135 | 136 | *Conflicts* can happen when, say two modules both try to create an alias with 137 | the same name. `msu` has no way to detect this. The best way to avoid this 138 | is to use a short prefix in front of your aliases. 139 | 140 | For example: 141 | 142 | alias pr.dance='msu run prior.module.dance' 143 | 144 | 145 | RESOURCES 146 | --------- 147 | Source code: https://github.com/GochoMugo/msu 148 | 149 | Issue tracker: https://github.com/GochoMugo/msu/issues 150 | 151 | 152 | AUTHOR 153 | ------ 154 | *msu* is developed and maintained by Gocho Mugo. 155 | 156 | 157 | COPYING 158 | ------- 159 | THE MIT LICENSE (MIT) 160 | 161 | Copyright \(C) 2015-2016 Gocho Mugo 162 | -------------------------------------------------------------------------------- /docs/showcase.md: -------------------------------------------------------------------------------- 1 | 2 | # showcase 3 | 4 | > A Showcase of modules using `msu` 5 | > 6 | > Send a **Pull Request** to get your module added here! 7 | 8 | * [Submarine](https://github.com/GochoMugo/submarine): Some useful submarines (utilities) for a Node.js/Devops developer 9 | * [Proverb](https://github.com/GochoMugo/proverb): A command-line interface to [Programmer's Proverbs](https://github.com/AntJanus/programmers-proverbs) 10 | * [Transfer](https://github.com/GochoMugo/transfer): Defines transfer alias and provides easy command line file and folder sharing using [transfer.sh](https://transfer.sh/) 11 | * [Android SDK installer](https://github.com/GochoMugo/android-sdk-installer): An easy way to install the standalone Android SDK tools 12 | * [Fancy Aliases](https://github.com/GochoMugo/fancy-aliases): Some fancy Bash aliases 13 | 14 | -------------------------------------------------------------------------------- /get.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Get me The MSU 3 | 4 | 5 | set -o errexit 6 | set -o nounset 7 | 8 | 9 | GIT_URL=https://github.com/GochoMugo/msu.git 10 | BUILD="${BUILD:-}" 11 | CLONE_DIR=msu 12 | MARKER=" >>>" 13 | 14 | pushd /tmp > /dev/null 15 | 16 | echo "${MARKER} cloning repo" 17 | rm -fr ${CLONE_DIR} 18 | if [ "${BUILD}" ] ; then 19 | git clone --quiet ${GIT_URL} ${CLONE_DIR} 20 | pushd ${CLONE_DIR} > /dev/null 21 | echo "${MARKER} checking out build ${BUILD}" 22 | git checkout --quiet "${BUILD}" 23 | popd > /dev/null 24 | else 25 | git clone --depth=1 --quiet ${GIT_URL} ${CLONE_DIR} 26 | fi 27 | 28 | echo "${MARKER} running installation script" 29 | pushd ${CLONE_DIR} > /dev/null 30 | LIB="${LIB:-}" BIN="${BIN:-}" ./install.sh 31 | popd > /dev/null 32 | 33 | popd > /dev/null 34 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Installs msu 3 | 4 | 5 | set -o errexit 6 | set -o nounset 7 | 8 | 9 | BASHRC="${HOME}/.bashrc" 10 | BIN="${BIN:-${HOME}/bin}" 11 | LIB="${LIB:-${HOME}/lib}" 12 | MAN="${MAN:-${HOME}/share/man}" 13 | MSU_LIB="${LIB}/msu" 14 | MSU_EXE="${BIN}/msu" 15 | MSU_MAN="${MAN}" 16 | MARKER=" >>>" 17 | MSU_LOAD_STRING="# loading msu 18 | . msu require load" 19 | 20 | 21 | function check_dep() { 22 | command -v "${1}" > /dev/null 2>&1 || { 23 | echo "\`${1}' is NOT available. It is required for: ${2}" > /dev/stderr 24 | exit 1 25 | } 26 | } 27 | 28 | 29 | function check_deps() { 30 | check_dep "bash" "runtime" 31 | check_dep "cat" "file reading, stream concat" 32 | check_dep "command" "command lookup/execution" 33 | check_dep "cp" "file copying" 34 | check_dep "cut" "stream cutting" 35 | check_dep "dirname" "path manipulation" 36 | check_dep "echo" "line printing" 37 | check_dep "git" "install, self-upgrade, module-install, metadata gen." 38 | check_dep "grep" "regexp matching" 39 | check_dep "id" "check user id" 40 | check_dep "ln" "symlink creation" 41 | check_dep "mkdir" "directory creation" 42 | check_dep "mv" "file renaming" 43 | check_dep "popd" "pop directory from stack" 44 | check_dep "pushd" "push directory onto stack" 45 | check_dep "python" "self-upgrade" 46 | check_dep "readlink" "handling symlinks" 47 | check_dep "rm" "file removal" 48 | check_dep "tar" "self-upgrade" 49 | check_dep "tput" "formatting styles" 50 | check_dep "tr" "character translation" 51 | check_dep "wget" "self-upgrade" 52 | } 53 | 54 | 55 | echo "${MARKER} checking if all dependencies are available" 56 | check_deps 57 | 58 | 59 | echo "${MARKER} checking if ${BIN} is in \${PATH}" 60 | echo "${PATH}" | grep "${BIN}" > /dev/null || { 61 | echo "${MARKER} ${BIN} not in path. Adding it to ${BASHRC}. You need to restart your terminal for this to take effect!" 62 | { 63 | echo "" 64 | echo "# added by msu" 65 | echo "export PATH=\"${BIN}\":\${PATH}" 66 | } >> "${BASHRC}" 67 | } 68 | 69 | 70 | echo "${MARKER} copying library" 71 | [ "${MSU_EXE}" == "${MSU_LIB}" ] && { 72 | MSU_LIB="${MSU_LIB}-lib" 73 | } 74 | rm -rf "${MSU_LIB}" 75 | mkdir -p "${MSU_LIB}" 76 | cp -r lib/* "${MSU_LIB}" 77 | 78 | 79 | echo "${MARKER} checking if ${MSU_MAN} is in \${MANPATH}" 80 | echo "${MANPATH:-}" | grep "${MSU_MAN}" > /dev/null || { 81 | echo "${MARKER} ${MSU_MAN} not in manpath. Adding it to ${BASHRC}." 82 | echo "${MARKER} !! You need to restart your terminal for this to take effect!" 83 | { 84 | echo "" 85 | echo "# added by msu" 86 | echo "export MANPATH=\"${MSU_MAN}\":\${MANPATH}" 87 | } >> "${BASHRC}" 88 | } 89 | 90 | 91 | # there are cases where manpages are not generated yet 92 | if [ -f dist/docs/man/man1/msu.1 ] && [ -f dist/docs/man/man3/msu.3 ] 93 | then 94 | echo "${MARKER} copying manpages" 95 | mkdir -p "${MSU_MAN}/man1" "${MSU_MAN}/man3" 96 | cp -r dist/docs/man/man1/*.1 "${MSU_MAN}/man1" || true 97 | cp -r dist/docs/man/man3/*.3 "${MSU_MAN}/man3" || true 98 | fi 99 | 100 | 101 | # tarballs do NOT ship with the .git directory 102 | # instead, the metadata is generated at release time 103 | if [ -d .git ] 104 | then 105 | echo "${MARKER} generating metadata" 106 | MSU_BUILD_HASH=$(git rev-parse HEAD) 107 | MSU_BUILD_DATE=$(git show -s --format=%ci "${MSU_BUILD_HASH}") 108 | { 109 | echo "MSU_BUILD_HASH='${MSU_BUILD_HASH}'" 110 | echo "MSU_BUILD_DATE='${MSU_BUILD_DATE}'" 111 | } >> "${MSU_LIB}"/metadata.sh 112 | fi 113 | 114 | 115 | echo "${MARKER} linking executable" 116 | mkdir -p "$(dirname "${MSU_EXE}")" 117 | rm -f "${MSU_EXE}" 118 | ln -sf "${MSU_LIB}"/msu.sh "${MSU_EXE}" 119 | 120 | 121 | echo "${MARKER} make bash load msu into environment on start" 122 | grep "${MSU_LOAD_STRING}" "${BASHRC}" > /dev/null || { 123 | { 124 | echo "" 125 | echo "${MSU_LOAD_STRING}" 126 | } >> "${BASHRC}" 127 | } 128 | 129 | 130 | echo "${MARKER} storing installation configuration" 131 | { 132 | echo "MSU_INSTALL_LIB='${LIB}'" 133 | echo "MSU_INSTALL_BIN='${BIN}'" 134 | echo "MSU_INSTALL_MAN='${MAN}'" 135 | echo "MSU_INSTALL_LOAD_STRING='${MSU_LOAD_STRING}'" 136 | } >> "${MSU_LIB}"/metadata.sh 137 | 138 | 139 | 140 | echo "${MARKER} finished installing" 141 | echo "${MARKER} !! Restart your terminal to load msu into environment" 142 | 143 | echo 144 | "${MSU_EXE}" version 145 | echo 146 | -------------------------------------------------------------------------------- /lib/aliases.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # how you live with no aliases 3 | # 4 | # Copyright (c) 2015 GochoMugo 5 | 6 | 7 | # external library 8 | MSU_EXTERNAL_LIB="${MSU_EXTERNAL_LIB:-${HOME}/.msu}" 9 | 10 | 11 | # general 12 | alias x="msu run" 13 | 14 | # this allows reloading the aliases, useful after installing new 15 | # modules 16 | alias msu.reload=". msu require load" 17 | 18 | 19 | # external module aliases 20 | if [ -d "${MSU_EXTERNAL_LIB}" ] 21 | then 22 | modules=$(ls "${MSU_EXTERNAL_LIB}") 23 | for module in ${modules} 24 | do 25 | alias_file=${MSU_EXTERNAL_LIB}/${module}/aliases.sh 26 | # shellcheck source=/dev/null 27 | [ -f "${alias_file}" ] && source "${alias_file}" 28 | done 29 | unset alias_file 30 | unset module 31 | unset modules 32 | fi 33 | 34 | 35 | # top-most aliases file 36 | # shellcheck source=/dev/null 37 | [ -f "${MSU_EXTERNAL_LIB}/aliases.sh" ] && source "${MSU_EXTERNAL_LIB}/aliases.sh" 38 | echo > /dev/null 39 | -------------------------------------------------------------------------------- /lib/console.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # write to the console 3 | # 4 | # Copyright (c) 2015 GochoMugo 5 | 6 | 7 | msu_require "format" 8 | 9 | 10 | # writes to console with colors 11 | # ${1} title of the message 12 | # ${2} message to write to console 13 | # ${3} what color to use. 0 - info(blue), 1- success(green), 14 | # 2 - error(red). Defaults to "0" 15 | # ${4} file to write to. Defaults to "/dev/stdout" 16 | function write_text() { 17 | local title 18 | local text 19 | local color_index 20 | local output 21 | local outfile 22 | text=${2} 23 | color_index=${3:-0} 24 | outfile=${4:-"/dev/stdout"} 25 | if [ "${1}" ] 26 | then 27 | title="${clr_white:-''}${1}: " 28 | fi 29 | local color=${clr_blue:-''} 30 | case "${color_index}" in 31 | "1" ) 32 | color=${clr_green:-''} 33 | ;; 34 | "2" ) 35 | color=${clr_red:-''} 36 | ;; 37 | esac 38 | output="${title}${color}${text}${clr_reset:-''}" 39 | echo -e "${output}" >> "${outfile}" 40 | } 41 | 42 | 43 | # normal logging 44 | # ${@} text 45 | function log() { 46 | write_text "${LOG_TITLE:-log}" "${*}" 0 47 | } 48 | 49 | 50 | # success logging 51 | # ${@} text 52 | function success() { 53 | write_text "${LOG_TITLE:-success}" "${*}" 1 54 | } 55 | 56 | 57 | # error logging 58 | # ${@} text 59 | function error() { 60 | write_text "${LOG_TITLE:-error}" "${*}" 2 "/dev/stderr" 61 | } 62 | 63 | 64 | # put a tick 65 | # ${@} text 66 | function tick() { 67 | write_text "" "${sym_tick:-tick} ${*}" 1 68 | } 69 | 70 | 71 | # put an x 72 | # ${@} text 73 | function cross() { 74 | write_text "" "${sym_cross:-cross} ${*}" 2 75 | } 76 | 77 | 78 | # list 79 | # ${@} text 80 | function list() { 81 | write_text "" "${sym_arrow_right:-list} ${*}" 0 82 | } 83 | 84 | 85 | # asks user a question 86 | # 87 | # ${1} question to ask user 88 | # ${2} variable to assign the result to 89 | # ${3} clear/hidden (0-clear, 1-hidden) 90 | # 91 | # reference: http://stackoverflow.com/questions/1923435/how-do-i-echo-stars-when-reading-password-with-read 92 | function ask() { 93 | local clear 94 | clear=0 95 | if [ "${3}" ] 96 | then 97 | clear="${3}" 98 | fi 99 | echo -e -n " ${clr_white}${1}${clr_reset} " 100 | if [ "${clear}" -eq 0 ] 101 | then 102 | read -r "${2}" 103 | else 104 | local password='' 105 | local prompt='' 106 | declare -i charcount=0 107 | while IFS='' read -p "${prompt}" -r -s -n 1 char 108 | do 109 | if [[ "${char}" == $'\0' ]] 110 | then 111 | break 112 | fi 113 | # Backspace 114 | if [[ "${char}" == $'\177' ]] 115 | then 116 | if [ "${charcount}" -gt 0 ] ; then 117 | charcount=$((charcount-1)) 118 | prompt=$'\b \b' 119 | password="${password%?}" 120 | else 121 | prompt='' 122 | fi 123 | else 124 | charcount=$((charcount+1)) 125 | prompt='*' 126 | password="${password}${char}" 127 | fi 128 | done 129 | eval "${2}=\${password}" 130 | echo 131 | fi 132 | } 133 | 134 | 135 | # asks a yes or no question. If ${MSU_ASSUME_YES} is defined, 'yes' is 136 | # implied immediately. 137 | # 138 | # ${1} question to ask 139 | # ${2} default answer (defaults to "N") 140 | # return 0 (yes), 1 (no) 141 | function yes_no() { 142 | if [ -n "${MSU_ASSUME_YES}" ] 143 | then 144 | return 0 145 | fi 146 | 147 | local show="y|N" 148 | local answer='' 149 | local return_code=1 150 | case "${2:-''}" in 151 | "Y" | "y" ) 152 | show="Y|n" 153 | return_code=0 154 | ;; 155 | esac 156 | question="${1} (${show})?" 157 | ask "${question}" answer 158 | case "${answer}" in 159 | "Y" | "y" ) 160 | return_code=0 161 | ;; 162 | "N" | "n" ) 163 | return_code=1 164 | ;; 165 | esac 166 | return ${return_code} 167 | } 168 | -------------------------------------------------------------------------------- /lib/core.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # The Core 3 | 4 | 5 | # we expect the executable exports a variable $MSU_LIB whose 6 | # value is the path to the library holding this and other modules 7 | if [ ! "${MSU_LIB}" ] 8 | then 9 | # without this path, we can not do anything! We should exit now! 10 | echo "error: core: library path not set '${MSU_LIB}'" 11 | exit 1 12 | fi 13 | 14 | 15 | # mod vars 16 | MSU_REQUIRE_LOCK='' # lock to ensure we don't source an already-sourced file 17 | MSU_EXTERNAL_LIB="${MSU_EXTERNAL_LIB:-${HOME}/.msu}" 18 | export MSU_EXTERNAL_LIB 19 | 20 | 21 | # check dependencies 22 | function msu__check_deps() { 23 | if [ ! "${DEPS}" ] 24 | then 25 | return 26 | fi 27 | for cmd in ${DEPS} 28 | do 29 | command -v "${cmd}" > /dev/null 2>&1 || { 30 | echo "warning: \`${cmd}' not found" 31 | } 32 | done 33 | } 34 | 35 | 36 | # require a module 37 | function msu_require() { 38 | # shellcheck disable=SC2001 39 | echo "${MSU_REQUIRE_LOCK}" | grep -E ":${1}:" > /dev/null || { 40 | local fullpath 41 | fullpath=$(echo "${1}" | sed 's/\.*$//g' | sed 's/\./\//g') 42 | # internal modules have precedence 43 | # shellcheck source=/dev/null 44 | source "${MSU_LIB}/${fullpath}.sh" > /dev/null 2>&1 || { 45 | # external libs 46 | # shellcheck source=/dev/null 47 | source "${MSU_EXTERNAL_LIB}/${fullpath}.sh" > /dev/null 2>&1 || { 48 | echo "error: require: failed to load module '$(echo "${1}" | sed 's/\.$//g')'" 49 | exit 1 50 | } 51 | } 52 | msu__check_deps 53 | MSU_REQUIRE_LOCK=":${1}:${MSU_REQUIRE_LOCK}" 54 | } 55 | } 56 | 57 | 58 | # run a single function 59 | function msu_run() { 60 | local module 61 | local func 62 | module=$(echo "${1}" | grep -Eo ".*\\.") 63 | func=$(echo "${1}" | grep -Eo "\\.[^.]+$" | cut -b 2-) 64 | msu_require "${module}" 65 | if [ "${func}" ] 66 | then 67 | ${func} "${@:2}" 68 | else 69 | echo "error: run: can not find function '${func}'" 70 | fi 71 | } 72 | 73 | 74 | # execute a file 75 | function msu_execute() { 76 | # shellcheck source=/dev/null 77 | source "${1}" 78 | } 79 | -------------------------------------------------------------------------------- /lib/core_utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # useful utilities 3 | 4 | # shellcheck disable=2181 5 | 6 | 7 | # modules 8 | msu_require "console" 9 | msu_require "format" 10 | msu_require "metadata" 11 | 12 | 13 | # module variables 14 | { 15 | # shellcheck disable=2034 16 | DEPS="git" 17 | } 18 | 19 | # upgrade myself 20 | function upgrade() { 21 | log "upgrading myself" 22 | LIB=${MSU_INSTALL_LIB:-$(dirname "${MSU_LIB}")} 23 | BIN=${MSU_INSTALL_BIN:-$(dirname "$(command -v msu)")} 24 | MAN=${MSU_INSTALL_MAN} 25 | if [ ! "${MAN}" ] 26 | then 27 | for path in ${MANPATH//\:/\n} 28 | do 29 | if [ -f "${path}/man1/msu.1" ] 30 | then 31 | MAN=${path} 32 | break 33 | fi 34 | done 35 | fi 36 | 37 | if [ "${1}" ] 38 | then 39 | head=$(echo "${1}" | tr '[:upper:]' '[:lower:]') 40 | case "${head}" in 41 | "head" ) 42 | wget -qO- http://git.io/vTE0s | LIB="${LIB}" BIN="${BIN}" MAN="${MAN}" bash 43 | ;; 44 | * ) 45 | version="${1}" 46 | ;; 47 | esac 48 | [ ! "${version}" ] && return 49 | fi 50 | 51 | # upgrade using tarball 52 | pushd /tmp > /dev/null || return 1 53 | [ ! "${version}" ] && { 54 | version=$(python "${MSU_LIB}/get-latest-version.py" "${MSU_VERSION}") 55 | status=$? 56 | case "${status}" in 57 | 1 ) 58 | log "you have the latest version" 59 | ;; 60 | 2 ) 61 | error "required python dependencies are missing" 62 | ;; 63 | 3 ) 64 | error "network request error" 65 | ;; 66 | esac 67 | [ "${status}" -ne 0 ] && exit ${status} 68 | } 69 | wget "https://github.com/GochoMugo/msu/releases/download/v${version}/msu-${version}.tar.gz" -q > /dev/null 70 | tar xvf "msu-${version}.tar.gz" > /dev/null 2>&1 71 | cd "msu-${version}" || { 72 | error "could not \`cd' into directory with extracted contents" 73 | return 1 74 | } 75 | LIB="${LIB}" BIN="${BIN}" MAN="${MAN}" ./install.sh 76 | popd > /dev/null || return 1 77 | } 78 | 79 | 80 | # install module(s) 81 | # ${*} module names 82 | function install() { 83 | mkdir -p "${MSU_EXTERNAL_LIB}" 84 | for dir in "${@}" 85 | do 86 | local module_name 87 | local remote_mark 88 | remote_mark=$(echo "${dir}" | grep -Eo "[a-zA-Z0-9]+:" | grep -Eo "[^:]*") 89 | if [ "${remote_mark}" ] ; then 90 | # requires cloning 91 | local tmpdir 92 | local shorthand 93 | local version 94 | local url 95 | tmpdir="/tmp/.msu.clones" 96 | shorthand=$(echo "${dir}" | sed --regexp-extended --expression=s/^.+:\([^\#]+\).*/\\1/) 97 | version=$(echo "${dir}" | grep '#' | sed --regexp-extended --expression=s/.+\#\(.+\)$/\\1/) 98 | remote_mark=$(echo "${remote_mark}" | tr '[:upper:]' '[:lower:]') 99 | case "${remote_mark}" in 100 | "gh" ) 101 | url="https://github.com/${shorthand}.git" 102 | ;; 103 | "ghs" ) 104 | url="git@github.com:${shorthand}.git" 105 | ;; 106 | "bt" ) 107 | url="https://bitbucket.org/${shorthand}.git" 108 | ;; 109 | "bts" ) 110 | url="git@bitbucket.org:${shorthand}.git" 111 | ;; 112 | "gl" ) 113 | url="https://gitlab.com/${shorthand}.git" 114 | ;; 115 | "gls" ) 116 | url="git@gitlab.com:${shorthand}.git" 117 | ;; 118 | esac 119 | rm -rf "${tmpdir}" 120 | mkdir -p "${tmpdir}" 121 | pushd "${tmpdir}" > /dev/null || return 1 122 | git clone --branch="${version:-master}" --depth=1 --quiet "${url}" 123 | if [ ! $? ] 124 | then 125 | cross "${shorthand}" 126 | continue 127 | fi 128 | module_name=$(echo "${shorthand}" | grep -Eo '\/.*$' | cut -b 2-) 129 | install "${module_name}" 130 | popd > /dev/null || return 1 131 | else 132 | # simple copying 133 | # for faster development, we may be in a module's repo and want to 134 | # install it without leaving the directory (or using $PWD) 135 | if [ "${dir}" == "." ] ; then dir="${PWD}" ; fi 136 | module_name="$(basename "${dir}")" 137 | rm -rf "${MSU_EXTERNAL_LIB:-'.'}/${module_name}" 138 | cp -rf "${module_name}" "${MSU_EXTERNAL_LIB}" > /dev/null 139 | if [ $? -eq 0 ] ; then 140 | generate_metadata "${module_name}" 141 | tick "${module_name}" 142 | else 143 | cross "${module_name}" 144 | fi 145 | fi 146 | done 147 | } 148 | 149 | 150 | # generate metadata for an installed module 151 | function generate_metadata() { 152 | pushd "${MSU_EXTERNAL_LIB}/${1}" > /dev/null || return 1 153 | if [ ! -d .git ] || [ "$(git rev-list --all --count 2> /dev/null || echo 0)" -eq 0 ] 154 | then 155 | error "can not generate metadata without at least one git commit" 156 | return 1 157 | fi 158 | { 159 | echo "author=$(git show -s --format='%an <%ae>')" 160 | echo "build=$(git rev-parse HEAD)" 161 | echo "date=$(git show -s --format=%ci)" 162 | } >> metadata.sh 163 | popd > /dev/null || return 1 164 | } 165 | 166 | 167 | # show metadata for an installed module 168 | function show_metadata() { 169 | local metadata_file 170 | metadata_file="${MSU_EXTERNAL_LIB}/${1}/metadata.sh" 171 | if [ ! -f "${metadata_file}" ] 172 | then 173 | error "metadata for '${1}' not found" 174 | return 1 175 | fi 176 | local metadata 177 | metadata=$(cat "${metadata_file}") 178 | function echo_value() { 179 | local value 180 | value=$(echo "${metadata}" | grep "${1}" | grep -Eo '[!=].*$' | cut -b 2-) 181 | echo -e " ${1}\\t${value}" 182 | } 183 | echo -e " ${clr_white:-''}${1}${clr_reset:-''}" 184 | echo_value "author" 185 | echo_value "build" 186 | echo_value "date" 187 | } 188 | 189 | 190 | # uninstall module(s) 191 | function uninstall() { 192 | for dir in "$@" 193 | do 194 | path="${MSU_EXTERNAL_LIB}/${dir}" 195 | if [ -e "${path}" ] 196 | then 197 | rm -rf "${path}" > /dev/null 198 | if [ $? ] ; then 199 | tick "${dir}" 200 | else 201 | cross "${dir} - failed" 202 | fi 203 | else 204 | tick "${dir} (not installed)" 205 | fi 206 | done 207 | } 208 | 209 | 210 | # get to install/uninstall from a list 211 | # ${1} - install/uninstall function 212 | # ${2} - path to file 213 | function get_from_list() { 214 | # ensure the file exists, otherwise the `cat` command will hang 215 | if [ ! -f "${2}" ] 216 | then 217 | error "file does NOT exist" 218 | return 1 219 | fi 220 | # read the list into a variable 221 | local mods 222 | mods="$(cat "${2}")" 223 | for mod in ${mods} 224 | do 225 | ${1} "${mod}" 226 | done 227 | } 228 | 229 | 230 | # install many 231 | function install_from_list() { 232 | get_from_list install "${1}" 233 | } 234 | 235 | 236 | # uninstall many 237 | function uninstall_from_list() { 238 | get_from_list uninstall "${1}" 239 | } 240 | 241 | 242 | # checking if command is available on the system. 243 | # ${1} - command to check for. 244 | function has_command() { 245 | command -v "${1}" > /dev/null 2>&1 246 | } 247 | 248 | 249 | # checking if script is run as root/superuser. 250 | function is_superuser() { 251 | [[ "$(id -u)" == "0" ]] 252 | } 253 | 254 | 255 | # listing installed modules. 256 | # ${1} - toggle option e.g. '-i', '--internal', '-e', '--external' 257 | function list_modules() { 258 | local internal 259 | local external 260 | internal=true 261 | external=true 262 | 263 | # processing switch 264 | case "${1}" in 265 | "-i" | "--internal" ) 266 | external=false 267 | ;; 268 | "-e" | "--external" ) 269 | internal=false 270 | ;; 271 | esac 272 | 273 | # output function. 274 | function output() { 275 | for mod in ${1} 276 | do 277 | # ignore "aliases.sh" 278 | if [[ "${mod}" != "aliases.sh" ]] 279 | then 280 | list "${mod//\.sh/}" 281 | fi 282 | done 283 | } 284 | 285 | # list internal modules, if allowed 286 | [[ "${internal}" == "true" ]] && { 287 | echo -e "\\n${clr_white}internal modules${clr_reset}" 288 | # shellcheck disable=SC2010 289 | output "$(ls "${MSU_LIB}" | grep -E "\\.sh$")" 290 | } 291 | 292 | # list external modules, if allowed 293 | [[ "${external}" == "true" ]] && { 294 | echo -e "\\n${clr_white}external modules${clr_reset}" 295 | if [ -d "${MSU_EXTERNAL_LIB}" ] 296 | then 297 | output "$(ls "${MSU_EXTERNAL_LIB}")" 298 | else 299 | echo -e "${clr_red:-}no modules found${clr_reset}" 300 | fi 301 | } 302 | } 303 | 304 | 305 | # nuke msu entirely 306 | function nuke() { 307 | # just ask to ensure we really want to nuke 308 | log "you are about to remove the msu executable, internal and external libraries" 309 | log "this action will change your ~/.bashrc! You may want to back it up in case shit happens!" 310 | yes_no "I want to nuke msu" || return 1 311 | 312 | local nuke_script="/tmp/nuke_msu" 313 | { 314 | echo "echo - removing load string from ~/.bashrc" 315 | echo "cp ~/.bashrc /tmp/bashrc.msu" 316 | echo "grep -v '${MSU_INSTALL_LOAD_STRING}' /tmp/bashrc.msu > ~/.bashrc" 317 | echo "cp ~/.bashrc /tmp/bashrc.msu" 318 | echo "grep -v '# added by msu' /tmp/bashrc.msu > ~/.bashrc" 319 | echo "rm /tmp/bashrc.msu" 320 | echo "echo - removing external libraries" 321 | echo "rm -rf '${MSU_EXTERNAL_LIB}'" 322 | echo "echo - removing internal libraries" 323 | echo "rm -rf '${MSU_LIB}'" 324 | echo "echo - removing manpages" 325 | echo "rm -rf ${MSU_INSTALL_MAN}/**/msu*" 326 | echo "echo - removing executable" 327 | echo "rm -f '${MSU_EXE}'" 328 | echo "echo - extra removal actions" 329 | echo "rm -rf /tmp/msu" # we need to nuke the location of installation clone 330 | echo "rm -rf /tmp/.msu.clones" 331 | echo "echo - removing this nuke script" 332 | echo "rm -f '${nuke_script}'" 333 | echo "echo !! Just nuked msu !!" 334 | } > "${nuke_script}" 335 | # running the nuke script 336 | bash "${nuke_script}" 337 | } 338 | -------------------------------------------------------------------------------- /lib/format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # formatting 3 | # 4 | # Copyright (c) 2015 GochoMugo 5 | 6 | # shellcheck disable=SC2034 7 | 8 | { # we need this for shellcheck to disable error 9 | 10 | # formatting text 11 | txt_underline=$(tput smul) 12 | txt_nounderline=$(tput rmul) 13 | txt_bold=$(tput bold) 14 | txt_normal=$(tput sgr0) 15 | 16 | 17 | # colors 18 | clr_blue="\\033[0;34m" 19 | clr_green="\\033[0;32m" 20 | clr_red="\\033[0;31m" 21 | clr_reset="\\e[0m" 22 | clr_white="\\033[1;37m" 23 | 24 | 25 | # symbols 26 | sym_tick="✓" 27 | sym_cross="✗" 28 | sym_smile="☺" 29 | sym_frown="☹" 30 | sym_danger="☠" 31 | sym_note="☛" 32 | sym_peace="✌" 33 | sym_arrow_right="⇒" 34 | } 35 | -------------------------------------------------------------------------------- /lib/get-latest-version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # we are trying to construct a valid URL to fetch the tarball 3 | 4 | # modules 5 | import sys 6 | import traceback 7 | try: 8 | import requests 9 | import semver 10 | except: 11 | # exit with status code (2) - missing dependencies 12 | traceback.print_exc() 13 | sys.exit(2) 14 | 15 | # module variables 16 | version = sys.argv[1] if len(sys.argv) is 2 else "0.0.0" 17 | url = "https://api.github.com/repos/GochoMugo/msu/releases/latest" 18 | 19 | # make the API request 20 | try: 21 | r = requests.get(url) 22 | # ensure we made a successful request 23 | r.raise_for_status() 24 | except: 25 | # exit with status code (3) - request error 26 | sys.exit(3) 27 | 28 | # the response data 29 | res = r.json() 30 | 31 | # check if it is a new version 32 | new_version = res['tag_name'].lstrip('v') 33 | is_newer = semver.compare(version, new_version) is -1 34 | 35 | # exit with code (1) - no newer version 36 | if is_newer is not True: 37 | sys.exit(1) 38 | 39 | # print the latest version 40 | print new_version 41 | -------------------------------------------------------------------------------- /lib/load.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Loading msu. This is usually 'source'd in '~/.bashrc' 3 | # (or equivalent) 4 | # 5 | # Copyright (c) 2015-2016 GochoMugo 6 | 7 | 8 | # shellcheck disable=SC1091 9 | 10 | # loading aliases (into env) 11 | . msu require aliases 12 | 13 | -------------------------------------------------------------------------------- /lib/metadata.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # project metadata 3 | # 4 | # Copyright (c) 2015 GochoMugo 5 | 6 | 7 | # shellcheck disable=SC2034 8 | { 9 | MSU_AUTHOR_NAME="GochoMugo" 10 | MSU_AUTHOR_EMAIL="mugo@forfuture.co.ke" 11 | MSU_VERSION="0.3.0" 12 | } 13 | -------------------------------------------------------------------------------- /lib/msu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # msu (my-shell-utils) 3 | # 4 | # Copyright (c) 2015 GochoMugo 5 | 6 | 7 | #set -e errexit 8 | 9 | 10 | MSU_EXE="${BASH_SOURCE[0]}" 11 | MSU_REAL_EXE=$(readlink -f "${MSU_EXE}") 12 | MSU_LIB=$(dirname "${MSU_REAL_EXE}") # directory holding our library 13 | export MSU_EXE 14 | export MSU_LIB 15 | 16 | 17 | # modules 18 | # shellcheck source=lib/core.sh 19 | source "${MSU_LIB}/core.sh" # so LOW-LEVEL 20 | msu_require "metadata" # how you like me now? 21 | 22 | 23 | # warn user, if we are running as root/superuser. 24 | msu_run core_utils.is_superuser && { 25 | echo 26 | echo " !! you are running as SUPERUSER !! " 27 | echo " !! with much power, comes great responsibility !! " 28 | echo " !! you have been WARNED! !! " 29 | echo 30 | } 31 | 32 | 33 | # parse command line arguments 34 | case "${1:-''}" in 35 | "re" | "require" ) 36 | msu_require "${2:-''}" 37 | ;; 38 | "r" | "run" ) 39 | msu_run "${@:2}" 40 | ;; 41 | "x" | "execute" ) 42 | msu_execute "${2:-''}" "${@:3}" 43 | ;; 44 | "i" | "install" ) 45 | msu_run core_utils.install "${@:2}" 46 | ;; 47 | "im" | "install-many" ) 48 | msu_run core_utils.install_from_list "${2}" 49 | ;; 50 | "u" | "uninstall" ) 51 | msu_run core_utils.uninstall "${@:2}" 52 | ;; 53 | "um" | "uninstall-many" ) 54 | msu_run core_utils.uninstall_from_list "${2}" 55 | ;; 56 | "up" | "upgrade" ) 57 | msu_run core_utils.upgrade "${2}" 58 | ;; 59 | "ls" | "list" ) 60 | msu_run core_utils.list_modules "${2}" 61 | ;; 62 | "nk" | "nuke" ) 63 | msu_run core_utils.nuke 64 | ;; 65 | "h" | "help" ) 66 | echo 67 | echo " msu by ${MSU_AUTHOR_NAME} <${MSU_AUTHOR_EMAIL}>" 68 | echo 69 | echo " usage:" 70 | echo " msu require|install|uninstall ..." 71 | echo " msu execute|install-many|uninstall-many " 72 | echo " msu run ." 73 | echo " msu list [scope]" 74 | echo " msu upgrade [version]" 75 | echo " msu version [mod]" 76 | echo 77 | echo " commands:" 78 | echo " re | require require the library module " 79 | echo " r | run run the function in module " 80 | echo " x | execute execute file at the path " 81 | echo " i | install install the module(s) ..." 82 | echo " im | install-many install from module-list at path " 83 | echo " u | uninstall uninstall the module(s) ..." 84 | echo " um | uninstall-many uninstall from module-list at path " 85 | echo " ls | list list installed modules" 86 | echo " up | upgrade upgrade to the latest version" 87 | echo " nk | nuke nuke msu entirely" 88 | echo " h | help show this help information" 89 | echo " v | version show version information of module [mod] or msu itself" 90 | echo 91 | echo " for more help information, see msu(1)" 92 | echo 93 | echo " see https://github.com/GochoMugo/msu/issues for bug reporting" 94 | echo " and feature requests" 95 | echo 96 | ;; 97 | "v" | "version" ) 98 | if [ "${2}" ] 99 | then 100 | msu_run core_utils.show_metadata "${2}" 101 | else 102 | echo " version ${MSU_VERSION}" 103 | echo " build ${MSU_BUILD_HASH:-?}" 104 | echo " date ${MSU_BUILD_DATE:-?}" 105 | fi 106 | ;; 107 | * ) 108 | # maybe, we are being used in a shebang e.g. #!/usr/bin/env msu 109 | if [ "${1}" ] 110 | then 111 | FILE="$(readlink -f "${1}" 2> /dev/null)" 112 | if [ -r "${FILE}" ] 113 | then 114 | msu_execute "${FILE}" "${@:2}" 115 | fi 116 | fi 117 | # otherwise, we just ignore it! 118 | ;; 119 | esac 120 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "msu", 3 | "version": "0.0.0", 4 | "description": "A small Shell framework that makes writing bash scripts less sucky", 5 | "homepage": "https://github.com/GochoMugo/msu", 6 | "bugs": { 7 | "url": "https://github.com/GochoMugo/msu/issues" 8 | }, 9 | "license": "MIT", 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/GochoMugo/msu.git" 13 | }, 14 | "author": { 15 | "name": "GochoMugo", 16 | "email": "mugo@forfuture.co.ke", 17 | "url": "https://gochomugo.github.io/" 18 | }, 19 | "main": "lib/msu.sh", 20 | "bin": { 21 | "msu": "bin/msu" 22 | }, 23 | "keywords": [ 24 | "bash", 25 | "shell", 26 | "framework", 27 | "scripts" 28 | ], 29 | "engines": { 30 | "bash": "4.3.11" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # making a new release 3 | 4 | 5 | # modules 6 | msu_require "console" 7 | 8 | 9 | # module variables 10 | { 11 | # shellcheck disable=SC2034 12 | DEPS="xargs hub" 13 | } 14 | RELEASE="" 15 | 16 | 17 | log "prompting for new version number" 18 | MSU_VERSION="" 19 | ask "New version number" MSU_VERSION 20 | 21 | 22 | log "mark that we are in RELEASE mode using the version number in \${RELEASE}" 23 | RELEASE=${MSU_VERSION} 24 | export RELEASE 25 | 26 | 27 | log "creating directory for releases" 28 | RELEASE_DIR="releases/v${MSU_VERSION}" 29 | RELEASE_TARBALL="releases/msu-${MSU_VERSION}.tar.gz" 30 | rm --force --recursive "${RELEASE_DIR}" "${RELEASE_TARBALL}" 31 | mkdir --parents "${RELEASE_DIR}" 32 | 33 | 34 | log "copying the contents of the working directory" 35 | release_contents=( 36 | dist/docs/ 37 | lib/ 38 | install.sh 39 | CHANGELOG.md 40 | LICENSE 41 | README.md 42 | ) 43 | for release_content in "${release_contents[@]}" ; do 44 | mkdir --parents "${RELEASE_DIR}/$(dirname "${release_content}")" 45 | cp --force --parents --recursive "${release_content}" "${RELEASE_DIR}" 46 | done 47 | 48 | 49 | log "generating metadata" 50 | MSU_BUILD_HASH=$(git rev-parse HEAD) 51 | MSU_BUILD_DATE=$(git show -s --format=%ci "${MSU_BUILD_HASH}") 52 | { 53 | echo "MSU_BUILD_HASH='${MSU_BUILD_HASH}'" 54 | echo "MSU_BUILD_DATE='${MSU_BUILD_DATE}'" 55 | } >> "${RELEASE_DIR}"/lib/metadata.sh 56 | 57 | 58 | log "creating a tarball of the release" 59 | tar --create --gzip --file "${RELEASE_TARBALL}" "${RELEASE_DIR}/" 60 | 61 | 62 | log "creating a new github release" 63 | hub release create -a ${RELEASE_TARBALL} v${MSU_VERSION} 64 | 65 | 66 | success "New MSU release: v${MSU_VERSION}" 67 | -------------------------------------------------------------------------------- /test/misc/test.docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Copyright (c) 2015-2016 Gocho Mugo 4 | # Licensed under the MIT License 5 | 6 | # ensure manpages were created 7 | txts="$(ls docs/man/**/*.txt)" 8 | for txt in ${txts} 9 | do 10 | manpage="dist/$(echo "${txt}" | sed 's/\.txt//')" 11 | [ -f "${manpage}" ] || { 12 | echo "ERROR: manpage missing: ${manpage} for ${txt}" 13 | exit 1 14 | } 15 | done 16 | -------------------------------------------------------------------------------- /test/test.aliases.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # tests against lib/aliases.sh 3 | 4 | 5 | MSU_EXTERNAL_LIB="${BATS_TMPDIR:-/tmp}/test-aliases" 6 | 7 | 8 | function setup() { 9 | # shellcheck disable=SC2015 10 | mkdir -p "${MSU_EXTERNAL_LIB}" 11 | } 12 | 13 | 14 | function teardown() { 15 | rm -rf "${MSU_EXTERNAL_LIB:-/tmp}/*" 16 | } 17 | 18 | 19 | @test "aliases.sh loads aliases into the current environment" { 20 | . lib/aliases.sh 21 | alias x 22 | alias msu.reload 23 | } 24 | 25 | 26 | @test "aliases.sh loads aliases from external modules" { 27 | local mod="${MSU_EXTERNAL_LIB}/aliases" 28 | mkdir -p "${mod}" 29 | echo "alias humansshouldriseup='echo external'" > "${mod}/aliases.sh" 30 | . lib/aliases.sh 31 | alias humansshouldriseup | grep "external" 32 | } 33 | 34 | 35 | @test "\${MSU_EXTERNAL_LIB}/aliases.sh overides all" { 36 | local alias_override="${MSU_EXTERNAL_LIB}/aliases.sh" 37 | local mod="${MSU_EXTERNAL_LIB}/overide" 38 | mkdir -p "${mod}" 39 | echo "alias bedaring='echo override'" > "${alias_override}" 40 | echo "alias bedaring='echo external'" > "${mod}/aliases.sh" 41 | . lib/aliases.sh 42 | alias bedaring | grep "override" 43 | } 44 | -------------------------------------------------------------------------------- /test/test.core.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | # tests against ./lib/core.sh 3 | 4 | 5 | MSU_LIB="${PWD}/lib" 6 | 7 | 8 | source ./lib/core.sh 9 | 10 | 11 | function setup() { 12 | MSU_REQUIRE_LOCK='' 13 | MSU_EXTERNAL_LIB_OLD="${MSU_EXTERNAL_LIB}" 14 | unset MSU_EXTERNAL_LIB 15 | source lib/core.sh 16 | } 17 | 18 | 19 | function teardown() { 20 | rm -rf lib/tmp_* 21 | MSU_EXTERNAL_LIB="${MSU_EXTERNAL_LIB_OLD}" 22 | } 23 | 24 | 25 | @test "defaults to \${HOME}/.msu if \${MSU_EXTERNAL_LIB} is not set" { 26 | unset MSU_EXTERNAL_LIB 27 | source lib/core.sh 28 | [ "${MSU_EXTERNAL_LIB}" == "${HOME}/.msu" ] 29 | } 30 | 31 | 32 | @test "uses the environment variable \${MSU_EXTERNAL_LIB} if set" { 33 | local libpath="${BATS_TMPDIR:-'.'}/var-testing" 34 | MSU_EXTERNAL_LIB="${libpath}" 35 | . lib/core.sh 36 | [ "${MSU_EXTERNAL_LIB}" == "${libpath}" ] 37 | } 38 | 39 | 40 | @test "check_deps shows warning if a dependency is not found" { 41 | DEPS="missing-cmd" msu__check_deps | grep "not found" 42 | } 43 | 44 | 45 | @test "\`check_deps' exits silently if all dependences exist" { 46 | [ $(DEPS="echo" msu__check_deps | wc -w) -eq 0 ] 47 | } 48 | 49 | 50 | @test "\`check_deps' exits silently if \${DEPS} is not defined" { 51 | [ $(msu__check_deps | wc -w) -eq 0 ] 52 | } 53 | 54 | 55 | @test "\`require' echos error and exits with a non-zero status if module is missing" { 56 | run msu_require missing 57 | [ "${status}" -eq 1 ] 58 | echo "${output}" | grep "error" 59 | } 60 | 61 | 62 | @test "\`require' loads a module once" { 63 | tmp_mod=lib/tmp_once.sh 64 | echo "echo red" > "${tmp_mod}" 65 | msu_require tmp_once 66 | rm "${tmp_mod}" 67 | run msu_require tmp_once 68 | [ "${status}" -eq 0 ] 69 | } 70 | 71 | 72 | @test "\`require' checks deps automatically" { 73 | tmp_mod=lib/tmp_deps.sh 74 | echo "DEPS=\"echo missing\"" > "${tmp_mod}" 75 | msu_require tmp_deps | grep "not found" 76 | } 77 | 78 | 79 | @test "\`require' loads scripts in nested directories" { 80 | tmp_dir=lib/tmp_nest/another/one/ 81 | tmp_mod="${tmp_dir}/sample.sh" 82 | mkdir -p "${tmp_dir}" 83 | echo "" > "${tmp_mod}" 84 | run msu_require tmp_nest.another.one.sample 85 | [ "${status}" -eq 0 ] 86 | } 87 | 88 | 89 | @test "\`require' loads external modules" { 90 | tmp_dir=~/.msu/gocho-msu-test/ 91 | tmp_mod="${tmp_dir}"/sample.sh 92 | mkdir -p "${tmp_dir}" 93 | echo "" > "${tmp_mod}" 94 | run msu_require gocho-msu-test.sample 95 | [ "${status}" -eq 0 ] 96 | rm -r "${tmp_dir}" 97 | } 98 | 99 | 100 | @test "\`run' runs a function in a module" { 101 | tmp_mod=lib/tmp_run.sh 102 | echo -e "function hey() { \n echo \${1} \n }" > "${tmp_mod}" 103 | run msu_run tmp_run.hey gocho 104 | echo "${output}" 105 | [ "${status}" -eq 0 ] 106 | echo "${output}" | grep "gocho" 107 | } 108 | -------------------------------------------------------------------------------- /test/test.core_utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | # tests against ./lib/core_utils.sh 3 | 4 | 5 | BASHRC_TMP=~/.bashrc.msu 6 | 7 | cp ~/.bashrc ~/.bashrc~ # backup 8 | 9 | MSU_LIB="${PWD}"/lib 10 | source lib/core.sh 11 | source lib/core_utils.sh 12 | source lib/format.sh 13 | 14 | 15 | function setup() { 16 | mv ~/.bashrc "${BASHRC_TMP}" 17 | touch ~/.bashrc 18 | } 19 | 20 | 21 | function teardown() { 22 | mv "${BASHRC_TMP}" ~/.bashrc 23 | rm -rf /tmp/msu 24 | } 25 | 26 | 27 | @test "gets the \${MSU_EXTERNAL_LIB} readily set" { 28 | [ ! -z "${MSU_EXTERNAL_LIB}" ] 29 | } 30 | 31 | 32 | @test "\`upgrade' runs upgrade" { 33 | skip 34 | } 35 | 36 | 37 | @test "\`install' installs one or more modules" { 38 | MSU_EXTERNAL_LIB="${BATS_TMPDIR:-'.'}"/install 39 | source lib/core_utils.sh 40 | mod1="${BATS_TMPDIR}/mod1" 41 | mod2="${BATS_TMPDIR}/mod2" 42 | mkdir -p "${mod1}" "${mod2}" 43 | rm -rf "${MSU_EXTERNAL_LIB}/mod1" "${MSU_EXTERNAL_LIB}/mod2" 44 | run install "${mod1}" "${mod2}" 45 | [ "${status}" -eq 0 ] 46 | echo "${output}" | grep "${sym_tick}" 47 | [ -d "${MSU_EXTERNAL_LIB}"/mod1 ] 48 | [ -d "${MSU_EXTERNAL_LIB}"/mod2 ] 49 | } 50 | 51 | 52 | @test "\`install' installs from github" { 53 | MSU_EXTERNAL_LIB="${BATS_TMPDIR}/gh" 54 | source lib/core_utils.sh 55 | samplemodule="GH:GochoMugo/msu" 56 | run install "${samplemodule}" 57 | [ "${status}" -eq 0 ] 58 | echo "${output}" | grep "${sym_tick}" 59 | [ -d "${MSU_EXTERNAL_LIB}/msu" ] 60 | } 61 | 62 | 63 | @test "\`install' installs from bitbucket" { 64 | MSU_EXTERNAL_LIB="${BATS_TMPDIR}/bt" 65 | source lib/core_utils.sh 66 | samplemodule="BT:GochoMugo/msu-test" 67 | run install "${samplemodule}" 68 | [ "${status}" -eq 0 ] 69 | echo "${output}" | grep "${sym_tick}" 70 | [ -d "${MSU_EXTERNAL_LIB}/msu-test" ] 71 | } 72 | 73 | 74 | @test "\`install' installs from gitlab" { 75 | MSU_EXTERNAL_LIB="${BATS_TMPDIR}/gl" 76 | source lib/core_utils.sh 77 | samplemodule="GL:GochoMugo/msu-test" 78 | run install "${samplemodule}" 79 | [ "${status}" -eq 0 ] 80 | echo "${output}" | grep "${sym_tick}" 81 | [ -d "${MSU_EXTERNAL_LIB}/msu-test" ] 82 | } 83 | 84 | 85 | @test "\`install' supports versions (git tags)" { 86 | MSU_EXTERNAL_LIB="${BATS_TMPDIR}/gl-version" 87 | source lib/core_utils.sh 88 | samplemodule="GL:GochoMugo/msu-test#v0.0.0" 89 | run install "${samplemodule}" 90 | [ "${status}" -eq 0 ] 91 | echo "${output}" | grep "${sym_tick}" 92 | [ -d "${MSU_EXTERNAL_LIB}/msu-test" ] 93 | } 94 | 95 | 96 | @test "\`install_from_list' installs from a list in a file" { 97 | MSU_EXTERNAL_LIB="${BATS_TMPDIR}/install-many" 98 | source lib/core_utils.sh 99 | listpath="${BATS_TMPDIR}/list.install" 100 | echo "GH:GochoMugo/msu" > ${listpath} 101 | run install_from_list "${listpath}" 102 | [ "${status}" -eq 0 ] 103 | echo "${output}" | grep "${sym_tick}" 104 | [ -d "${MSU_EXTERNAL_LIB}/msu" ] 105 | } 106 | 107 | 108 | @test "\`uninstall' uninstalls one or more modules" { 109 | MSU_EXTERNAL_LIB="${BATS_TMPDIR}/uninstall" 110 | source lib/core_utils.sh 111 | mkdir -p "${MSU_EXTERNAL_LIB}/mod1" "${MSU_EXTERNAL_LIB}/mod2" 112 | run uninstall mod1 mod2 113 | [ "${status}" -eq 0 ] 114 | echo "${output}" | grep "${sym_tick}" 115 | [ ! -d "${MSU_EXTERNAL_LIB}/mod1" ] 116 | [ ! -d "${MSU_EXTERNAL_LIB}/mod2" ] 117 | } 118 | 119 | 120 | @test "\`uninstall_from_list' uninstall from a list in a file" { 121 | MSU_EXTERNAL_LIB="${BATS_TMPDIR}/uninstall-many" 122 | source lib/core_utils.sh 123 | mkdir -p "${MSU_EXTERNAL_LIB}/mod1" 124 | listpath="${BATS_TMPDIR}/list.uninstall" 125 | echo "mod1" > ${listpath} 126 | run uninstall_from_list ${listpath} 127 | [ "${status}" -eq 0 ] 128 | echo "${output}" | grep "${sym_tick}" 129 | [ ! -d "${MSU_EXTERNAL_LIB}/mod1" ] 130 | } 131 | 132 | 133 | function new_mod() { 134 | rm -rf "${1}" 135 | mkdir -p "${1}" 136 | pushd "${1}" > /dev/null 137 | git init 138 | git config --local user.email "mugo@forfuture.co.ke" 139 | git config --local user.name "GochoMugo" 140 | touch first 141 | git add first 142 | git commit -m "Init" 143 | popd > /dev/null 144 | } 145 | 146 | 147 | @test "\`install' through \`generate_metadata' generates module metadata" { 148 | MSU_EXTERNAL_LIB="${BATS_TMPDIR}"/gen-metadata 149 | local sample_module="${BATS_TMPDIR}"/sample-metadata 150 | source lib/core_utils.sh 151 | new_mod "${sample_module}" 152 | run install "${sample_module}" 153 | [ "${status}" -eq 0 ] 154 | [ -f "${MSU_EXTERNAL_LIB}/sample-metadata/metadata.sh" ] 155 | } 156 | 157 | 158 | @test "\`generate_metadata' ignores if there are no git commits" { 159 | MSU_EXTERNAL_LIB="${BATS_TMPDIR}/gen-md" 160 | source lib/core_utils.sh 161 | mkdir -p "${MSU_EXTERNAL_LIB}/sample" 162 | ! generate_metadata "sample" 163 | cd "${MSU_EXTERNAL_LIB}/sample" 164 | git init 165 | cd - 166 | ! generate_metadata "sample" 167 | } 168 | 169 | 170 | @test "\`show_metadata' shows module metadata" { 171 | MSU_EXTERNAL_LIB="${BATS_TMPDIR}/show-metadata" 172 | local sample_module="${BATS_TMPDIR}/show-me-some" 173 | source lib/core_utils.sh 174 | new_mod "${sample_module}" 175 | run install "${sample_module}" 176 | run show_metadata "show-me-some" 177 | [ "${status}" -eq 0 ] 178 | echo "${output}" | grep "author" | grep "GochoMugo" # author 179 | echo "${output}" | grep "build" # build hash 180 | echo "${output}" | grep "date" # date 181 | } 182 | 183 | 184 | @test "\`has_command' checks if command is available" { 185 | has_command "cat" # I can be almost certain that `cat` is available. 186 | ! has_command "gochomugo" # last time i checked i didn't create the program. 187 | } 188 | 189 | 190 | @test "\`is_superuser' checks if script is run as superuser" { 191 | ! is_superuser 192 | sudo ./lib/msu.sh run core_utils.is_superuser 193 | } 194 | 195 | 196 | @test "\`list_modules' lists installed modules" { 197 | MSU_EXTERNAL_LIB="${BATS_TMPDIR}/list-modules" 198 | local sample_module="${BATS_TMPDIR}/a-stupid-module-ofcos" 199 | source lib/core_utils.sh 200 | new_mod "${sample_module}" 201 | run install "${sample_module}" 202 | run list_modules 203 | echo "${output}" | grep "internal modules" 204 | echo "${output}" | grep "console" 205 | ! echo "${output}" | grep "get-latest-version.py" 206 | echo "${output}" | grep "external modules" 207 | echo "${output}" | grep "a-stupid-module-ofcos" 208 | run list_modules --internal 209 | ! echo "${output}" | grep "external modules" 210 | run list_modules --external 211 | ! echo "${output}" | grep "internal modules" 212 | } 213 | 214 | 215 | @test "\`list_modules' does not error if no external modules exist" { 216 | MSU_EXTERNAL_LIB="${BATS_TMPDIR}/no-external-mods" 217 | [ ! -d "${MSU_EXTERNAL_LIB}" ] 218 | run list_modules 219 | echo "${output}" | grep "no modules found" 220 | } 221 | 222 | 223 | @test "\`nuke' nukes msu entirely" { 224 | LIB="${BATS_TMPDIR}/nuke-lib" 225 | BIN="${BATS_TMPDIR}/nuke-bin" 226 | MAN="${BATS_TMPDIR}/nuke-man" 227 | LIB="${LIB}" BIN="${BIN}" MAN="${MAN}" ./install.sh 228 | MSU_ASSUME_YES="yes" "${BIN}/msu" nuke 229 | [ ! -d "${LIB}/msu" ] 230 | [ ! -e "${BIN}/msu" ] 231 | [ ! -e "${MAN}/man1/msu.1" ] 232 | [ ! -e "${MAN}/man3/msu.3" ] 233 | ! grep "${MSU_LOAD_STRING}" ~/.bashrc 234 | ! grep "# added by msu" ~/.bashrc 235 | [ ! -d "/tmp/msu" ] 236 | [ ! -e "/tmp/nuke_msu" ] 237 | [ ! -d "/tmp/.msu.clones" ] 238 | } 239 | -------------------------------------------------------------------------------- /test/test.get.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | # tests against get.sh 3 | 4 | 5 | BASHRC_TMP=~/.bashrc.msu 6 | 7 | cp ~/.bashrc ~/.bashrc~ # backup 8 | 9 | 10 | function setup() { 11 | mv ~/.bashrc "${BASHRC_TMP}" 12 | touch ~/.bashrc 13 | } 14 | 15 | 16 | function teardown() { 17 | mv "${BASHRC_TMP}" ~/.bashrc 18 | rm -rf /tmp/msu 19 | } 20 | 21 | 22 | @test "test-run get.sh" { 23 | cat get.sh | bash 24 | } 25 | 26 | 27 | @test "clones to /tmp/msu" { 28 | cat get.sh | bash 29 | [ -d /tmp/msu ] 30 | } 31 | 32 | 33 | @test "removes existing directory at /tmp/msu before cloning" { 34 | mkdir -p /tmp/msu 35 | cat get.sh | bash 36 | [ -d /tmp/msu ] 37 | } 38 | 39 | 40 | @test "clones to a depth of 1 by default" { 41 | cat get.sh | bash 42 | cd /tmp/msu 43 | [ "$(git rev-list HEAD --count)" -eq 1 ] 44 | cd .. 45 | } 46 | 47 | 48 | @test "clone for a certain build" { 49 | local hash="9bc50798b321b134a0d471a8584fba4fc0c15b06" 50 | cat get.sh | BUILD="${hash}" bash 51 | cd /tmp/msu 52 | [ "$(git rev-parse HEAD)" == "${hash}" ] 53 | cd .. 54 | } 55 | 56 | 57 | @test "download url resolves successfully" { 58 | wget https://git.io/vTE0s -O _test_get.sh 59 | real="$(cat get.sh)" 60 | downloaded="$(cat _test_get.sh)" 61 | [ "${real}" == "${downloaded}" ] 62 | } 63 | 64 | -------------------------------------------------------------------------------- /test/test.install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | # tests against ./install.sh 3 | 4 | 5 | BASHRC_TMP=~/.bashrc.msu 6 | 7 | 8 | cp ~/.bashrc ~/.bashrc~ # backup 9 | 10 | 11 | function setup() { 12 | mv ~/.bashrc "${BASHRC_TMP}" 13 | touch ~/.bashrc 14 | } 15 | 16 | 17 | function teardown() { 18 | [ -e ~/bin/msu ] && rm -rf ~/bin/msu 19 | [ -e "${BIN}/msu" ] && rm -rf "${BIN:-'.'}/msu" 20 | [ -d ~/lib/msu ] && rm -rf ~/lib/msu 21 | [ -d "${LIB}/msu" ] && rm -rf "${LIB:-'.'}/msu" 22 | mv "${BASHRC_TMP}" ~/.bashrc 23 | } 24 | 25 | 26 | @test "test-run install (requires NO sudo or other variables)" { 27 | ./install.sh 28 | } 29 | 30 | 31 | @test "uses \${LIB} to prefix destination directory for library" { 32 | local lib="${BATS_TMPDIR}/some-lib" 33 | rm -rf "${lib}/msu" 34 | LIB="${lib}" ./install.sh 35 | [ -d "${lib}/msu" ] 36 | } 37 | 38 | 39 | @test "uses \${BIN} to prefix destination directory for executable" { 40 | local bin="${BATS_TMPDIR}/some-bin" 41 | rm -rf "${bin:-'.'}/msu" 42 | BIN="${bin}" ./install.sh 43 | [ -x "${bin}/msu" ] 44 | } 45 | 46 | 47 | @test "uses \${MAN} to set destination to manpages" { 48 | local man="${BATS_TMPDIR}/some-man" 49 | rm -rf "${man:-'.'}/man*" 50 | MAN="${man}" ./install.sh 51 | [ -d "${man}/man1" ] 52 | [ -d "${man}/man3" ] 53 | [ -f "${man}/man1/msu.1" ] 54 | [ -f "${man}/man3/msu.3" ] 55 | } 56 | 57 | 58 | @test "adds \${BIN} to ~/.bashrc if not in \${PATH}" { 59 | local bin="${BATS_TMPDIR:-'.'}/another-bin" 60 | BIN="${bin}" ./install.sh 61 | cat ~/.bashrc | grep "export PATH=\"${bin}\":\${PATH}" 62 | } 63 | 64 | 65 | @test "adds \${MAN} to ~/.bashrc if not in \${MANPATH}" { 66 | local man="${BATS_TMPDIR:-'.'}/another-man" 67 | MAN="${man}" ./install.sh 68 | cat ~/.bashrc | grep "export MANPATH=\"${man}\":\${MANPATH}" 69 | } 70 | 71 | 72 | @test "generates metadata" { 73 | ./install.sh 74 | local data=$(cat ~/lib/msu/metadata.sh) 75 | local path_regexp="'.+'" 76 | echo "${data}" 77 | echo "${data}" | grep -E "MSU_INSTALL_LIB=${path_regexp}" 78 | echo "${data}" | grep -E "MSU_INSTALL_BIN=${path_regexp}" 79 | echo "${data}" | grep -E "MSU_INSTALL_MAN=${path_regexp}" 80 | echo "${data}" | grep -E "MSU_BUILD_HASH='[a-z0-9]+'" 81 | echo "${data}" | grep -E "MSU_BUILD_DATE='.+'" 82 | } 83 | 84 | 85 | @test "links executable in path to that in library" { 86 | ./install.sh 87 | local realpath=$(readlink ~/bin/msu) 88 | [ -x ~/bin/msu ] 89 | [ "${realpath}" == "$(readlink -f ~/lib/msu/msu.sh)" ] 90 | } 91 | 92 | 93 | @test "adds loader string for loading msu" { 94 | ./install.sh 95 | cat ~/.bashrc | grep -E ". msu require load" 96 | } 97 | 98 | 99 | @test "spits out version information when done" { 100 | local version_info=$(./install.sh) 101 | local expected=$(msu version) 102 | echo "${version_info}" | grep "${expected}" 103 | } 104 | -------------------------------------------------------------------------------- /test/test.metadata.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | # tests against lib/metadata.sh 3 | 4 | 5 | @test "metadata contains version & contact information" { 6 | source lib/metadata.sh 7 | [ "${MSU_AUTHOR_NAME}" ] 8 | [ "${MSU_AUTHOR_EMAIL}" ] 9 | [ "${MSU_VERSION}" ] 10 | } 11 | -------------------------------------------------------------------------------- /test/test.msu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | # tests against lib/msu.sh 3 | 4 | 5 | @test "\`msu' sets \${MSU_LIB} to library, if its a symbolic link" { 6 | msu_ln="${BATS_TMPDIR}/msu" 7 | ln -sf "${PWD}/lib/msu.sh" "${msu_ln}" 8 | . "${msu_ln}" 9 | [ "$(readlink -f ${MSU_LIB})" == "$(readlink -f ${PWD}/lib)" ] 10 | } 11 | 12 | 13 | @test "\`msu' sets \${MSU_LIB} to library, if executed directly" { 14 | . ./lib/msu.sh 15 | [ "$(readlink -f ${MSU_LIB})" == "$(readlink -f ${PWD}/lib)" ] 16 | } 17 | 18 | 19 | @test "\`msu require' loads a module" { 20 | [ ! "$(command -v log)" ] 21 | . ./lib/msu.sh require console 22 | command -v log 23 | command -v success 24 | command -v error 25 | } 26 | 27 | 28 | @test "\`msu upgrade' upgrades msu" { 29 | skip 30 | } 31 | 32 | 33 | @test "\`msu run' runs a module function" { 34 | run lib/msu.sh run console.log ian 35 | [ "${status}" -eq 0 ] 36 | echo "${output}" | grep "ian" 37 | } 38 | 39 | 40 | @test "\`msu help' shows help information" { 41 | run lib/msu.sh help 42 | [ "${status}" -eq 0 ] 43 | echo "${output}" | grep "help information" 44 | } 45 | 46 | 47 | @test "\`msu version' shows version information" { 48 | run lib/msu.sh version 49 | [ "${status}" -eq 0 ] 50 | echo "${output}" | grep "version" 51 | echo "${output}" | grep "build" 52 | echo "${output}" | grep "date" 53 | } 54 | 55 | 56 | @test "\`msu' does not error if run without arguments" { 57 | run lib/msu.sh 58 | [ "${status}" -eq 0 ] 59 | } 60 | 61 | 62 | @test "\`msu' can be used in a shebang" { 63 | bang_sh="${BATS_TMPDIR}/bang.sh" 64 | shebang="#!/usr/bin/env msu.sh" 65 | { 66 | echo "${shebang}" 67 | echo "msu_require 'console'" 68 | echo "log 'LOGGED'" 69 | } > "${bang_sh}" 70 | chmod +x "${bang_sh}" 71 | PATH="${PWD}/lib:${PATH}" run ${bang_sh} 72 | echo "${output}" 73 | [ "${status}" -eq 0 ] 74 | echo "${output}" 75 | echo "${output}" | grep "LOGGED" 76 | } 77 | --------------------------------------------------------------------------------