├── definitions ├── machines │ └── README.md ├── bootstrap │ ├── linux │ └── macos └── global │ └── example ├── grind.d ├── conf ├── _verify ├── show ├── search ├── _core-force ├── _macos-defaults ├── _secret-keys ├── _core-quiet ├── bootstrap ├── list ├── edit ├── clone ├── _core-dryrun ├── run ├── _apt ├── link ├── update ├── _mas ├── _github ├── unlink ├── _appdmg ├── _brew ├── check ├── _environment ├── _files └── _core ├── LICENSE ├── conf └── grind.conf ├── CONTRIBUTING.md ├── README.md └── grind /definitions/machines/README.md: -------------------------------------------------------------------------------- 1 | ## Definitions specific to machines 2 | 3 | This directory is definitions that will execute only for a specific machines. 4 | Let's say that one of your machines is called 'bigblue', first create a 5 | directory called 'bigblue': 6 | 7 | ``` 8 | # hostname -s returns the short machine name (without domain) 9 | mkdir $(hostname -s) 10 | ``` 11 | 12 | Then add any definition for this machine inside this directory new directory. 13 | `grind` will execute first the *global* definitions and after that will execute 14 | definitions for the machine. 15 | -------------------------------------------------------------------------------- /grind.d/conf: -------------------------------------------------------------------------------- 1 | # vim: ft=zsh 2 | SUBCOMMAND_DESC="Grind's configuration" 3 | SUBCOMMAND_HELP=$(cat <= 4" 25 | _msg "You can now run '/usr/local/bin/bash'" 26 | _msg "And then invoke ./grind update run" 27 | fi 28 | -------------------------------------------------------------------------------- /grind.d/_core-force: -------------------------------------------------------------------------------- 1 | # vim: ft=sh 2 | SUBCOMMAND_DESC="Library for forced execution mode" 3 | SUBCOMMAND_HELP=$(cat <&1 | egrep '^password' | awk -F\" '{print $2}') 18 | echo -n ${key%\\n} 19 | } 20 | 21 | function _keyring { 22 | log "called _keyring(${1})" 23 | key=$(secret-tool lookup devcli ${1}) 24 | echo -n ${key%\\n} 25 | } 26 | 27 | function key() { 28 | local key=${1} 29 | __osx && _keychain ${key} || _keyring ${key} 30 | } 31 | 32 | # allow access to help by 'grind _lib --help' 33 | case ${1} in -h|--help) help;; esac 34 | -------------------------------------------------------------------------------- /grind.d/_core-quiet: -------------------------------------------------------------------------------- 1 | # vim: ft=zsh 2 | SUBCOMMAND_DESC="Library for quiet mode" 3 | SUBCOMMAND_HELP=$(cat < for SKIP, R -> for RUN 31 | function __box { 32 | local msg="${1}" 33 | echo -n ${msg[1,6]} 34 | } 35 | 36 | # allow access to help by 'grind _lib --help' 37 | case ${1} in -h|--help) help;; esac 38 | -------------------------------------------------------------------------------- /grind.d/bootstrap: -------------------------------------------------------------------------------- 1 | # vim: ft=zsh 2 | SUBCOMMAND_DESC="Bootstrap machine" 3 | SUBCOMMAND_HELP=$(cat < /dev/null 20 | } 21 | 22 | function xcode_cli_install() { 23 | _msg "Installing XCode CLI Tools" 24 | xcode-select --install 25 | _msg "Press any key after XCode CLI installation, CTRL-C to cancel." 26 | read 27 | } 28 | 29 | # Install XCode CLI Tools 30 | _msg "Check if XCode CLI Tools is installed" 31 | xcode_cli_installed || xcode_cli_install 32 | 33 | # Install Homebrew if XCode is present 34 | if [[ `xcode_cli_installed` -eq 0 ]]; then 35 | _msg "Check if homebrew is installed" 36 | whence -p brew &> /dev/null || install_homebrew 37 | fi 38 | -------------------------------------------------------------------------------- /grind.d/list: -------------------------------------------------------------------------------- 1 | # vim: ft=zsh 2 | 3 | SUBCOMMAND_DESC="List definitions" 4 | SUBCOMMAND_HELP=$(cat <>>${d}<<<\n"; 20 | done 21 | [[ -n ${GRIND_VERBOSE} ]] || echo 22 | } 23 | 24 | function _all_defs() { 25 | log "listing definitions from ${GRIND_DEF_DIR}" 26 | for i in $(export LC_COLLATE=C; find -E ${GRIND_DEF_DIR} \ 27 | \( -type f -or -type l \) \ 28 | -not -path '*/\.*' \ 29 | -not -regex '.*/(machines|files|global)/.*' | sort); do 30 | log "found: ${i}" 31 | in_cyan ">>>${i##${GRIND_DEF_DIR}/}<<<\n"; 32 | done 33 | } 34 | 35 | 36 | case ${1} in 37 | noop) ;; 38 | -h|--help) help ;; 39 | -a|--all) _all_defs ;; 40 | *) _machine_defs ;; 41 | esac 42 | -------------------------------------------------------------------------------- /grind.d/edit: -------------------------------------------------------------------------------- 1 | # vim: ft=zsh 2 | 3 | SUBCOMMAND_DESC="Edit a defition" 4 | SUBCOMMAND_HELP=$(cat </dev/null 14 | } 15 | 16 | function apt_update() { 17 | sudo apt-get update &>/dev/null 18 | } 19 | 20 | function apt_pkg() { 21 | local package_name="${1}" 22 | log "apt_pkg: ${package_name}" 23 | do_run "sudo apt-get install --yes --quiet ${package_name}" 24 | unless "_apt_pkg_installed '${package_name}'" 25 | } 26 | 27 | function dkpg_install() { 28 | local package_name="${1}" 29 | local file_name="${2}" 30 | do_run "sudo dpkg -i ${file_name}" 31 | unless "_apt_pkg_installed '${package_name}'" 32 | } 33 | 34 | function downloaded_deb_install() { 35 | local package_name="${1}" 36 | local download_url="${2}" 37 | local download_path="${DOWNLOADS_DIR}/${package_name}.deb" 38 | 39 | do_run "wget ${download_url} -O ${download_path}" 40 | unless_file "${download_path}" 41 | 42 | dkpg_install ${package_name} ${download_path} 43 | } 44 | 45 | # allow access to help by 'grind _lib --help' 46 | case ${1} in -h|--help) help;; esac 47 | -------------------------------------------------------------------------------- /conf/grind.conf: -------------------------------------------------------------------------------- 1 | # Configurations for grind 2 | 3 | # GRIND_DEF_DIR 4 | # Directory from where to load grind defitions, the default 5 | # will be the `definitions/` directory 6 | # 7 | # Example: 8 | # GRIND_DEF_DIR="/home/myuser/grind-defs" 9 | 10 | # GRIND_MACHINE_NAME 11 | # grind uses `hostname -s` to figure the machine name 12 | # and use it on definitions. You can override the machine 13 | # name here. 14 | # 15 | # Example: 16 | # GRIND_MACHINE_NAME="my-machine-name" 17 | # GRIND_CHECK_IGNORE_APPS This is an array of application names you wish to 18 | # ignore from the `grind update check` list. Some applications be installed 19 | # through grind so you might as well ignore them. 20 | # 21 | # Other applications are not defined as artifacts on brew formula spec so, 22 | # regardless of installing them, grind cannot infer if they should be on the 23 | # check list or not. 24 | # example: 25 | #GRIND_CHECK_IGNORE_APPS=( 26 | # # Google office suite, I think is istalled by Google Drive(?) 27 | # 'Google Docs' 28 | # 'Google Slides' 29 | # 'Google Sheets' 30 | # # microsoft applications can be business or home 31 | # # the business version can't be installed through grind 32 | # 'Microsoft Excel' 33 | # 'Microsoft PowerPoint' 34 | # 'Microsoft Teams' 35 | # 'Microsoft Word' 36 | # # 1Password extension comes with 1Password 37 | # '1Password for Safari' 38 | #) 39 | 40 | -------------------------------------------------------------------------------- /definitions/global/example: -------------------------------------------------------------------------------- 1 | # vim: ft=sh 2 | run "# This is a global definition example" 3 | run "# It will run for every machine" 4 | 5 | run "# Specific definitions should go into machines/MACHINE_NAME" 6 | 7 | do_run "echo 'Since the file does not exist, this should be called. A RUN message appears.'" 8 | unless_file "/this/file/most/likely/does/not/exist" 9 | 10 | do_run "echo '${USER} things inside do_run and are actually zsh.'" 11 | if_ "[[ -f /bin/zsh ]]" 12 | 13 | function functions_too_can_be_used() { 14 | cat < /dev/null" 26 | 27 | do_run "functions_too_can_be_used" 28 | if_ "true" 29 | 30 | do_run "echo 'This should not run, a SKIP message should appear'" 31 | unless_file "${0}" 32 | 33 | do_run "echo 'Should SKIP as zsh should be running.'" 34 | unless_running 'zsh' 35 | 36 | do_run "echo 'Should RUN as process blahblah probably is not running.'" 37 | unless_running 'blahblah' 38 | 39 | run "echo 'stop_on_fail' should stop the whole execution if the last command failed." 40 | stop_on_fail 41 | 42 | run "false" 43 | stop_on_fail "False always fail." 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing with grind 2 | 3 | First off, thanks for considering helping. 4 | 5 | `grind` is a quite simple software in theory you should be able to understand 6 | the code and *create a pull request with your change*. And that's the preferred 7 | way of contributing. 8 | 9 | ## Things to keep in mind when contributing 10 | 11 | * Be mindful of everyone's machine 12 | * Use admin privileges with utmost care 13 | * Keep it simple but idiomatic 14 | 15 | ## Be mindful of everyone's machine 16 | 17 | `grind` is mostly a configuration tool, it helps to set up your machine without 18 | having to remember all the steps or software you usually need. This is very 19 | personal, each user will have different sets of configurations and 20 | applications. 21 | 22 | So keep `grind` unopinionated as most as you can, do not force conventions that 23 | are not already conventions from the OS or other tools you interact with. 24 | 25 | ### Use admin privileges with utmost care 26 | 27 | It is possible to call for admin privileges through `grind` functions. But for 28 | each instance that this is needed it is important that the user is informed and 29 | consent to the admin use. 30 | 31 | Never require to run `grind` as root. Never ask for admin privileges without 32 | informing the user what you're trying to do. Never do more with admin 33 | privileges than what you asked for, if you need to do two unrelated things just 34 | ask for admin again. 35 | 36 | ### Keep it simple but idiomatic 37 | 38 | Regarding the code itself, keep it simple. Do not write long functions or over 39 | complicated code. 40 | 41 | With this said, `grind` is a zsh script and shell scripting has it's common 42 | idioms. Like ternary such as `[[ test ]] && if_pass || if_fails` and that's 43 | Okay. 44 | -------------------------------------------------------------------------------- /grind.d/link: -------------------------------------------------------------------------------- 1 | # vim: ft=zsh 2 | 3 | SUBCOMMAND_DESC="Link a defintion to a machine" 4 | SUBCOMMAND_HELP=$(cat <abs2rel(@ARGV) . "\n"' ${GRIND_DEF_DIR} ${defmachine}) 41 | log "relative path: ${relative}" 42 | 43 | (cd ${GRIND_DEF_DIR}/machines/${machine} || \ 44 | error "Failed to change into machine def dir.\n" 45 | 46 | log "ln -s ${relative}/${def} ${defname}" 47 | [[ -L ${defname} ]] && \ 48 | log "definition '${def}' already linked to '${machine}', skip ln command" || \ 49 | ln -s ${relative}/${def} ${defname} 50 | ) 51 | ;; 52 | esac 53 | -------------------------------------------------------------------------------- /grind.d/update: -------------------------------------------------------------------------------- 1 | # vim: ft=zsh 2 | use "core" 3 | 4 | SUBCOMMAND_DESC="Run definitions for this machine" 5 | SUBCOMMAND_HELP=$(cat <>>${d}<<<\n"; 27 | done 28 | [[ -n ${GRIND_VERBOSE} ]] || echo 29 | } 30 | 31 | 32 | case ${1} in 33 | noop) ;; 34 | -h|--help) help ;; 35 | -l|--list) 36 | in_cyan "Definitions for ${GRIND_MACHINE_NAME}\n" 37 | _update_recur "list" 38 | ;; 39 | -n|--dry) 40 | in_cyan "Update using dry run\n" 41 | use "core-dryrun" # this overrides __run 42 | _update_recur "run" 43 | ;; 44 | -f|--force) 45 | in_cyan "Update using force\n" 46 | warn "Force will run definitions regardless of its run checks results\n" 47 | warn "This is potentially dangerous\n" 48 | 49 | if read -q "confirm?Press Y/y to confirm, other key to cancel: "; then 50 | use "core-force" # this overrides __conditional_run 51 | _update_recur "run" 52 | else 53 | in_yellow "\nCanceled.\n" 54 | fi 55 | ;; 56 | *) 57 | in_cyan "Update run\n" 58 | [[ -n ${GRIND_VERBOSE} ]] || use "core-quiet" 59 | _update_recur "run" 60 | ;; 61 | esac 62 | -------------------------------------------------------------------------------- /grind.d/_mas: -------------------------------------------------------------------------------- 1 | # vim: ft=zsh 2 | SUBCOMMAND_DESC="Library for App Store (mas) definitions" 3 | SUBCOMMAND_HELP=$(cat < /dev/null 38 | [[ $? -ne 0 ]] && true || false 39 | } 40 | 41 | function mas_requested() { 42 | echo "${_MAS_APPS_REQUESTED}" 43 | } 44 | 45 | function appstore() { 46 | local appname="${1}" 47 | local appid="${2}" 48 | 49 | log "appstore appname: ${appname} appid: ${appid}" 50 | 51 | _mas_not_available && \ 52 | warn "'mas' not installed, run 'grind _mas' for more information.\n" && \ 53 | warn "Ignoring: appstore '${appname}' ${appid}\n" && \ 54 | return 55 | 56 | _MAS_APPS_REQUESTED+="${appname}\n" 57 | 58 | do_run "mas install ${appid}" 59 | unless "_mas_is_installed '${appname}'" 60 | } 61 | 62 | # allow access to help by 'grind _lib --help' 63 | case ${1} in -h|--help) help;; esac 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grind 2 | 3 | `grind` is a tool to help you configure and maintain your machine. I wrote a 4 | [blog post](http://marcovaltas.com/2016/12/21/introducing-grind.html) 5 | explaining why I created `grind`. 6 | 7 | ## How to use it (short) 8 | 9 | 1. [Download](https://github.com/mavcunha/grind/releases) or clone grind. 10 | 2. Check the `definitions` directory and write your own definition. 11 | 3. run `./grind update` 12 | 13 | ## How to use it (long) 14 | 15 | ### Bootstrap 16 | 17 | `grind` provides a `bootstrap` command for a pre-setup for grind itself: 18 | 19 | ```zsh 20 | grind bootstrap 21 | ``` 22 | 23 | This is a special definition and it should set and install the requirements 24 | from `grind` itself. On macOS it means installing [XCode CLI 25 | Tools](https://developer.apple.com/xcode/features/) and 26 | [Homebrew](http://brew.sh/). 27 | 28 | ### Update 29 | 30 | `grind update` is the daily command from `grind`, it will execute and run all 31 | definitions for current machine. 32 | 33 | ### Definitions 34 | 35 | All `grind` does is execute some functions in series defined in the `definitions` directory. 36 | Here's an example of a definition on macOS: 37 | 38 | ### A simple definition 39 | 40 | This definition will install `git` through *homebrew* if is not already installed. 41 | 42 | ```zsh 43 | use "brew" 44 | 45 | brew_pkg "git" 46 | ``` 47 | 48 | ### Definitions directory organization 49 | 50 | First `grind` will load and execute whatever is in the `global` directory, 51 | these are assumed to be definitions to be applied to any machine. 52 | 53 | After that `grind` will load scripts from the `machines` directory, where it will try 54 | to find a subdirectory with the machine name and from there load the scripts. These 55 | definitions are specific for each machine you want to maintain. 56 | 57 | ## Documentation 58 | 59 | [Click here for more information.](https://github.com/mavcunha/grind/wiki) 60 | -------------------------------------------------------------------------------- /grind.d/_github: -------------------------------------------------------------------------------- 1 | # vim: ft=zsh 2 | SUBCOMMAND_DESC="Library to interact with github" 3 | SUBCOMMAND_HELP=$(cat </dev/null) 27 | log "mount-point: ${mount_point}" 28 | echo "${mount_point}" 29 | } 30 | 31 | function _appdmg_teardown() { 32 | local mount_point="${1}" 33 | local downloaded_file="${2}" 34 | 35 | log "_appdmg_teardown" 36 | 37 | log "unmounting: ${mount_point}" 38 | ${HDIUTIL_BIN} eject ${mount_point} &> /dev/null || warn "Problems unmounting ${mount_point}\n" 39 | 40 | log "safe removing: ${downloaded_file}" 41 | safe_rm_download_dir "${downloaded_file}" 42 | } 43 | 44 | function _appdmg_install() { 45 | local mount="${1}" 46 | while read file; do 47 | appfile=$(basename "${file}") 48 | log "calling ditto on ${file} to /Applications/${appfile}" 49 | ${DITTO_BIN} --rsrc "${file}" "/Applications/${appfile}" || error "Moving ${file} to /Applications\n" 50 | done < <(find "${mount}" -type d -name "*.app" 2> /dev/null) 51 | } 52 | 53 | function _appdmg_process() { 54 | local uri="${1}" 55 | local dmg=${uri##*/} 56 | 57 | log "init download of dmg: ${dmg} from uri: ${uri}" 58 | local downloaded_file=$(file_download "${uri}" "${dmg}") 59 | 60 | log "mounting: ${downloaded_file}" 61 | mount=$(_appdmg_mount "${downloaded_file}") 62 | 63 | log "installing apps found in ${mount}" 64 | _appdmg_install "${mount}" 65 | 66 | log "call _appdmg_teardown" 67 | _appdmg_teardown "${mount}" "${downloaded_file}" 68 | } 69 | 70 | function appdmg_custom() { 71 | local uri="${1}" 72 | do_run "_appdmg_process '${uri}'" 73 | } 74 | 75 | function appdmg() { 76 | local appname="${1}" 77 | local uri="${2}" 78 | 79 | _APPDMG_REQUESTED+="${appname}\n" 80 | 81 | appdmg_custom "${uri}" 82 | unless_dir "/Applications/${appname}.app" 83 | } 84 | 85 | function appdmg_requested() { 86 | echo "${_APPDMG_REQUESTED}" 87 | } 88 | 89 | # allow access to help by 'grind _lib --help' 90 | case ${1} in -h|--help) help;; esac 91 | -------------------------------------------------------------------------------- /grind.d/_brew: -------------------------------------------------------------------------------- 1 | # vim: ft=zsh 2 | SUBCOMMAND_DESC="Library for homebrew definitions" 3 | SUBCOMMAND_HELP=$(cat < 0' ) 82 | echo ${clean_deps} 83 | else 84 | log "no requests found" 85 | fi 86 | } 87 | 88 | function brew_missing() { 89 | local req_type="${1}" 90 | local requests="_BREW_${(U)req_type}_REQUESTED" 91 | local installed="_BREW_${(U)req_type}_INSTALLED" 92 | 93 | all_deps=$(_brew_build_deps_reference ${req_type}) 94 | log "all_deps: ${all_deps}" 95 | for p in ${(@fP)installed}; do 96 | log "check if '${p}' was requested" 97 | if ! grep -q "^${p}$" <<<${all_deps}; then 98 | extra+="${p}\n" 99 | fi 100 | done 101 | echo ${extra} 102 | } 103 | 104 | function brew_cask_requested() { 105 | log "brew_cask_requested()" 106 | 107 | [[ $(whence -p jq) ]] || error "'jq' not found, please install 'jq' to use this command" 108 | 109 | local apps="" 110 | log "resolving application names" 111 | for cask in ${(@f)_BREW_CASK_REQUESTED}; do 112 | # we need to the app name as it is installed like MyApp.app and 113 | # not the lower case version, unfortunately this is super slow 114 | # to run 115 | app_name=$(brew info --json=v2 --cask ${cask} | jq -r '.. // empty | strings | select(test("\\.app$"))') 116 | if [[ -z ${app_name} ]]; then 117 | log "'${cask}' didn't resolve to any app, skipping." 118 | continue 119 | else 120 | log "${cask} => ${app_name:t}" 121 | apps+="${app_name:t}\n" 122 | fi 123 | done 124 | 125 | echo "${apps}" 126 | } 127 | 128 | # allow access to help by 'grind _lib --help' 129 | case ${1} in -h|--help) help;; esac 130 | -------------------------------------------------------------------------------- /grind.d/check: -------------------------------------------------------------------------------- 1 | # vim: ft=zsh 2 | use "core" 3 | 4 | SUBCOMMAND_DESC="Check current installation versus definitions" 5 | SUBCOMMAND_HELP=$(cat <>>brew packages installed but not defined<<<\n" 116 | in_yellow "$(brew_missing 'pkg')\n" 117 | 118 | in_yellow ">>>applications installed but not defined (please wait)<<<\n" 119 | in_yellow "$(missing_apps)\n" 120 | ;; 121 | esac 122 | -------------------------------------------------------------------------------- /grind.d/_environment: -------------------------------------------------------------------------------- 1 | # vim: ft=zsh sw=2 ts=2 expandtab 2 | # This file is loaded first and anything defined here 3 | # will be available to all subcommands. 4 | # This is a valid bash file and will execute 5 | SUBCOMMAND_DESC="Environment setttings" 6 | SUBCOMMAND_HELP=$(cat <&2 19 | echo -e "debug: ${*}" >&2 20 | tput -Txterm sgr0 >&2 21 | fi 22 | } 23 | 24 | function error() { 25 | in_red "ERROR: ${1}" 26 | exit 1 27 | } 28 | 29 | function warn() { 30 | in_yellow "WARN: ${1}" 31 | } 32 | 33 | # accept '-d' or '--debug' as a debug flag 34 | # accept '-v' or '--verbose' as verbose flag 35 | zparseopts -D -E - d=DEBUG -debug=DEBUG v=GRIND_VERBOSE -verbose=GRIND_VERBOSE 36 | 37 | # also accept GRIND_DEBUG=true as debug 38 | _DEBUG_ENV="${(U)MAIN_COMMAND}_DEBUG" 39 | [[ -z ${DEBUG} ]] && DEBUG=${(P)_DEBUG_ENV} 40 | 41 | # this should not run as root it is too dangerous. 42 | # Privileged should be asked in a definition to 43 | # control the scope of priviledged run. 44 | [[ "${EUID}" -eq 0 ]] && error "Running ${MAIN_COMMAND} as root is not supported.\n" 45 | 46 | # add itself to PATH if needed 47 | log "check if ${MAIN_COMMAND} is on PATH" 48 | type -a ${MAIN_COMMAND} &> /dev/null 49 | if [[ $? -ne 0 ]]; then 50 | export PATH=${PATH}:${ROOT_DIR} 51 | fi 52 | 53 | # environment, this is a point of extension and configuration 54 | # it should give some ability to extend behavior 55 | [[ -f ${SUBCOMMANDS_DIR}/_environment ]] && . ${SUBCOMMANDS_DIR}/_environment noop 56 | 57 | function _list_commands() { 58 | cat < /dev/null ]]" 27 | 28 | unless_link ARG Same as unless "[[ -L ARG ]]" 29 | 30 | if_ CMD Will execute CMD, if CMD returns 0 it will 31 | return true. If CMD returns != 0 it will 32 | return false. 33 | 34 | if_dir ARG Same as if_ '[[ -d ARG ]]' 35 | 36 | stop_on_fail [MSG] Will stop execution of grind if the last 37 | executed command failed. 38 | 39 | warn_on_fail [MSG] Will issue a warn during execution if the last 40 | executed command failed. 41 | 42 | Private functions: 43 | 44 | __strip_spaces ARG Will remove all spaces of a given string. 45 | 46 | __iexec CMD "Idempotent Exec" will execute CMD and remember 47 | that was called. If __iexec is called with the same 48 | CMD again it will warn and won't call CMD again. 49 | 50 | __capture_exit_code Saves last exit code for use with __last_exit_code 51 | 52 | __last_exit_code Returns as string the last exit code encountered 53 | 54 | __box MSG Writes "[MSG]" on screen 55 | 56 | __yellow_box MSG Same as __box with MSG printed in yellow 57 | 58 | __green_box MSG Same as __box with MSG printed in green 59 | 60 | __magenta_box MSG Same as __box with MSG printed in magenta 61 | 62 | __red_box MSG Same as __box with MSG printed in red 63 | 64 | __run Will invoke __iexec for the next CMD already buffered 65 | 66 | __conditional_run CMD REG Will execute CMD through __iexec and check the exit code 67 | against REG (regular expression). If match should call __run 68 | if not should print SKIP using __green_box 69 | 70 | EOH 71 | ) 72 | 73 | typeset -gA __GRIND_EXECS 74 | typeset -g __GRIND_CMD_NEXT 75 | typeset -g __GRIND_CMD_LAST 76 | integer -g __GRIND_CMD_EXIT_CODE 77 | 78 | function __strip_spaces() { 79 | echo ${1// /} 80 | } 81 | 82 | function __iexec() { 83 | local k=$(__strip_spaces "${1}") 84 | log "__iexec: ${1}" 85 | if [[ -n ${__GRIND_EXECS[${k}]} ]]; then 86 | log "already ran: ${1}, skipping." 87 | __capture_exit_code 88 | else 89 | log "mark: ${1} as ran" 90 | __GRIND_EXECS[${k}]="${1}" 91 | __GRIND_CMD_LAST="${1}" 92 | log "call: ${1}" 93 | eval "${1}" 94 | __capture_exit_code 95 | fi 96 | } 97 | 98 | function __capture_exit_code() { 99 | local code=$? 100 | log "__capture_exit_code: ${code}" 101 | __GRIND_CMD_EXIT_CODE=${code} 102 | } 103 | 104 | function __last_exit_code() { 105 | log "__last_exit_code: ${__GRIND_CMD_EXIT_CODE}" 106 | echo ${__GRIND_CMD_EXIT_CODE} 107 | } 108 | 109 | function __box() { 110 | echo "$(in_cyan '[')$(echo "${1}" )$(in_cyan ']')" 111 | } 112 | 113 | function __yellow_box() { 114 | __box "$(in_yellow "${1}")" 115 | } 116 | function __green_box() { 117 | __box "$(in_green "${1}")" 118 | } 119 | function __magenta_box() { 120 | __box "$(in_magenta "${1}")" 121 | } 122 | function __red_box() { 123 | __box "$(in_red "${1}")" 124 | } 125 | 126 | function __run() { 127 | __iexec "${__GRIND_CMD_NEXT}" 128 | } 129 | 130 | function __conditional_run() { 131 | local cmd="${1}" 132 | local reg="${2}" 133 | log "__conditional_run: ${cmd} re: ${reg}" 134 | __iexec "${cmd}" 135 | if [[ $(__last_exit_code) =~ ${reg} ]]; then 136 | log "exit matched ${reg}: RUN" 137 | __run 138 | __yellow_box 'RUN' 139 | else 140 | log "exit didn't match ${reg}: SKIP" 141 | __green_box 'SKIP' 142 | fi 143 | } 144 | 145 | ### Public accessible API 146 | 147 | function do_run() { 148 | log "do_run: ${1}" 149 | in_cyan "do_run: ${1}\n" 150 | __GRIND_CMD_NEXT="${1}" 151 | } 152 | 153 | function run() { 154 | log "run: ${1}" 155 | in_cyan "run: ${1}\n" 156 | __GRIND_CMD_NEXT="${1}" 157 | __run 158 | } 159 | 160 | function unless() { 161 | log "unless: ${1}" 162 | in_cyan "\tunless: ${1}\n" 163 | # run if command returns != 0 164 | __conditional_run "${1}" "^[1-9][0-9]*$" 165 | } 166 | 167 | function unless_file() { 168 | log "unless_file: ${1}" 169 | in_cyan "\tunless_file: ${1}\n" 170 | unless "[[ -f \"${1}\" ]]" 171 | } 172 | 173 | function unless_dir() { 174 | log "unless_dir: ${1}" 175 | in_cyan "\tunless_dir: ${1}\n" 176 | unless "[[ -d \"${1}\" ]]" 177 | } 178 | 179 | function unless_on_path() { 180 | log "unless_on_path: ${1}" 181 | in_cyan "\tunless_on_path: ${1}\n" 182 | unless "type ${1} &> /dev/null" 183 | } 184 | 185 | function unless_link() { 186 | log "unless_link: ${1}" 187 | in_cyan "\tunless_link: ${1}\n" 188 | unless "[[ -L \"${1}\" ]]" 189 | } 190 | 191 | function unless_running() { 192 | log "unless_running: ${1}" 193 | in_cyan "\tunless_running: ${1}\n" 194 | unless "pgrep ${1} &> /dev/null" 195 | } 196 | 197 | function if_() { 198 | log "if_: ${1}\n" 199 | in_cyan "\tif_: ${1}\n" 200 | __conditional_run "${1}" "^0$" 201 | } 202 | 203 | function if_dir() { 204 | log "if_dir: ${1}" 205 | in_cyan "\tif_dir: ${1}\n" 206 | if_ "[[ -d \"${1}\" ]]" 207 | } 208 | 209 | function if_link() { 210 | log "if_link: ${1}" 211 | in_cyan "\tif_link: ${1}\n" 212 | if_ "[[ -L \"${1}\" ]]" 213 | } 214 | 215 | function if_file() { 216 | log "if_dir: ${1}" 217 | in_cyan "\tif_file: ${1}\n" 218 | if_ "[[ -f \"${1}\" ]]" 219 | } 220 | 221 | function stop_on_fail() { 222 | local msg="${1:-"stop_on_fail defined"}" 223 | log "stop_on_fail: ${1}" 224 | if [[ $(__last_exit_code) -ne 0 ]]; then 225 | in_red "STOP: ${__GRIND_CMD_LAST} exited with $(__last_exit_code)\n" 226 | in_red "STOP: ${msg}\n" 227 | exit 1 228 | fi 229 | } 230 | 231 | function warn_on_fail() { 232 | local msg="${1:-"warn_on_fail defined"}" 233 | log "warn_on_fail: ${1}" 234 | if [[ $(__last_exit_code) -ne 0 ]]; then 235 | in_yellow "WARN: ${__GRIND_CMD_LAST} exited with $(__last_exit_code)\n" 236 | in_yellow "WARN: ${msg}\n" 237 | exit 1 238 | fi 239 | } 240 | 241 | function run_def() { 242 | local def="${1}" 243 | if [[ -f ${GRIND_DEF_DIR}/${def} ]]; then 244 | in_cyan ">>>${def}<<<\n" 245 | log "running ${GRIND_DEF_DIR}/${def}" 246 | . ${GRIND_DEF_DIR}/${def} 247 | elif [[ -n ${def} ]]; then 248 | error "${GRIND_DEF_DIR}/${def} definition not found.\n" 249 | fi 250 | } 251 | 252 | # allow access to help by 'grind _lib --help' 253 | case ${1} in -h|--help) help;; esac 254 | --------------------------------------------------------------------------------