Compare commits

...

63 Commits

Author SHA1 Message Date
Peter Dave Hello 430d0854f2
Add more "Build and Compile" tools (#77) 2023-10-16 12:04:16 +05:30
Peter Dave Hello 43a6c97bcd
Refine git clone commands in Dockerfiles (#76)
Adjusted git clone commands in Dockerfiles to use `--depth=1`. This
optimizes the cloning process by fetching a limited history, thereby
saving resources and time.
2023-10-16 12:04:01 +05:30
Arkoprabho Chakraborti 61032d63f1
Fixes shellcheck warnings (#75)
* MODIFY: Declare and assign separately to avoid masking return values

For Shellcheck SC2155

* DELETE: Unused variables

* MODIFY: Double quote to prevent globbing and word splitting

Addresses SC2086

* DELETE: Remove unused variable

Addresses SC2034

* MODIFY: Remove use of expr

Addresses SC2003 and SC2004

* Remove spaces around COLOR options

Thanks to @cclaus
https://github.com/kdabir/has/pull/75#discussion_r1301313069

Co-authored-by: Christian Clauss <cclauss@me.com>

---------

Co-authored-by: Christian Clauss <cclauss@me.com>
2023-08-28 14:04:58 +05:30
Kunal Dabir a2d7c083c7 call it a v1.5.0 2023-07-28 20:49:10 +05:30
Kunal Dabir abb3decba7 minor refactoring but changes to CLI. add pnpm
- use -v instead of -V for has version
- use '-' in color-<auto|always|never> instead of '_'
2023-07-28 20:02:21 +05:30
Maksym Bilyk 54c334962c
feature_58: Add support for detecting the stdout (#63)
Resolve whether decoration (color) is needed based on flags --color_{auto,never,always} (auto is default).
If --color_auto is passed, text decoration will ocurr only to the stdout.
If --color_{never,always} is passed, will or will not occur in any case.
2023-07-28 19:46:39 +05:30
wzy 8b43040ce2
Support detecting stdout, Fix #58 (#66) 2023-07-28 19:41:40 +05:30
Alex Meyer c12a38bbdd
bugfixes and more tools (#61)
- `cmake`
- `openssh`/`ssh`

- `openssl` now includes letters at the end (eg. "1.1.1j")
- `has` didn't actually detect its own version
2023-07-28 19:05:54 +05:30
Kunal Dabir 49444b3179
Replace status badge 2023-07-28 19:00:34 +05:30
Christian Clauss 81a613a80c
Fix typo (#68)
* Fix typo

* Fix typo
2023-07-28 18:23:47 +05:30
Sylvain METAYER 0f965b7713
feat: add asdf installation instruction (#60) 2023-07-28 18:23:19 +05:30
Christian Clauss b6869f3f8d
Upgrade GitHub Actions (#67) 2023-07-28 18:22:11 +05:30
Christian Clauss fc3505077a
Delete .travis.yml (#69)
* Delete .travis.yml
* Travis to GHA
2023-07-28 18:21:15 +05:30
Talas 7446f451dd
Resolves #17 (#62)
* Update has

Add pip/pip3/firefox/gunzip

* Update has

Add tee/screen/sqlite3
2021-10-09 19:43:39 +05:30
Kunal Dabir 45fd56f557
bump version 2021-10-02 17:58:17 +05:30
Megumin 559e885f22
added support for more tools (#59)
Signed-off-by: meguminloli <meguminloli@protonmail.com>
2021-10-02 17:55:43 +05:30
Saager Mhatre 3ce21c9b2c
Update `uninstall` target in Makefile to respect env vars (#56)
`uninstall` now `rm`s from `${DESTDIR}${PREFIX}/bin` instead of
defaulting to `/usr/local/bin`
2021-10-02 17:55:25 +05:30
ptt-homme 868bc1aff6
add command options for help and version (#53)
* add command options for help and version

* fix tests
2021-04-25 14:16:16 +05:30
ptt-homme 1f28a53597
Add gpg package (#52)
* add GnuPG tools

* clean unneeded space in REGEX for gulp
2020-08-31 09:20:54 +05:30
ptt-homme 41149c3474
support composer (#51) 2020-08-31 09:20:38 +05:30
John-Michael Faircloth 45c4126659
fix TERM env assignment (#50)
incorrect assignment will result in error:

```has: line 14: =xterm: command not found```
2020-08-13 10:31:12 +05:30
Abhishek Anand Amralkar 99595431ff
* add hashicorp tools (#49)
- terraform
- packer
- consul
- nomad
- vagrant
2020-07-17 01:01:20 +05:30
Stephen Dolenc e8bdf2278c
fixing tests on ubuntu (#48)
* fixing tests: eb vesion and node packages

* downgrade netlify due to recent dependency errors

Co-authored-by: stepdo <local@stepdo@com>
2020-06-23 00:26:01 +05:30
Kunal Dabir 90d1817ab9
[WIP] change references (#46)
* change references

* Fix containers (#47)

* spelling

* remove extra xz install

* more references

* more stable strategy for installing node, npm

* more flexible version pinning for alpine

* syntax


Co-authored-by: Stephen Dolenc <sdolenc@users.noreply.github.com>
2020-06-21 10:30:55 +05:30
johan-ejstrud 3dd103126e
Add quite mode (#45)
Close #39

Contributed by: Johan Ejstrud <johan@ejstrud.com>
2020-06-20 20:16:48 +05:30
Stephen Dolenc 4ccd0177d4
Verify version for each command (#43)
Thanks to contributions by @sdolenc 

* docker files
* list tools for testing
* test packages
* github actions
* documentation
* ensure expected command count
* output count of individual tools tested
* test: eb rg podman subl brunch grunt gulp heroku netlify sls java brew gor, heroku, mvn, netlify, netlifyctl, php5,  gcloud, hub, coreutils, brunch, code, kotlin, sbt, go, ant, gem, rake, autojump, groovy, aws, perl6, sbt, scala, lein
2020-04-25 16:48:24 +05:30
Jordi Sola 31828ec6bf
Add podman to the list container runtimes (#42) 2020-03-18 11:49:15 +05:30
Cody Hiar 28484822d6 Fix bug when `gor` not installed (#41) 2019-10-08 09:00:12 +05:30
Kunal Dabir bdf7c2ffe6 add doc for contributing more tools 2019-07-24 17:56:04 +05:30
Ashish Mohite 197dce1d03 Support psql (#40)
support for pg client
2019-07-24 17:49:48 +05:30
Kunal Dabir b82d650df0 support docker 2019-07-24 17:29:01 +05:30
Kunal Dabir e9b78acff2 add more tools. wip:organize the tools in groups 2019-07-24 12:34:13 +05:30
Kunal Dabir a788fdf6b8
minor updates 2019-07-13 14:21:20 +05:30
Kunal Dabir 754a4e444c
update usage in scripts 2019-07-13 14:11:04 +05:30
Kunal Dabir e8028b18ad
add instructions for installation via brew for #30 2019-06-28 11:54:43 +05:30
Virgil c25ce0eea4 add coreutils and archiving utilities to known commands (#34)
*  🎨 follow Bats coding style for tests

- [x] renamed working directory variable to `HAS_TMPDIR`
- [x] explicitly calling has binary
- [x] Only using `[[ ]]` for regex matches

*  add coreutils and archiving utilities to known commands

- [x] command mapping `*coreutils` to `gnu_coreutils`
- [x]  add test for gnu_coreutils
- [x] 💚 add archiving packages to travis
- [x]  add test for archiving commands

*  add utilities `hub` and `zip`

- [x] add custom processing for `hub` and `zip`
- [x]  add tests for `hub` and `zip`

* adding restyled.io 🔧 config

*  add dynamic `-V` check

- [x] 👌 update 'ab' to use dynamic `-V`
- [x] 👌 update 'go' to use dynamic `arg version`
- [x] 🚨 general whitespace and whitespace in command subsitution
- [x] [SC2004] 🚨 `$`/`${}` is unnecessary on arithmetic variables.
- [x]  make `hub` test conditional on command found
2019-05-21 09:54:40 +05:30
Virgil 1f7dd61324 use apt addon in CI config (#33) 2019-05-20 23:18:08 +05:30
Virgil f5981b9145 Linting with shellcheck (#32)
* [SC2196] Use grep -Eegrep is non-standard and deprecated.

* [SC2086] Double quoting

Double quote to prevent globbing and word splitting.

* [SC2059] printf syntax

Don't use variables in the printf format string.
Use printf "..%s.." "$foo".

Update matching test to not assume whitespace is <space>

* [SC2002] Useless cat.

Consider 'cmd < file | ..' or 'cmd file | ..' instead.

* Update Unicode PASS/FAIL output

- [x] NEW variables for 'checkmark' and 'fancy x' and tput colours
  - [x] eg: `checkmark` = `\342\234\223`
  - [x] eg: `PASS=${txtbold}${txtgreen}${checkmark}${txtreset}`
  - [x] update `printf` statements to use `%b` for unicode variables.
- [x] Using readonly variables for
  - [x] $BINARY_NAME and $VERSION
  - [x] $PASS and $FAIL
- [x] refactor `.hasrc` file reading
  - [x] [SC2002] Useless cat
- [x] Add new BATS tests for Unicode output

* Repo scaffolding - shellscript filetype

- [x] Add shellscripts to `.editorconfig`
- [x] Add shellscripts to `.gitattributes`

* Refactored tests to use consistent unicode glyphs.
2019-04-23 12:08:38 +05:30
Kunal Dabir 53ab06dd03 Sadly, longopts like --verbose are not working on MacOS for cp, mkdir and rm. also -u was not supported for cp. alternative is rsync
https://superuser.com/questions/515373/cp-u-is-illegal-on-mac-what-are-the-alternatives
2019-04-04 16:14:00 +05:30
Virgil 36e11e238f Freshen Makefile and update README.md (#31)
Details:

* Freshen Makefile and update README.md

- [x] Add `sudo` to revelvant commands
- [x] Use named languages (bash) in fenced codeblocks
- [x] Use `console` in fenced codeblocks for command output

- [x] Use `install` with permission mode

The permissions on `has` were 777 from `git clone`.

- [x] Add option to use $PREFIX

What if I don't want to install to /usr/local/bin?

`make PREFIX=$HOME/.local install` now works.

- [x] Add `update` target for `git pull`
- [x] Include .PHONY targets

* Freshen Makefile and update README.md

- [x] Add `sudo` to revelvant commands
- [x] Use named languages (bash) in fenced codeblocks
- [x] Use `console` in fenced codeblocks for command output

- [x] Use `install` with permissions set

The permissions on `has` were 777 from `git clone`.

- [x] Add option to use $PREFIX

What if I don't want to install to /usr/local/bin?

`make PREFIX=$HOME/.local install` now works.

- [x] Add `update` target for `git pull`
- [x] Include .PHONY targets

- [x] Add tests for Makefile changes
- [x] Uses `bats` variables for directories

- ✓ make install creates a valid installation
- ✓ ..even if has is missing from directory
- ✓ make update runs git pull

* Update .hastest.bats

temp remove "git pull" check.

* Update travis to use bats-core 1.10

Do not be concerned about dirty working tree when running make update.

* Change version output to non-blinking.

* Update travis to use bats-core 1.10

Do not be concerned about dirty working tree when running make update.

* Working makefile

* Use `[[` and `@` for the $lines match.

* `make install` MacOS friendly again
2019-04-04 16:09:17 +05:30
Kunal Dabir 3b26771c8a
Update README.md 2018-12-03 09:17:49 +05:30
Kunal Dabir 33b0623497
Update README.md 2018-11-27 08:30:33 +05:30
Kunal Dabir 29e5c412d2 release v1.4.0 2018-11-18 17:37:01 +05:30
Kunal Dabir 10c906e7f8 fix regression created with #15. self verion had stopped working. now added a test for it 2018-11-17 11:05:14 +05:30
Kunal Dabir ef55e62fd0
detailed installation instructions 2018-11-17 10:51:49 +05:30
Kunal Dabir b1f41e786c
cleanup 2018-11-16 18:43:35 +05:30
Kunal Dabir 7dc6946d36
link this repo's .hasrc 2018-11-16 18:41:00 +05:30
Kunal Dabir d07a65596d #15 docs on project specific .hasrc 2018-11-16 17:19:46 +05:30
Kunal Dabir 33cbc13738 #15 update documentation for .hasrc file 2018-11-16 16:50:36 +05:30
Kunal Dabir 44a71cf023 #15 test for excluding comments (lines starting with a #) 2018-11-16 16:23:34 +05:30
Kunal Dabir 1702c991ce implements #15
support for .hasrc file in current working directory, from where
has command is fired. these commands are merged with cli args.
2018-11-16 16:01:11 +05:30
Kunal Dabir b60c239bc4 extract magic number into a variable 2018-11-16 12:12:37 +05:30
Kunal Dabir 79f3eb8bba fixes #27 return status code only upto 126 2018-11-16 07:58:05 +05:30
Kunal Dabir 27a6b24b1c update demo 2018-11-15 13:24:22 +05:30
Kunal Dabir 722a5495d6 bump to v1.3.0 2018-11-15 11:57:30 +05:30
Kunal Dabir 4e91758c00 colorize the version as well 2018-11-15 11:53:28 +05:30
Kunal Dabir 1ad2f05548 its possible because of many more 2018-11-15 10:44:33 +05:30
Jason Phan 4d1035e241 added: colors (#26)
Support Colors #12 

* added: colors

No more echo with variables in them. Think of it like the Holocaust... Never. Again.

* update: removed color codes from tests
2018-11-15 10:39:17 +05:30
Kunal Dabir 8674525059
Update LICENSE 2018-11-14 21:51:07 +05:30
Kunal Dabir 87959c9b70
Merge pull request #25 from danstewart/master
Add perl6 and kotlin
2018-10-26 18:37:35 +05:30
Dan Stewart 86e7a8c4d6 Add perl6 and kotlin 2018-10-26 13:25:51 +01:00
Kunal Dabir 75ccc96171
Merge pull request #24 from layik/patch-1
tiny: add R
2018-09-12 18:31:32 +05:30
Layik Hama 621616fe56
tiny: add R 2018-09-12 13:17:28 +01:00
23 changed files with 1384 additions and 175 deletions

View File

@ -1,19 +1,23 @@
# http://editorconfig.org/
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{sh,bash,bats}]
indent_size = 2
indent_style = space
# Markdown
[*.md]
indent_size = 4
trim_trailing_whitespace = false
# Makefiles always use tabs for indentation
[{Makefile, makefile}]
indent_style = tab
indent_size = 4

10
.gitattributes vendored
View File

@ -1 +1,9 @@
* text=auto
# Auto detect text files and perform LF normalization
# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
* text=auto eol=lf
#
# The above will handle all files NOT found below
#
*.sh text diff=sh eol=lf
*.bash text diff=sh eol=lf
*.md text eol=lf

52
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: CI
on: [push, pull_request]
jobs:
shellcheck:
name: shellcheck
runs-on: ubuntu-latest
steps:
- name: Clone Repo
uses: actions/checkout@v3
- name: Run Shellcheck
uses: ludeeus/action-shellcheck@1.1.0
test:
name: test
runs-on: ubuntu-latest
strategy:
matrix:
container:
- debian # uses debian:buster-20200327-slim which is debian 10.3
- ubuntu16 # uses ubuntu:xenial-20200212 which is ubuntu 16.04
container: kdabir/has-test-containers:${{ matrix.container }}
steps:
- name: Clone Repo
uses: actions/checkout@v3
- name: test
run: make test
shell: bash
test_all:
name: test_all
runs-on: ubuntu-latest
strategy:
matrix:
container:
- ubuntu # uses ubuntu:bionic-20200311 which is ubuntu 18.04
- alpine # uses bash:5.0.16 which is alpine 3.11
container: kdabir/has-test-containers:${{ matrix.container }}
steps:
- name: Clone Repo
uses: actions/checkout@v3
- name: test_all
run: bats -t ./tests/test_all_packages.bats
shell: bash

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
*~
.DS_Store
.idea
tmp-for-test/

6
.hasrc Normal file
View File

@ -0,0 +1,6 @@
# make to install/test the project
make
curl
git
# bats for testing
bats

View File

@ -1,30 +1,224 @@
#!/usr/bin/env bats
@test "works with single command check" {
run bash has git
INSTALL_DIR=
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 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 [[ -d "${HAS_TMPDIR}" ]]; then
rm -rf "${HAS_TMPDIR}"
fi
}
@test "invoking 'has' without arguments prints usage" {
run $has
[ "$status" -eq 0 ]
[[ $output == *"✔ git"* ]]
[ "${lines[0]}" = 'Usage: has [OPTION] <command-names>...' ]
[ "${lines[1]}" = 'Has checks the presence of various command line tools on the PATH and reports their installed version.' ]
[ "${lines[2]}" = 'Options:' ]
[ "${lines[3]}" = ' -q Silent mode' ]
[ "${lines[4]}" = ' -h, --help Display this help text and quit' ]
[ "${lines[5]}" = ' -V, --version Show version number and quit' ]
[ "${lines[6]}" = 'Examples: has git curl node' ]
}
@test "safely tells about tools not configured" {
run bash has foobar
@test "make install creates a valid installation" {
INSTALL_DIR="${HAS_TMPDIR}/.local"
cd "${BATS_TEST_DIRNAME}"
run make PREFIX="${INSTALL_DIR}" install
[ "$status" -eq 0 ]
[ -x "${INSTALL_DIR}/bin/has" ]
[ "$status" -eq 1 ]
[[ $output == *"✘ foobar not understood"* ]]
# has reads .hasrc from $PWD, so change anywhere else.
cd "${INSTALL_DIR}"
run "${INSTALL_DIR}/bin/has"
[ "$status" -eq 0 ]
[ "${lines[0]}" = 'Usage: has [OPTION] <command-names>...' ]
[ "${lines[1]}" = 'Has checks the presence of various command line tools on the PATH and reports their installed version.' ]
[ "${lines[2]}" = 'Options:' ]
[ "${lines[3]}" = ' -q Silent mode' ]
[ "${lines[4]}" = ' -h, --help Display this help text and quit' ]
[ "${lines[5]}" = ' -V, --version Show version number and quit' ]
[ "${lines[6]}" = 'Examples: has git curl node' ]
}
@test "env var lets override safety check" {
HAS_ALLOW_UNSAFE=y run bash has foobar
@test "..even if 'has' is missing from directory" {
if [[ -n $GITHUB_ACTION ]] || [[ -n $GITHUB_ACTIONS ]]; then
if grep -iq "ubuntu" /etc/issue; then
skip "todo: this test fails on ubuntu in CI"
fi
fi
INSTALL_DIR="${HAS_TMPDIR}/system_local"
cd "${BATS_TEST_DIRNAME}"
mv has has-been
run make PREFIX="${INSTALL_DIR}" install
[ "$status" -eq 0 ]
[ -x "${INSTALL_DIR}/bin/has" ]
cd "${BATS_TEST_DIRNAME}"
mv has-been has
}
@test "make update runs git fetch" {
cd "${BATS_TEST_DIRNAME}"
if [[ -z $GITHUB_ACTION ]] && [[ -z $GITHUB_ACTIONS ]]; then
skip "make update overwrites my git working tree"
elif grep -iq "ubuntu" /etc/issue; then
skip "todo: this test fails on ubuntu in CI"
fi
run make update
[ "$status" -eq 0 ]
[ "$(echo "${output}" | grep "git fetch --verbose")" ]
}
@test "works with single command check" {
run $has git
[ "$status" -eq 0 ]
[ "$(echo "${lines[0]}" | grep "git")" ]
}
@test "'has' warns about tools not configured" {
run $has foobar
[ "$status" -eq 1 ]
[[ $output == *"✘ foobar"* ]]
[ "$(echo "${output}" | grep ${fancyx} | grep "foobar not understood")" ]
}
@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")" ]
}
@test "status code reflects number of failed commands" {
HAS_ALLOW_UNSAFE=y run bash has foobar make git barbaz
HAS_ALLOW_UNSAFE=y run $has foobar bc git barbaz
[ "$status" -eq 2 ]
[[ $output == *"✘ foobar"* ]]
[[ $output == *"✘ barbaz"* ]]
[ "$(echo "${output}" | grep ${fancyx} | grep "foobar")" ]
[ "$(echo "${output}" | grep ${fancyx} | grep "barbaz")" ]
}
@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 ]
}
@test "loads commands from .hasrc file and excludes comments" {
printf "bash\n#comment\nmake\n" >> .hasrc
run $has
[ "$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 $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")" ]
}
@test "testing PASS output with unicode" {
run $has git
[ "$status" -eq 0 ]
[[ "printf '%b\n' ${lines[0]}" =~ '✓' ]]
}
@test "testing FAIL output with unicode" {
run $has foobar
[ "$status" -eq 1 ]
[[ "printf '%b\n' ${lines[0]}" =~ '✗' ]]
}
@test "fail count 3: testing output with and without unicode" {
run $has git foobar barbaz barfoo
[ "$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. Installation command can be found at the bottom of ./tests/containers/debian.Dockerfile"
fi
run $has hub git
[ "$status" -eq 0 ]
[ "$(echo "${lines[0]}" | grep "hub")" ]
[ "$(echo "${lines[1]}" | grep "git")" ]
[ ! "${lines[0]##*\ }" = "${lines[1]##*\ }" ]
}
@test "quiet mode" {
run $has -q git
[ "$status" -eq 0 ]
[ -z "${output}" ]
}
@test "quiet mode should print usage when no commands or .hasrc file" {
run $has -q
[ "$status" -eq 0 ]
[ "${lines[0]}" = 'Usage: has [OPTION] <command-names>...' ]
[ "${lines[1]}" = 'Has checks the presence of various command line tools on the PATH and reports their installed version.' ]
[ "${lines[2]}" = 'Options:' ]
[ "${lines[3]}" = ' -q Silent mode' ]
[ "${lines[4]}" = ' -h, --help Display this help text and quit' ]
[ "${lines[5]}" = ' -V, --version Show version number and quit' ]
[ "${lines[6]}" = 'Examples: has git curl node' ]
}
@test "status code in quiet mode still equal to number of failed commands" {
HAS_ALLOW_UNSAFE=y run $has -q foobar bc git barbaz
[ "$status" -eq 2 ]
[ -z "${output}" ]
}

24
.restyled.yaml Normal file
View File

@ -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

View File

@ -1,10 +0,0 @@
language: bash
before_install:
- sudo add-apt-repository ppa:duggan/bats --yes
- sudo apt-get update -qq
- sudo apt-get install -qq bats
install:
- sudo apt-get install -qq bc
script:
- make test

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014-2017 Kunal Dabir, Saager Mhatre
Copyright (c) 2014-2018 Kunal Dabir, Saager Mhatre and various contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@ -1,8 +1,40 @@
test:
# Makefile for has
# https://github.com/kdabir/has
# Sadly, longopts like --verbose are not working on MacOS for cp, mkdir and rm
# PREFIX is an environment variable.
# Use default value if not set.
ifeq ($(PREFIX),)
PREFIX := /usr/local
endif
test : has
bats .hastest.bats
install:
cp has /usr/local/bin/has
has :
# ensure 'has' in repo
git checkout --force -- has
# install 'has' in specified directory
install : has
chmod 755 has && \
mkdir -v -p $(DESTDIR)$(PREFIX)/bin && \
cp -v has $(DESTDIR)$(PREFIX)/bin/has
# update: has
update : update-fetch has
update-fetch : update-force
# update repo from upstream
git fetch --verbose --force
update-force :
# remove local repo 'has' to force update
rm -f has
uninstall :
rm -f $(DESTDIR)$(PREFIX)/bin/has
.PHONY: test install uninstall update
uninstall:
rm -f /usr/local/bin/has

211
README.md
View File

@ -1,83 +1,210 @@
# has
`has` checks presence of various command line tools on the path and also reports their installed version
`has` checks presence of various command line tools on the PATH and reports their installed version.
[![Build Status](https://travis-ci.org/kdabir/has.svg?branch=master)](https://travis-ci.org/kdabir/has)
[![Build Status](https://github.com/kdabir/has/actions/workflows/main.yml/badge.svg)](https://github.com/kdabir/has/actions/workflows/main.yml)
[![Open Source Helpers](https://www.codetriage.com/kdabir/has/badges/users.svg)](https://www.codetriage.com/kdabir/has)
[![demo](demo.svg)](demo.svg)
## Quick Start 🚴
## How ?
Just [install](#installing) the `has` script, (there is no dependency apart from `bash` itself). From the command line, pass the list of commands you want to check as arguments to `has`, for example:
Download the `has` file. There is no dependency apart from `bash` itself
```console
$ has node npm java git gradle
✔ node 8.2.1
✔ npm 5.3.0
✔ java 1.8.0
✔ git 2.14.1
✔ gradle 4.0.1
```
$ has node npm java git gradle
✔ node 8.2.1
✔ npm 5.3.0
✔ java 1.8.0
✔ git 2.14.1
✔ gradle 4.0.1
If everything is good `has` exits with status code `0`. The exit status code reflects number of commands **not found** on your path.
If everything is good `has` exits with status code `0`. The status code
reflects number of commands **not found** on your path.
$ has node go javac
✔ node 8.2.1
✔ go 1.8.3
✘ javac
```console
$ has node go javac
✔ node 8.2.1
✔ go 1.8.3
✘ javac
```
And echo the status:
$ echo $?
1
```console
$ echo $?
1
```
## Use `has` in scripts
`has` can be used in shell scripts to check presence of tool in very readable way
```bash
if has node
then echo you have what it takes 🎉
fi
```
**Pro Tip**: the `has` in above command can be replaced with the entire curl command for to ensure portability of script → `if curl -sL https://git.io/_has | bash -s node then ...`
## Installing
## Installing 🚀
Just download the `has` script in your path.
`has` is a single bash script that does it all. You can [download](https://raw.githubusercontent.com/kdabir/has/master/has) the script and make it available on your `$PATH`. However, to make it even simpler, just follow *one* of these methods.
git clone https://github.com/kdabir/has.git && cd has && make install
### Homebrew (MacOS) 🍺
Just run the following:
If you are lazy, you can run `has` directly off the internet as well:
```bash
brew install kdabir/tap/has
```
curl -sL https://git.io/_has | bash -s git node npm
✔ git 2.14.1
✔ node 8.2.1
✔ npm 5.3.0
### Cloning the Repo
Just execute the following command in a terminal: it clones `has` repo and installs it into your path.
And if that's too much of typing every time, setup an alias
alias has="curl -sL https://git.io/_has | bash -s"
```bash
git clone https://github.com/kdabir/has.git && cd has && sudo make install
```
For a non-root installation:
```bash
git clone https://github.com/kdabir/has.git
cd has
make PREFIX=$HOME/.local install
```
To update just do a `git fetch` or `make update` followed by the appropriate `make install` command.
### Downloading to a file
```bash
curl -sL https://git.io/_has > /usr/local/bin/has
```
```bash
curl -sL https://git.io/_has | sudo tee /usr/local/bin/has >/dev/null
```
These commands are safe to be called multiple times as well (to update `has`)
### asdf users
```
asdf plugin add has https://github.com/sylvainmetayer/asdf-has
asdf install has 1.4.0
```
### Running directly off the Internet
If you are lazy, you can run `has` directly off the Internet as well:
```console
curl -sL https://git.io/_has | bash -s git node npm
✔ git 2.17.1
✔ node 11.11.0
✔ npm 6.7.0
```
**ProTip**: if that's too much typing every time, setup an alias in your `.bashrc`/`.zshrc` file:
```.bashrc
alias has="curl -sL https://git.io/_has | bash -s"
```
And use it
$ has git
✔ git 2.14.1
```console
$ has git
✔ git 2.17.1
$ type has
has is aliased to `curl -sL https://git.io/_has | bash -s'
```
## command not understood by has?
## Command not understood by has?
Let's say `$ has foobar` returns `foobar not understood`, because `has` may not have whitelisted `foobar`.
In such cases, pass `HAS_ALLOW_UNSAFE=y has foobar`. This is should still check for existance of `foobar` and tries to detect version as well.
In such cases, pass `HAS_ALLOW_UNSAFE=y has foobar`. This should still check for existence of `foobar` and tries to detect version as well.
> the value must exactly be `y` for it to work.
## Demo
## The `.hasrc` file
[![asciicast](https://asciinema.org/a/135790.png)](https://asciinema.org/a/135790)
`has` looks for `.hasrc` file in the directory from where `has` command is issued. This file can contain commands that `has`
will check for. List one command per line. Lines starting with `#` are treated as comments.
Following is example of `.hasrc` file:
```hs
# tools
git
curl
# interpreters
ruby
node
```
When `has` is run in directory containing this file, it produces:
```console
$ has
✔ git 2.19.1
✔ curl 7.54.0
✔ ruby 2.3.1
✔ node 10.7.0
```
Also, CLI arguments passed to `has` are additive to `.hasrc` file. For example, in the same dir, if the following command is fired,
`has` checks for both commands passed from cli args and provided in `.hasrc` file.
```bash
$ has java
✔ java 11.0.1
✔ git 2.19.1
✔ curl 7.54.0
✔ ruby 2.3.1
✔ node 10.7.0
```
**Pro Tip**: commit `.hasrc` file in root of your project. This can work as a quick check for confirming presence all command
line tools required to build and run your project.
On machines that don't even have `has` installed, your project's `.hasrc` is honored by this command:
`curl -sL https://git.io/_has | bash -s`
> take a look at [.hasrc](https://github.com/kdabir/has/blob/master/.hasrc) file for this repo.
## Contributing
[![Build Status](https://travis-ci.org/kdabir/has.svg?branch=has)](https://travis-ci.org/kdabir/has)
1. Star the repo, tweet about it, spread the word
1. Star the repo, tweet about it, spread the word
2. Update the documentation (i.e. the README file)
3. Adding support for more commands
4. Adding more features to `has`
## Adding more tools
#### ♥
The current list of supported packages can be viewed with `bash tests/packages_all.sh`
If the command you wish to include supports any of `-v`, `--version`, `-version`, `version`, `-V` then you can find
corresponding function which can be called to check presence and extract version. However, for many tools version
extraction may not work and you will need to add custom parsing of command's output. The `has` script is commented
to guide developers about what needs to be done to add more tools.
`/tests/test_all_packages.bats` will test every package has supports. This includes newly added commands so please add new packages to
- `alpine.Dockerfile` and `ubuntu.Dockerfile` to install the tool OR
- `packages_alpine_skip.txt` and `packages_ubuntu_skip.txt` to exclude the package from the tests
## Adding Features
If you are contributing a feature, please ensure to check current tests. Add test cases for your feature. Tests are
executed using the excellent [bats](https://github.com/bats-core/bats-core) testing framework. Add tests and run `make test`
Raise the PR and **make sure the tests pass** on [GitHub Actions](https://github.com/kdabir/has/actions).
### ♥

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 31 KiB

436
has
View File

@ -1,13 +1,52 @@
#!/usr/bin/env bash
## source: https://github.com/kdabir/has
## To add more tools, search for "__detect" function in this file
## Important so that version is not extracted for failed commands (not found)
set -o pipefail
## constant - symbols for success failure
PASS='✔'
FAIL='✘'
readonly BINARY_NAME="has"
readonly VERSION="v1.5.0"
## constants - symbols for success failure
if [[ ! -t 1 ]]; then
TERM="dumb"
elif [[ -z $TERM ]]; then
TERM="xterm"
fi
txtreset="$(tput -T "$TERM" sgr0)"
readonly txtreset
txtbold="$(tput -T "$TERM" bold)"
readonly txtbold
txtred="$(tput -T "$TERM" setaf 1)"
readonly txtred
txtgreen="$(tput -T "$TERM" setaf 2)"
readonly txtgreen
txtyellow="$(tput -T "$TERM" setaf 3)"
readonly txtyellow
# unicode "✗"
readonly fancyx='\342\234\227'
# unicode "✓"
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}"
readonly FAIL="${txtbold}${txtred}${fancyx}${txtreset}"
## These variables are used to control decoration of output
COLOR_AUTO="auto"
COLOR_NEVER="never"
COLOR_ALWAYS="always"
COLOR_OPTS=("${COLOR_AUTO} ${COLOR_NEVER} ${COLOR_ALWAYS}")
COLOR="${COLOR_AUTO}"
COLOR_PREFIX="--color"
## These variables are used to keep track of passed and failed commands
OK=0
@ -16,121 +55,253 @@ KO=0
## Regex to extract simple version - extracts numeric sem-ver style versions
REGEX_SIMPLE_VERSION="([[:digit:]]+\.?){2,3}"
## name of RC file that can contain list of tools to be checked
RC_FILE=".hasrc"
## try to extract version by executing $1 with $2 arg
_usage() {
cat <<EOF
Usage: ${BINARY_NAME} [OPTION] <command-names>...
Has checks the presence of various command line tools on the PATH and reports their installed version.
Options:
-q Silent mode
-h, --help Display this help text and quit
-V, --version Show version number and quit
Examples: ${BINARY_NAME} git curl node
EOF
}
_version() {
printf '%s\n' "${VERSION}"
}
# function to parse color option
_set_color() {
local found=0;
for opt in "${COLOR_OPTS[@]}"; do
[ "${1}" == "${COLOR_PREFIX}-${opt}" ] && COLOR="${opt}" && found=1 && break
done
[ ${found} -eq 1 ] || >&2 echo "Error: wrong flag ${1}"
}
# try to extract version by executing "${1}" with "${2}" arg
# command name to be executed is dynamically passed to this function as ${1}
# arg to ${1} is passed in ${2}
__dynamic_detect(){
cmd=$1
params=$2
version=$(eval ${cmd} ${params} "2>&1" | egrep -o "$REGEX_SIMPLE_VERSION" | head -1)
cmd="${1}"
params="${2}"
version=$( eval "${cmd}" "${params}" "2>&1" | grep -Eo "${REGEX_SIMPLE_VERSION}" | head -1)
status=$?
}
# commands that use `--version` flag
__dynamic_detect--version(){
__dynamic_detect $1 "--version"
__dynamic_detect "${1}" "--version"
}
## commands that use `-version` flag
# commands that use `-version` flag
__dynamic_detect-version(){
__dynamic_detect $1 "-version"
__dynamic_detect "${1}" "-version"
}
# commands that use `-v` flag
__dynamic_detect-v(){
__dynamic_detect $1 "-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"
__dynamic_detect "${1}" "version"
}
## the main function
__detect(){
name=$1
name="${1}"
# setup aliases maps commonly used name to exact command name
# default values (for case when decoration applied)
fail=${FAIL}
pass=${PASS}
wrapper_beg="${txtbold}${txtyellow}"
wrapper_end="${txtreset}"
# 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} ;;
rust ) command="rustc" ;;
ssl ) command="openssl" ;;
openssh ) command="ssh" ;;
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" ;;
postgresql ) command="psql" ;;
*coreutils|linux*utils) command="gnu_coreutils" ;;
* ) command=${name} ;;
esac
case ${command} in
case "${command}" in
# commands that need --version flag
bash|zsh) __dynamic_detect--version ${command} ;;
git|hg|svn|bzr) __dynamic_detect--version ${command} ;;
gcc|make) __dynamic_detect--version ${command} ;;
curl|wget|http) __dynamic_detect--version ${command} ;;
vim|emacs|nano|subl) __dynamic_detect--version ${command} ;;
bats|tree|ack|autojump) __dynamic_detect--version ${command} ;;
jq|ag|brew) __dynamic_detect--version ${command} ;;
node|npm|yarn) __dynamic_detect--version ${command} ;;
grunt|brunch) __dynamic_detect--version ${command} ;;
ruby|gem|rake|bundle) __dynamic_detect--version ${command} ;;
python|python3) __dynamic_detect--version ${command} ;;
perl|php|php5) __dynamic_detect--version ${command} ;;
groovy|gradle|mvn) __dynamic_detect--version ${command} ;;
lein) __dynamic_detect--version ${command} ;;
aws|eb|sls|gcloud) __dynamic_detect--version ${command} ;;
## Shells
bash|zsh|fish) __dynamic_detect--version "${command}" ;;
## VCS
git|hg|svn|bzr) __dynamic_detect--version "${command}" ;;
## Http
curl|wget|http) __dynamic_detect--version "${command}" ;;
## Editors
vim|emacs|nano) __dynamic_detect--version "${command}" ;;
subl|code|codium) __dynamic_detect--version "${command}" ;;
## File system search and navigation
jq) __dynamic_detect--version "${command}" ;;
ag|ack|rg) __dynamic_detect--version "${command}" ;;
tree|autojump) __dynamic_detect--version "${command}" ;;
## OS Package managers
apt|apt-get|aptitude) __dynamic_detect--version "${command}" ;;
brew) __dynamic_detect--version "${command}" ;;
## System tools
sed|awk|grep|file|sudo) __dynamic_detect--version "${command}" ;;
gzip|xz|unar|bzip2) __dynamic_detect--version "${command}" ;;
tar|pv) __dynamic_detect--version "${command}" ;;
gunzip) __dynamic_detect--version "${command}" ;;
tee) __dynamic_detect--version "${command}" ;;
screen) __dynamic_detect-v "${command}" ;;
# Container runtimes
docker|podman) __dynamic_detect--version "${command}" ;;
## Database CLI
psql) __dynamic_detect--version "${command}" ;;
sqlite3) __dynamic_detect-version "${command}" ;;
# commands that need -version flag
ant|java|javac) __dynamic_detect-version ${command} ;;
scala) __dynamic_detect-version ${command} ;;
########### Programming languages Build tools & Package managers ###########
## Build and Compile
gcc|make|cmake|bats) __dynamic_detect--version "${command}" ;;
g++|clang|ccache) __dynamic_detect--version "${command}" ;;
ninja) __dynamic_detect--version "${command}" ;;
composer) __dynamic_detect-V "${command}" ;;
pip|pip3|conda) __dynamic_detect-V "${command}" ;;
lein|gradle|mvn) __dynamic_detect--version "${command}" ;;
grunt|brunch) __dynamic_detect--version "${command}" ;;
gem|rake|bundle) __dynamic_detect--version "${command}" ;;
npm|yarn|pnpm) __dynamic_detect--version "${command}" ;;
act) __dynamic_detect--version "${command}" ;;
## Scripting Language / runtime
ruby|R|python|python3) __dynamic_detect--version "${command}" ;;
perl|perl6|php|php5) __dynamic_detect--version "${command}" ;;
groovy|node) __dynamic_detect--version "${command}" ;;
# JVM tools that need -version flag
ant) __dynamic_detect-version "${command}" ;;
java|javac|scala|kotlin) __dynamic_detect-version "${command}" ;;
## Rust
rustc|cargo) __dynamic_detect--version "${command}" ;;
## Cloud Tools
aws|eb|sls|gcloud) __dynamic_detect--version "${command}" ;;
heroku) __dynamic_detect--version "${command}" ;;
netlify) __dynamic_detect--version "${command}" ;;
netlifyctl) __dynamic_detect-arg_version "${command}" ;;
## GPG Tools
gpg|gpgconf|gpg-agent) __dynamic_detect--version "${command}" ;;
gpg-connect-agent) __dynamic_detect--version "${command}" ;;
gpgsm) __dynamic_detect--version "${command}" ;;
## Hashicorp Tools
terraform|packer) __dynamic_detect--version "${command}" ;;
vagrant|consul) __dynamic_detect--version "${command}" ;;
nomad) __dynamic_detect--version "${command}" ;;
## Browsers
firefox) __dynamic_detect-v "${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 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| egrep -o "$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| egrep -o " $REGEX_SIMPLE_VERSION" | head -1)
status=$?
;;
## ab uses -V flag
ab)
version=$(ab -V 2>&1 | egrep -o "$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 | egrep -o "$REGEX_SIMPLE_VERSION" | head -1)
if [ $? -eq 1 ]; then status=0; else status=1; fi
version=$( gor version 2>&1 | grep -Eo "${REGEX_SIMPLE_VERSION}" | head -1)
if [ $? -eq 1 ]; then status=0; else status=127; fi
;;
sbt)
version=$(sbt about 2>&1 | egrep -o "([[:digit:]]{1,4}\.){2}[[:digit:]]{1,4}" | head -1)
status=$?
version=$( sbt about 2>&1 | grep -Eo "([[:digit:]]{1,4}\.){2}[[:digit:]]{1,4}" | head -1)
status=$?
;;
# openssl can have a letter at the end of the version number (eg. "1.1.1j")
openssl)
version=$( openssl version 2>&1 | grep -Eo "${REGEX_SIMPLE_VERSION}[[:alnum:]]*" |head -1)
status=$?
;;
# openssh version output has a lot going on
ssh)
version=$(ssh -V 2>&1 |grep -Eo "OpenSSH_${REGEX_SIMPLE_VERSION}[[:alnum:]]*" |cut -d'_' -f2)
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 | egrep -o "$REGEX_SIMPLE_VERSION" | head -1)
version=$( has -v 2>&1 | grep -Eo "${REGEX_SIMPLE_VERSION}" | head -1)
status=$?
;;
*)
## Can allow dynamic checking here, i.e. checking commands that are not listed above
if [[ "${HAS_ALLOW_UNSAFE}" == "y" ]]; then
__dynamic_detect--version ${command}
__dynamic_detect--version "${command}"
## fallback checking based on status!=127 (127 means command not found)
## TODO can check other type of supported version-checks if status was not 127
else
@ -140,47 +311,114 @@ __detect(){
;;
esac
if [ "$status" -eq "-1" ]; then ## When unsafe processing is not allowed, the -1 signifies
echo ${FAIL} ${command} "not understood"
KO=$(($KO+1))
elif [ ${status} -eq 127 ]; then ## command not installed
echo ${FAIL} ${command}
KO=$(($KO+1))
elif [ ${status} -eq 0 ] || [ ${status} -eq 141 ]; then ## successfully executed
echo ${PASS} ${command} ${version}
OK=$(($OK+1))
else ## as long as its not 127, command is there, but we might not have been able to extract version
echo ${PASS} ${command}
OK=$(($OK+1))
if [ "${COLOR}" == "${COLOR_NEVER}" ] || [[ ! -t 1 && "${COLOR}" == "${COLOR_AUTO}" ]]; then
pass=${checkmark}
fail=${fancyx}
wrapper_beg=""
wrapper_end=""
fi
}
if [ "$status" -eq "-1" ]; then ## When unsafe processing is not allowed, the -1 signifies
printf '%b %s not understood\n' "${fail}" "${command}" > "$OUTPUT"
KO=$(( KO+1 ))
elif [ ${status} -eq 127 ]; then ## command not installed
printf '%b %s\n' "${fail}" "${command}" > "$OUTPUT"
KO=$(( KO+1 ))
# if no arguments passed to script
if [ "$#" -eq 0 ]; then
# print help
BINARY_NAME="has"
VERSION="v1.2.1"
echo "${BINARY_NAME} ${VERSION}"
echo "USAGE: ${BINARY_NAME} <command-names>.."
echo "EXAMPLE: ${BINARY_NAME} git curl node"
elif [ ${status} -eq 0 ] || [ ${status} -eq 141 ]; then ## successfully executed
printf "%b %s %b\n" "${pass}" "${command}" "${wrapper_beg}${version}${wrapper_end}" > "$OUTPUT"
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}" > "$OUTPUT"
OK=$(( OK+1 ))
fi
} #end __detect
OPTIND=1
OUTPUT=/dev/stdout
while getopts ":qhv-" OPTION; do
case "$OPTION" in
q)
OUTPUT=/dev/null
;;
h)
_usage
exit 0
;;
v)
_version
exit 0
;;
-)
[ $OPTIND -ge 1 ] && optind=$((OPTIND - 1)) || optind=$OPTIND
eval OPTION="\$$optind"
OPTARG=$(echo "$OPTION" | cut -d'=' -f2)
OPTION=$(echo "$OPTION" | cut -d'=' -f1)
case $OPTION in
--version)
_version
exit 0
;;
--help)
_usage
exit 0
;;
${COLOR_PREFIX}*)
_set_color "${OPTION}"
;;
*)
printf '%s: unrecognized option '%s'\n' "${BINARY_NAME}" "${OPTARG}"
_usage
exit 2
;;
esac
OPTIND=1
shift
;;
?)
printf '%s: unrecognized option '%s'\n' "${BINARY_NAME}" "${OPTARG}"
_usage
exit 2
;;
esac
done
shift $((OPTIND - 1))
if [ -s "${RC_FILE}" ]; then
HASRC="true"
fi
# if HASRC is not set AND no arguments passed to script
if [[ -z "${HASRC}" ]] && [ "$#" -eq 0 ]; then
_usage
else
# for each arg
# for each cli-arg
for cmd in "$@"; do
__detect $cmd
__detect "${cmd}"
done
# echo ${OK} / $(($OK+$KO))
exit ${KO}
## display found / total
# echo ${OK} / $(($OK+$KO))
## if HASRC has been set
if [[ -n "${HASRC}" ]]; then
## for all
while read -r line; do
__detect "${line}"
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
exit "${MAX_STATUS_CODE}"
else
exit "${KO}"
fi
fi

