diff --git a/.hastest.bats b/.hastest.bats index d4c3a1e..a336f5b 100644 --- a/.hastest.bats +++ b/.hastest.bats @@ -1,26 +1,35 @@ #!/usr/bin/env bats INSTALL_DIR= -BATS_TMPDIR=${BATS_TMPDIR:-/tmp} +BATS_TMPDIR="${BATS_TMPDIR:-/tmp}" fancyx='✗' checkmark='✓' - ## We need to create a new directory so that .hasrc file in the root does not get read by the `has` instance under test setup() { - export BATS_TEST_TMPDIR="$BATS_TMPDIR/tmp-for-test" - mkdir -p "$BATS_TEST_TMPDIR" - cp -f has "$BATS_TEST_TMPDIR" - cd "$BATS_TEST_TMPDIR" + export HAS_TMPDIR="${BATS_TMPDIR}/tmp-for-test" + mkdir -p "${HAS_TMPDIR}" + cp -f "${BATS_TEST_DIRNAME}"/has "${HAS_TMPDIR}" + cd "${HAS_TMPDIR}" || return + export has="${HAS_TMPDIR}/has" } teardown() { - if [[ -n "$BATS_TEST_TMPDIR" ]]; then - rm -rf "$BATS_TEST_TMPDIR" + if [[ -d "${HAS_TMPDIR}" ]]; then + rm -rf "${HAS_TMPDIR}" fi } +@test "invoking 'has' without arguments prints usage" { + run $has + + [ "$status" -eq 0 ] + [ "${lines[0]%% *}" = 'has' ] + [ "${lines[1]%%:*}" = 'USAGE' ] + [ "${lines[2]}" = 'EXAMPLE: has git curl node' ] +} + @test "make install creates a valid installation" { - INSTALL_DIR="${BATS_TEST_TMPDIR}/.local" + INSTALL_DIR="${HAS_TMPDIR}/.local" cd "${BATS_TEST_DIRNAME}" run make PREFIX="${INSTALL_DIR}" install [ "$status" -eq 0 ] @@ -30,13 +39,14 @@ teardown() { cd "${INSTALL_DIR}" run "${INSTALL_DIR}/bin/has" [ "$status" -eq 0 ] - [ "${lines[0]%% *}" == 'has' ] - [ "${lines[1]%%:*}" == 'USAGE' ] - rm -rf ${INSTALL_DIR} + [ "${lines[0]%% *}" = 'has' ] + [ "${lines[1]%%:*}" = 'USAGE' ] + [ "${lines[2]}" = 'EXAMPLE: has git curl node' ] + # [ "${lines[2]%%:*}" = 'EXAMPLE' ] } @test "..even if 'has' is missing from directory" { - INSTALL_DIR="${BATS_TEST_TMPDIR}/system_local" + INSTALL_DIR="${HAS_TMPDIR}/system_local" cd "${BATS_TEST_DIRNAME}" mv has has-been run make PREFIX="${INSTALL_DIR}" install @@ -44,102 +54,125 @@ teardown() { [ -x "${INSTALL_DIR}/bin/has" ] cd "${BATS_TEST_DIRNAME}" mv has-been has - rm -rf ${INSTALL_DIR} } @test "make update runs git fetch" { cd "${BATS_TEST_DIRNAME}" + skip "make update overwrites my git working tree" run make update - [[ "$status" -eq 0 ]] - [[ "${lines[@]}" =~ "git fetch --verbose" ]] -} - -@test "has prints help" { - run bash has - - [[ "$(echo "${output}" | grep "has")" ]] - [[ "$(echo "${output}" | grep "USAGE:")" ]] - [[ "$(echo "${output}" | grep "EXAMPLE:")" ]] + [ "$status" -eq 0 ] + [ "${lines[*]}" =~ "git fetch --verbose" ] } @test "works with single command check" { - run bash has git + run $has git - [[ "$status" -eq 0 ]] - [[ "$(echo "${output}" | grep ${checkmark} | grep "git")" ]] + [ "$status" -eq 0 ] + [ "$(echo "${lines[0]}" | grep "git")" ] } -@test "safely tells about tools not configured" { - run bash has foobar +@test "'has' warns about tools not configured" { + run $has foobar - [[ "$status" -eq 1 ]] - [[ "$(echo "${output}" | grep ${fancyx} | grep "foobar not understood")" ]] + [ "$status" -eq 1 ] + [ "$(echo "${output}" | grep ${fancyx} | grep "foobar not understood")" ] } -@test "env var lets override safety check" { - HAS_ALLOW_UNSAFE=y run bash has foobar +@test "env var 'HAS_ALLOW_UNSAFE' overrides safety check" { + HAS_ALLOW_UNSAFE=y run $has foobar - [[ "$status" -eq 1 ]] - [[ "$(echo "${output}" | grep ${fancyx} | grep "foobar")" ]] + [ "$status" -eq 1 ] + [ "$(echo "${output}" | grep ${fancyx} | grep "foobar")" ] } @test "status code reflects number of failed commands" { - HAS_ALLOW_UNSAFE=y run bash has foobar bc git barbaz + HAS_ALLOW_UNSAFE=y run $has foobar bc git barbaz - [[ "$status" -eq 2 ]] - [[ "$(echo "${output}" | grep ${fancyx} | grep "foobar")" ]] - [[ "$(echo "${output}" | grep ${fancyx} | grep "barbaz")" ]] + [ "$status" -eq 2 ] + [ "$(echo "${output}" | grep ${fancyx} | grep "foobar")" ] + [ "$(echo "${output}" | grep ${fancyx} | grep "barbaz")" ] } -@test "status code reflects number of failed commands upto 126" { - run bash has $(for i in {1..256}; do echo foo; done) +@test "status code reflects number of failed commands up to 126" { + run $has $(for i in {1..256}; do echo foo; done) - [[ "$status" -eq 126 ]] + [ "$status" -eq 126 ] } @test "loads commands from .hasrc file and excludes comments" { printf "bash\n#comment\nmake\n" >> .hasrc - run bash has + run $has - [[ "$status" -eq 0 ]] - - [[ "$(echo "${output}" | grep ${checkmark} | grep "bash")" ]] - [[ "$(echo "${output}" | grep ${checkmark} | grep "make")" ]] + [ "$status" -eq 0 ] + [ "$(echo "${output}" | grep ${checkmark} | grep "bash")" ] + [ "$(echo "${output}" | grep ${checkmark} | grep "make")" ] } @test "loads commands from .hasrc file and honors CLI args as well" { printf "bash\nmake\ngit" >> .hasrc - HAS_ALLOW_UNSAFE=y run bash has git bc + HAS_ALLOW_UNSAFE=y run $has git bc - [[ "$status" -eq 0 ]] - - [[ "$(echo "${output}" | grep ${checkmark} | grep "bash")" ]] - [[ "$(echo "${output}" | grep ${checkmark} | grep "make")" ]] - [[ "$(echo "${output}" | grep ${checkmark} | grep "git")" ]] - [[ "$(echo "${output}" | grep ${checkmark} | grep "bc")" ]] + [ "$status" -eq 0 ] + [ "$(echo "${output}" | grep ${checkmark} | grep "bash")" ] + [ "$(echo "${output}" | grep ${checkmark} | grep "make")" ] + [ "$(echo "${output}" | grep ${checkmark} | grep "git")" ] + [ "$(echo "${output}" | grep ${checkmark} | grep "bc")" ] } @test "testing PASS output with unicode" { - run bash has git + run $has git - [[ "$status" -eq 0 ]] + [ "$status" -eq 0 ] [[ "printf '%b\n' ${lines[0]}" =~ '✓' ]] } @test "testing FAIL output with unicode" { - run bash has foobar + run $has foobar - [[ "$status" -eq 1 ]] + [ "$status" -eq 1 ] [[ "printf '%b\n' ${lines[0]}" =~ '✗' ]] } @test "fail count 3: testing output with and without unicode" { - run bash has git foobar barbaz barfoo + run $has git foobar barbaz barfoo - [[ "$status" -eq 3 ]] + [ "$status" -eq 3 ] [[ "printf '%b\n' ${lines[0]}" =~ "${checkmark}" ]] [[ "printf '%b\n' ${lines[2]}" =~ '✗' ]] } +@test "testing archiving commands" { + run $has tar unzip gzip xz unar pv zip + + [ "$status" -eq 0 ] + [ "$(echo "${lines[0]}" | grep "tar")" ] + [ "$(echo "${lines[1]}" | grep "unzip")" ] + [ "$(echo "${lines[2]}" | grep "gzip")" ] + [ "$(echo "${lines[3]}" | grep "xz")" ] + [ "$(echo "${lines[4]}" | grep "unar")" ] + [ "$(echo "${lines[5]}" | grep "pv")" ] + [ "$(echo "${lines[6]}" | grep "zip")" ] +} + +@test "testing coreutils commands" { + run $has coreutils sed awk grep sudo file linuxutils + + [ "$status" -eq 0 ] + [ "$(echo "${lines[0]}" | grep "gnu_coreutils")" ] + [ "$(echo "${lines[5]}" | grep "file")" ] + [ "$(echo "${lines[6]}" | grep "gnu_coreutils")" ] +} + +@test "testing hub version is different to git version" { + if ! command -v hub; then + skip "'hub' command not found. This passes for @virgilwashere locally." + fi + run $has hub git + + [ "$status" -eq 0 ] + [ "$(echo "${lines[0]}" | grep "hub")" ] + [ "$(echo "${lines[1]}" | grep "git")" ] + [ ! "${lines[0]##*\ }" = "${lines[1]##*\ }" ] +} diff --git a/.restyled.yaml b/.restyled.yaml new file mode 100644 index 0000000..44a118a --- /dev/null +++ b/.restyled.yaml @@ -0,0 +1,24 @@ +--- +enabled: true +auto: false +remote_files: [] +comments: true +statuses: true +request_review: null +labels: [linting] +restylers: + - shfmt: +# -ln str language variant to parse (bash/posix/mksh, default "bash") +# -i uint indent: 0 for tabs (default), >0 for number of spaces +# -bn binary ops like && and | may start a line +# -ci switch cases will be indented +# -sr redirect operators will be followed by a space +# -kp keep column alignment paddings +# -mn minify program to reduce its size (implies -s) + arguments: + - -ln bash + - -i 2 + - -ci + - -bn + - -kp + - -sr \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index b592514..381aa2a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,9 @@ addons: update: true packages: - bc + - pv + - xz-utils + - unar env: global: diff --git a/has b/has index 328bba2..8e4e3c3 100755 --- a/has +++ b/has @@ -8,20 +8,20 @@ readonly BINARY_NAME="has" readonly VERSION="v1.4.0" ## constant - symbols for success failure -readonly txtreset="$(tput sgr0)" -readonly txtbold="$(tput bold)" -readonly txtblack="$(tput setaf 0)" -readonly txtred="$(tput setaf 1)" -readonly txtgreen="$(tput setaf 2)" -readonly txtyellow="$(tput setaf 3)" -readonly txtblue="$(tput setaf 4)" -readonly txtpurple="$(tput setaf 5)" -readonly txtcyan="$(tput setaf 6)" -readonly txtwhite="$(tput setaf 7)" +readonly txtreset="$(tput sgr0)" +readonly txtbold="$(tput bold)" +readonly txtblack="$(tput setaf 0)" +readonly txtred="$(tput setaf 1)" +readonly txtgreen="$(tput setaf 2)" +readonly txtyellow="$(tput setaf 3)" +readonly txtblue="$(tput setaf 4)" +readonly txtpurple="$(tput setaf 5)" +readonly txtcyan="$(tput setaf 6)" +readonly txtwhite="$(tput setaf 7)" # unicode "✗" -readonly fancyx='\342\234\227' +readonly fancyx='\342\234\227' # unicode "✓" -readonly checkmark='\342\234\223' +readonly checkmark='\342\234\223' # PASS="\e[1m\e[38;5;2m✔\e[m" # FAIL="\e[1m\e[38;5;1m✘\e[m" readonly PASS="${txtbold}${txtgreen}${checkmark}${txtreset}" @@ -41,7 +41,7 @@ RC_FILE=".hasrc" __dynamic_detect(){ cmd="${1}" params="${2}" - version=$(eval "${cmd}" "${params}" "2>&1" | grep -Eo "${REGEX_SIMPLE_VERSION}" | head -1) + version=$( eval "${cmd}" "${params}" "2>&1" | grep -Eo "${REGEX_SIMPLE_VERSION}" | head -1) status=$? } @@ -60,28 +60,33 @@ __dynamic_detect-v(){ __dynamic_detect "${1}" "-v" } +# commands that use `-V` flag +__dynamic_detect-V(){ + __dynamic_detect "${1}" "-V" +} + # commands that use `version` argument __dynamic_detect-arg_version(){ __dynamic_detect "${1}" "version" } - ## the main function __detect(){ name="${1}" - # setup aliases maps commonly used name to exact command name + # setup aliases - maps commonly used name to exact command name case ${name} in - golang) command="go" ;; - jre) command="java" ;; - jdk) command="javac" ;; - nodejs) command="node" ;; - goreplay) command="gor";; - httpie) command="http";; - homebrew) command="brew";; - awsebcli) command="eb";; - awscli) command="aws";; - *) command=${name} ;; + golang ) command="go" ;; + jre ) command="java" ;; + jdk ) command="javac" ;; + nodejs ) command="node" ;; + goreplay ) command="gor" ;; + httpie ) command="http" ;; + homebrew ) command="brew" ;; + awsebcli ) command="eb" ;; + awscli ) command="aws" ;; + *coreutils|linux*utils) command="gnu_coreutils" ;; + * ) command=${name} ;; esac case "${command}" in @@ -94,6 +99,10 @@ __detect(){ vim|emacs|nano|subl) __dynamic_detect--version "${command}" ;; bats|tree|ack|autojump) __dynamic_detect--version "${command}" ;; jq|ag|brew) __dynamic_detect--version "${command}" ;; + apt|apt-get|aptitude) __dynamic_detect--version "${command}" ;; + sed|awk|grep|file|sudo) __dynamic_detect--version "${command}" ;; + gzip|xz|unar|bzip2) __dynamic_detect--version "${command}" ;; + tar|pv) __dynamic_detect--version "${command}" ;; R) __dynamic_detect--version "${command}" ;; node|npm|yarn) __dynamic_detect--version "${command}" ;; @@ -105,45 +114,56 @@ __detect(){ lein) __dynamic_detect--version "${command}" ;; aws|eb|sls|gcloud) __dynamic_detect--version "${command}" ;; + # commands that need -v flag + unzip) __dynamic_detect-v "${command}" ;; + + # commands that need -V flag + ab) __dynamic_detect-V "${command}" ;; + # commands that need -version flag ant|java|javac) __dynamic_detect-version "${command}" ;; scala|kotlin) __dynamic_detect-version "${command}" ;; # commands that need version arg - hugo) __dynamic_detect-arg_version "${command}" ;; + go|hugo) __dynamic_detect-arg_version "${command}" ;; ## Example of commands that need custom processing - ## go needs version arg - go) - version=$(go version 2>&1| grep -Eo "${REGEX_SIMPLE_VERSION}" | head -1) - status=$? - ;; ## TODO cleanup, currently need to add extra space in regex, otherwise the time gets selected gulp) - version=$(gulp --version 2>&1| grep -Eo " ${REGEX_SIMPLE_VERSION}" | head -1) - status=$? - ;; - - ## ab uses -V flag - ab) - version=$(ab -V 2>&1 | grep -Eo "${REGEX_SIMPLE_VERSION}" | head -1) + version=$( gulp --version 2>&1| grep -Eo " ${REGEX_SIMPLE_VERSION}" | head -1) status=$? ;; ## gor returns version but does not return normal status code, hence needs custom processing gor) - version=$(gor version 2>&1 | grep -Eo "${REGEX_SIMPLE_VERSION}" | head -1) + version=$( gor version 2>&1 | grep -Eo "${REGEX_SIMPLE_VERSION}" | head -1) if [ $? -eq 1 ]; then status=0; else status=1; fi ;; sbt) - version=$(sbt about 2>&1 | grep -Eo "([[:digit:]]{1,4}\.){2}[[:digit:]]{1,4}" | head -1) + version=$( sbt about 2>&1 | grep -Eo "([[:digit:]]{1,4}\.){2}[[:digit:]]{1,4}" | head -1) + status=$? + ;; + + ## use 'readlink' to test for GNU coreutils + # readlink (GNU coreutils) 8.28 + gnu_coreutils) __dynamic_detect--version readlink ;; + + ## hub uses --version but version string is on second line, or third if HUB_VERBOSE set + hub) + version=$( HUB_VERBOSE='' hub --version 2>&1 | sed -n 2p | grep -Eo "${REGEX_SIMPLE_VERSION}" | head -1) + status=$? + ;; + + ## zip uses -v but version string is on second line + zip) + version=$( zip -v 2>&1 | sed -n 2p | grep -Eo "${REGEX_SIMPLE_VERSION}" | head -1) status=$? ;; has) - version=$(has 2>&1 | grep -Eo "${REGEX_SIMPLE_VERSION}" | head -1) + version=$( has 2>&1 | grep -Eo "${REGEX_SIMPLE_VERSION}" | head -1) status=$? ;; @@ -162,19 +182,19 @@ __detect(){ if [ "$status" -eq "-1" ]; then ## When unsafe processing is not allowed, the -1 signifies printf '%b %s not understood\n' "${FAIL}" "${command}" - KO=$(($KO+1)) + KO=$(( KO+1 )) elif [ ${status} -eq 127 ]; then ## command not installed printf '%b %s\n' "${FAIL}" "${command}" - KO=$(($KO+1)) + KO=$(( KO+1 )) elif [ ${status} -eq 0 ] || [ ${status} -eq 141 ]; then ## successfully executed printf "%b %s %b\n" "${PASS}" "${command}" "${txtbold}${txtyellow}${version}${txtreset}" - OK=$(($OK+1)) + OK=$(( OK+1 )) else ## as long as its not 127, command is there, but we might not have been able to extract version printf '%b %s\n' "${PASS}" "${command}" - OK=$(($OK+1)) + OK=$(( OK+1 )) fi } #end __detect @@ -203,13 +223,13 @@ else ## for all while read -r line; do __detect "${line}" - done <<<"$(grep -Ev "^\s*(#|$)" "${RC_FILE}" )" + done <<<"$( grep -Ev "^\s*(#|$)" "${RC_FILE}" )" fi ## max status code that can be returned MAX_STATUS_CODE=126 - if [[ "$KO" -gt "${MAX_STATUS_CODE}" ]]; then + if [[ "${KO}" -gt "${MAX_STATUS_CODE}" ]]; then exit "${MAX_STATUS_CODE}" else exit "${KO}"