View File

@ -0,0 +1,10 @@
# has containers
Custom containers used for testing https://github.com/kdabir/has
| container image | status |
|------------------------------|--------|
| kdabir/has-test-containers:alpine | ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/kdabir/has-test-containers/alpine) |
| kdabir/has-test-containers:ubuntu | ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/kdabir/has-test-containers/ubuntu) |
| kdabir/has-test-containers:debian | ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/kdabir/has-test-containers/debian) |
| kdabir/has-test-containers:ubuntu16 | ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/kdabir/has-test-containers/ubuntu16) |

View File

@ -0,0 +1,142 @@
FROM bash:5.0.16
# already contains
# awk
# bash=5.0.16
# bzip2
# grep
# gzip=1.31.1
# sed=4.0
# tar=1.31.1
# unzip=6.00
# wget
# xz
RUN apk add --no-cache \
ack~=3.2.0 \
apache2-utils~=2.4 `# ab=2.3` \
apache-ant~=1.10.8 `# ant=1.10.8` \
build-base libffi-dev openssl-dev `# required for eb` \
curl~=7.67.0 \
bzr~=2.7.0 \
docker~=19.03.5 \
emacs~=26.3 \
file~=5.37 \
gcc~=9.2.0 \
git~=2.24.3 \
go~=1.13.10 \
gradle~=5.6.4 \
hugo~=0.61.0 \
jq=~1.6 \
make~=4.2.1 \
maven~=3.6.3 `# mvn=3.6.3` \
mercurial~=5.3.2 `# hg=5.3.2` \
nano~=4.6 \
ncurses~=6.1_p20200118 `#tput:todo` \
npm~=12.15.0 `# npm=6.13.4 # node=12.15.0` \
openjdk11~=11.0.5 `# java=11.0.5` \
perl~=5.30 ` # perl=30` \
php7~=7.3.18 `# php=7.3.18 ` \
postgresql~=12.2 `# psql=12.2` \
pv~=1.6.6 \
python~=2.7.18 \
python3-dev~=3.8.2 `#python3=3.8.2` \
R~=3.6.2 \
ruby~=2.6.6 `# gem=3.0.3` \
ruby-bundler~=2.0.2 `# bundle=2.0.2` \
ruby-bigdecimal ruby-json `# required for brew` \
ruby-rake~=2.6.6 `# rake=12.3.3` \
subversion~=1.12.2 `# svn=1.12.2` \
sudo~=1.8.31 \
tree~=1.8.0 \
vim~=8.2 \
yarn~=1.19.2 \
zip~=3.0 \
zsh~=5.7.1 && \
\
# required for brew and lein
ln -s $(which bash) /bin/bash && \
\
npm install --global --no-optional \
brunch@"=3.0.0" \
grunt-cli@"=1.3.2" \
gulp-cli@"=2.2.0" \
heroku@"=7.39.3" \
netlify-cli@"=2.33.0" \
serverless@"=1.67.3" `# sls=1.67.3` && \
\
autojump=22.5.3 && \
curl -L "https://github.com/wting/autojump/archive/release-v${autojump}.tar.gz" | tar xz && \
cd "autojump-release-v${autojump}" && \
SHELL=bash ./install.py && \
cd / && \
ln -s ~/.autojump/bin/autojump /usr/local/bin/autojump && \
\
commit="87b16eb" `# bats=1.2.0` && \
curl -L "https://github.com/bats-core/bats-core/tarball/${commit}" | tar xz && \
"bats-core-bats-core-${commit}/install.sh" /usr/local && \
\
brew=2.2.13 && \
git clone --depth=1 --branch ${brew} https://github.com/Homebrew/brew && \
eval $(brew/bin/brew shellenv) && \
ln -s /brew/bin/brew /usr/local/bin/brew && \
brew --version && \
\
eb=3.18.0-1 && \
curl -L "https://github.com/sdolenc/aws-elastic-beanstalk-cli/archive/${eb}.tar.gz" | tar xz && \
pip3 install ./aws-elastic-beanstalk-cli-${eb} && \
\
gcloud=289.0.0 && \
curl -L "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-${gcloud}-linux-x86_64.tar.gz" | tar xz && \
ln -s /google-cloud-sdk/bin/gcloud /usr/local/bin/gcloud && \
\
gor=1.0.0 && \
curl -L "https://github.com/buger/goreplay/releases/download/v${gor}/gor_${gor}_x64.tar.gz" | tar xz --directory /usr/local/bin && \
\
groovy=3.0.3 && \
curl -L "https://dl.bintray.com/groovy/maven/apache-groovy-binary-${groovy}.zip" -o /groovy.zip && \
unzip groovy.zip && rm groovy.zip && \
ln -s "/groovy-3.0.3/bin/groovy" /usr/local/bin/groovy && \
\
pip3 install \
awscli==1.18.43 `# aws=1.18.43` \
httpie==2.1.0 `# http=2.1.0` && \
\
hub=2.14.2 && \
curl -L "https://github.com/github/hub/releases/download/v${hub}/hub-linux-386-${hub}.tgz" | tar xz && \
ln -s "/hub-linux-386-${hub}/bin/hub" /usr/local/bin/hub && \
\
`# javac=11.0.5` && \
ln -s "/usr/lib/jvm/java-11-openjdk/bin/javac" /usr/local/bin/javac && \
\
kotlin=1.3.72 && \
curl -L "https://github.com/JetBrains/kotlin/releases/download/v${kotlin}/kotlin-compiler-${kotlin}.zip" -o /kotlin.zip && \
unzip kotlin.zip && rm kotlin.zip && \
ln -s /kotlinc/bin/kotlin /usr/local/bin/kotlin && \
ln -s /kotlinc/bin/kotlinc /usr/local/bin/kotlinc && \
\
netlifyctl=0.4.0 && \
curl -L "https://github.com/netlify/netlifyctl/releases/download/v${netlifyctl}/netlifyctl-linux-amd64-${netlifyctl}.tar.gz" | tar xz --directory /usr/local/bin && \
\
perl6=2020.02.1-04 && \
curl -L "https://github.com/nxadm/rakudo-pkg/releases/download/v${perl6}/rakudo-pkg-Alpine3.11_${perl6}_x86_64.apk" --output perl6.apk && \
apk add --allow-untrusted perl6.apk && rm perl6.apk && \
ln -s /opt/rakudo-pkg/bin/perl6 /usr/local/bin/perl6 && \
\
rg=12.0.1 && \
curl -L "https://github.com/BurntSushi/ripgrep/releases/download/${rg}/ripgrep-${rg}-x86_64-unknown-linux-musl.tar.gz" | tar xz && \
ln -s "/ripgrep-${rg}-x86_64-unknown-linux-musl/rg" /usr/local/bin/rg && \
\
sbt=1.3.4 && \
curl -L "https://piccolo.link/sbt-${sbt}.tgz" | tar xz && \
ln -s /sbt/bin/sbt /usr/local/bin/sbt && \
sbt --version && sbt --version && \
\
scala=2.12.11 && \
curl -L "https://downloads.lightbend.com/scala/${scala}/scala-${scala}.tgz" | tar xz && \
ln -s "/scala-${scala}/bin/scala" /usr/local/bin/scala && \
\
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
echo "http://dl-cdn.alpinelinux.org/alpine/v3.12/community" >> /etc/apk/repositories `# podman requires crun` && \
apk add --no-cache \
leiningen~=2.9.3 `# lein=2.9.3` \
podman~=1.9.3

View File

@ -0,0 +1,32 @@
FROM debian:buster-20200327-slim
# already contains
# apt
# apt-get
# bash
# grep
# gzip
# tar
RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install --no-install-recommends -y -qq \
bc \
pv \
unar \
make \
curl \
git \
unzip \
xz-utils `#xz` \
unar \
pv \
zip \
sudo \
file \
&& apt-get -y autoremove && apt-get -y clean && rm -rf /var/lib/apt/lists/*; \
\
#bats
commit="87b16eb"; \
curl -L "https://github.com/bats-core/bats-core/tarball/${commit}" | tar xz; \
"bats-core-bats-core-${commit}/install.sh" /usr/local; \
\
#hub
curl -fsSL https://github.com/github/hub/raw/master/script/get | bash -s 2.14.2

View File

@ -0,0 +1,147 @@
FROM ubuntu:bionic-20200311
# Updates path with node, npm, npx, and globally installed npm packages
ENV node=12.18.1
ENV PATH="${PATH}:/node-v${node}-linux-x64/bin"
# already contains
# apt=1.6.12
# apt-get=1.6.12
# awk
# bash=4.4.20
# bzip2=1.0.6
# gnu_coreutils=8.28
# grep=3.1
# gzip=1.6
# perl=26
# sed=4.4
# tar=1.29
# tput #todo
RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install --no-install-recommends -y -qq \
ack=2.22* \
ant=1.10.5* \
apache2-utils=2.4* `# ab=2.3` \
apt-utils `# todo` \
aptitude=0.8.10* \
autojump=22.5.0* \
awscli=1.14.44* `# aws=1.14.44` \
build-essential zlib1g-dev libssl-dev libncurses-dev libffi-dev libsqlite3-dev libreadline-dev libbz2-dev `# required for eb` \
bzr=2.7.0+bzr6622-10 `# bzr=2.8.0` \
curl=7.58.0* \
docker.io=19.03.6* `# docker=19.03.6` \
emacs=47.0 `# emacs=25.2.2` \
file=1:5.32* \
gcc=4:7.4.0-1ubuntu2.3 `# gcc=7.5.0` \
git=1:2.17.1* \
gpg-agent `# todo:apt-key` \
gradle=4.4.1* \
groovy=2.4.16* \
httpie=0.9.8* `# http=0.9.8` \
hugo=0.40.1* \
jq=1.5* \
leiningen=2.8.1* `# lein=2.8.1` \
locales `# required for brew` \
make=4.1* \
maven=3.6.0* `# mvn=3.6.0` \
mercurial=4.5.3* `# hg=4.5.3` \
nano=2.9.3* \
openjdk-11-jdk-headless=11.0.7* `# java=11.0.7 # javac=11.0.7` \
perl6=6.c-1 `# perl6=2018.03` \
php=1:7.2+60ubuntu1 `# php=7.2.24` \
postgresql-client=10+190* `# psql=10.12` \
pv=1.6.6* \
python=2.7.15~rc1-1 `# python=2.7.17` \
python3=3.6.7-1~18.04 `# python3=3.6.9` \
r-cran-littler=0.3.3* `# R=3.4.4` \
rake=12.3.1* \
ruby=1:2.5.1 \
ruby-bundler=1.16.1* `# bundle=1.16.1` \
rubygems `# gem=2.7.6` \
scala=2.11.12* \
silversearcher-ag=2.1.0* `# ag=2.1.0` \
software-properties-common `# todo:add-apt-repository` \
subversion=1.9.7* `# svn=1.9.7` \
sudo=1.8.21* \
tree=1.7.0* \
unar=1.10.1* \
unzip=6.0-21ubuntu1 `# unzip=6.00` \
vim=2:8.0* \
wget=1.19.4* \
xz-utils=5.2.2* `# xz=5.2.2` \
yarn `# yarn=0.32` \
zip=3.0* \
zsh=5.4.2* && \
\
commit="87b16eb" `# bats=1.2.0` && \
curl -L "https://github.com/bats-core/bats-core/tarball/${commit}" | tar xz && \
"bats-core-bats-core-${commit}/install.sh" /usr/local && \
\
brew=2.2.13 && \
git clone --depth=1 --branch ${brew} https://github.com/Homebrew/brew && \
locale-gen en_US en_US.UTF-8 && \
eval $(brew/bin/brew shellenv) && \
ln -s /brew/bin/brew /usr/local/bin/brew && \
brew --version && \
\
code=1.44.2 && \
curl -L "https://az764295.vo.msecnd.net/stable/ff915844119ce9485abfe8aa9076ec76b5300ddd/code_${code}-1587059832_amd64.deb" --output code_${code}.deb && \
`# installing missing dependencies requires apt update which is the first done above` \
dpkg -i code_${code}.deb || apt-get install -f -y && \
rm -f code_${code}.deb && \
\
commit="102025c" `# eb=3.18.1` && \
curl -L "https://github.com/aws/aws-elastic-beanstalk-cli-setup/tarball/${commit}" | tar xz && \
"aws-aws-elastic-beanstalk-cli-setup-${commit}/scripts/bundled_installer" && \
ln -s /root/.ebcli-virtual-env/executables/eb /usr/local/bin/eb && \
\
gcloud=289.0.0 && \
curl -L "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-${gcloud}-linux-x86_64.tar.gz" | tar xz && \
ln -s /google-cloud-sdk/bin/gcloud /usr/local/bin/gcloud && \
\
gor=1.0.0 && \
curl -L "https://github.com/buger/goreplay/releases/download/v${gor}/gor_${gor}_x64.tar.gz" | tar xz --directory /usr/local/bin && \
\
hub=2.14.2 && \
curl -fsSL "https://github.com/github/hub/raw/master/script/get" | bash -s ${hub} && \
\
kotlin=1.3.72 && \
curl -L "https://github.com/JetBrains/kotlin/releases/download/v${kotlin}/kotlin-compiler-${kotlin}.zip" -o /kotlin.zip && \
unzip kotlin.zip && rm kotlin.zip && \
ln -s /kotlinc/bin/kotlin /usr/local/bin/kotlin && \
ln -s /kotlinc/bin/kotlinc /usr/local/bin/kotlinc && \
\
netlifyctl=0.4.0 && \
curl -L "https://github.com/netlify/netlifyctl/releases/download/v${netlifyctl}/netlifyctl-linux-amd64-${netlifyctl}.tar.gz" | tar xz --directory /usr/local/bin && \
\
rg=12.0.1 && \
curl -L "https://github.com/BurntSushi/ripgrep/releases/download/${rg}/ripgrep-${rg}-x86_64-unknown-linux-musl.tar.gz" | tar xz && \
ln -s "/ripgrep-${rg}-x86_64-unknown-linux-musl/rg" /usr/local/bin/rg && \
\
sbt=1.3.4 && \
curl -L "https://piccolo.link/sbt-${sbt}.tgz" | tar xz && \
ln -s /sbt/bin/sbt /usr/local/bin/sbt && \
sbt --version && sbt --version && \
\
add-apt-repository -y ppa:longsleep/golang-backports `#go` && \
add-apt-repository -y ppa:ondrej/php `#php5` && \
add-apt-repository -y ppa:projectatomic/ppa `#podman` && \
wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | sudo apt-key add - `#subl` && \
add-apt-repository -y "deb https://download.sublimetext.com/ apt/stable/" `#subl` && \
apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install --no-install-recommends -y -qq \
golang-go=2:1.14* `# go=1.14.2` \
php5.6=5.6.40* `# php5=5.6.40` \
podman=1.6.2* \
sublime-text=3211 `# subl=3211` && \
ln -s /usr/bin/php5.6 /usr/bin/php5 && \
\
curl -L "https://nodejs.org/dist/v${node}/node-v${node}-linux-x64.tar.gz" | tar xz && \
\
npm install --global --no-optional `# npm=6.14.5` \
brunch@"=3.0.0" \
grunt-cli@"=1.3.2" \
gulp-cli@"=2.2.0" \
heroku@"=7.39.3" \
netlify-cli@"=2.33.0" \
serverless@"=1.67.3" `# sls=1.67.3` && \
\
apt-get -y autoremove && apt-get -y clean && rm -rf /var/lib/apt/lists/*

View File

@ -0,0 +1,32 @@
FROM ubuntu:xenial-20200212
# already contains
# apt
# apt-get
# bash
# grep
# gzip
# tar
RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install --no-install-recommends -y -qq \
bc \
pv \
unar \
make \
curl \
git \
unzip \
xz-utils `#xz` \
unar \
pv \
zip \
sudo \
file \
&& apt-get -y autoremove && apt-get -y clean && rm -rf /var/lib/apt/lists/*; \
\
#bats
commit="87b16eb"; \
curl -L "https://github.com/bats-core/bats-core/tarball/${commit}" | tar xz; \
"bats-core-bats-core-${commit}/install.sh" /usr/local; \
\
#hub
curl -fsSL https://github.com/github/hub/raw/master/script/get | bash -s 2.14.2

9
tests/packages_all.sh Normal file
View File

@ -0,0 +1,9 @@
#!/bin/bash
## This script outputs all of the packages that has explicitly supports.
set -euo pipefail
pushd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null
grep -o "^ \\+[a-zA-Z0-9_|-]\\+)" ../has | grep -o "[a-zA-Z0-9_|-]\\+" | tr "|" "\\n" | sort -f
popd >/dev/null

View File

@ -0,0 +1,55 @@
# Not pre-installed to container, but repository cloned during CI
has
# non-alpine packages
apt
apt-get
aptitude
code
subl
# gnu_coreutils shows as installed, but is not on alpine.
# todo: We should change the command we use for this to
# something busybox doesn't also implement. Examples:
# chcon, csplit, dir, dircolors, fmt, join, logname, pathchk,
# pinky, pr, ptx, runcon, stdbuf, tsort, users, vdir, who
# Then we can move this to the "non-alpine packages" list above.
gnu_coreutils
# installed on container (and has confirms), but
# todo: has shows EMPTY version string on container
awk
bzip2
grep
gzip
wget
xz
# installed on container (and has confirms), but
# todo: has grabs INCORRECT version string on container
jq
perl
# installed on container, but
# todo: has reports command not found
gor
# --------------------------------------------------------------------------
# tools below have not be added to the alpine test container image.
# todo: we may need to compile executable binaries that run on alpine 3.11
# --------------------------------------------------------------------------
# todo: compile binary with help from:
# https://github.com/ggreer/the_silver_searcher
# https://github.com/Ketouem/ag-alpine
ag
# apk available for alpine 3.8
# todo: compile binary with help from:
# https://github.com/sgerrand/alpine-pkg-php5
php5
# todo: compile binary with help from:
# https://theunarchiver.com/command-line
# https://github.com/ashang/unar
unar

View File

@ -0,0 +1,16 @@
# Not pre-installed to container, but repository cloned during CI
has
# Tests are run as admin in CI. Code requires an extra parameter to get version as admin.
# todo: add --user-data-dir=/tmp for code when has is run as admin
code
# installed on container (and has confirms), but
# todo: has shows EMPTY version string on container
awk
groovy
# --------------------------------------------------------------------------
# tools below have not be added to the ubuntu test container image.
# --------------------------------------------------------------------------

View File

@ -0,0 +1,60 @@
#!/usr/bin/env bats
cd $BATS_TEST_DIRNAME
distro="alpine"
if grep -iq "ubuntu" /etc/issue; then
distro="ubuntu"
fi
SKIP_FILE=packages_${distro}_skip.txt
DOCKER_FILE=./containers/${distro}.Dockerfile
expected_version() {
grep -Eo "( |#)${1}(|-cli)(|@\"|~)=[^\`\"; *-]+" $DOCKER_FILE | tr "=" "\n" | tr ":" "\n" | tail -1
}
@test "test each package individually and verify version" {
packages_count=0
final_status=0
for package in $(bash packages_all.sh); do
if [ -n $package ] && ! grep -q "^$package$" $SKIP_FILE; then
run expected_version $package
[ "$status" -eq 0 ]
[ -n "$output" ]
package="$package" expected_ver="$output" run bats -t test_package.bats
echo "# $output" >&3
echo "#" >&3
packages_count=$(($packages_count + 1))
final_status=$(($final_status + $status))
fi
done
echo "# tested ${packages_count} commands individually" >&3
echo "#" >&3
echo "# status code=$final_status" >&3
[ "$final_status" -eq 0 ]
echo "#" >&3
}
@test "test all packages at once" {
# subtract skips from full list
packages_to_skip="$(grep -Ev "^\s*(#|$)" $SKIP_FILE | xargs | tr " " "|")"
packages=$(bash packages_all.sh | egrep -Ev "^($packages_to_skip)$" | xargs)
run ../has $packages
echo "$output" >&3
echo "#" >&3
packages_count=$(echo "${packages}" | wc -w)
echo "# tested ${packages_count} commands simultaneously" >&3
actual_packages_count=$(echo "${output}" | wc -l)
echo "# received output from ${actual_packages_count} commands" >&3
[ "$packages_count" -eq "$actual_packages_count" ]
echo "#" >&3
echo "# status code=$status" >&3
[ "$status" -eq 0 ]
echo "#" >&3
}

30
tests/test_package.bats Normal file
View File

@ -0,0 +1,30 @@
#!/usr/bin/env bats
cd $BATS_TEST_DIRNAME
get_version_from_has() {
echo "$1" | tr " " "\n" | tail -1
}
@test "test $package" {
# Ensure inputs
[ "$(echo "${package}" | wc -w)" -eq 1 ]
[ "$(echo "${expected_ver}" | wc -w)" -eq 1 ]
# Confirm package is installed
run ../has $package
echo "$output" >&3
[ "$status" -eq 0 ]
# Get actual version (reported from has)
actual_ver=""
run get_version_from_has "$output"
if [ "$status" -eq 0 ]; then
actual_ver="$output"
[ "$(echo "${actual_ver}" | wc -w)" -eq 1 ]
fi
# Compare expected with actual
# We grep instead of direct compare because has adds ansi colors with tput
[ "$(echo "${actual_ver}" | grep "$expected_ver")" ]
}