Skip to content

Commit

Permalink
Merge pull request #42 from segmentio/mckern/globstar
Browse files Browse the repository at this point in the history
Add support for extended and recursive globs
  • Loading branch information
pzeballos authored Sep 28, 2022
2 parents 3b0de92 + 1145b7d commit 03732ed
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 12 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ The files (or globs) to run shellcheck on.

### Optional

### `extended_glob` (boolean)

Enable using [extended glob patterns](https://www.gnu.org/software/bash/manual/html_node/Pattern-Matching.html), e.g. `*.+(sh|bash)`

Default: `false`

### `recursive_glob` (boolean)

Enable using recursive globbing, e.g. `**/*.sh`

Note: _requires at least Bash 4 on the Buildkite Agent_

Default: `false`

### `options` (string or array of strings)

Command line options to pass to shellcheck.
Expand Down
79 changes: 69 additions & 10 deletions hooks/command
Original file line number Diff line number Diff line change
@@ -1,35 +1,94 @@
#!/bin/bash
set -eu -o pipefail

fail() {
echo "ERROR: ${*}" 1>&2
exit 1
}

# `boolean_check()` runs in a subshell to keep the use
# of `shopt` constrained from the rest of the script.
boolean_check() (
local value="${1}"
shopt -s nocasematch

# return immediately (and successfully) if the value is explicitly set to true
if [[ ${value} =~ ^(true|1)$ ]]; then
return 0
fi

return 1
)

# Attempts to set a given shopt. Errors will be caught and pushed up through
# error logging along with a note about the required Bash version number.
set_shopt() {
local option="${1}"

# macOS is the last major platform still using Bash 3, and `shopt -q` is
# broken on macOS. So we have to mute `shopt` the old-fashioned way
# and redirect output to /dev/null. Since the list of options that we'll
# attempt to set is so constrained, we can use a small helper fuction to look up
# which version of bash introduced a given the shopts in our error handling.
if ! shopt -s "${option}" &> /dev/null; then
fail "failed to set shopt '${option}: $(bash_version_lookup "${option}")"
fi
}

# look up which version of bash introduced
# a given shopt and print it to stdout.
bash_version_lookup() {
local option="${1}"
local required

case "${option}" in
globstar) required="4" ;;
extglob) required="2" ;;
esac

echo "shopt '${option}' requires Bash ${required} or newer"
}

# Reads either a value or a list from plugin config
function plugin_read_list() {
plugin_read_list() {
local prefix="BUILDKITE_PLUGIN_SHELLCHECK_$1"
local parameter="${prefix}_0"

if [[ -n "${!parameter:-}" ]]; then
if [[ -n ${!parameter:-} ]]; then
local i=0
local parameter="${prefix}_${i}"
while [[ -n "${!parameter:-}" ]]; do
while [[ -n ${!parameter:-} ]]; do
echo "${!parameter}"
i=$((i+1))
i=$((i + 1))
parameter="${prefix}_${i}"
done
elif [[ -n "${!prefix:-}" ]]; then
elif [[ -n ${!prefix:-} ]]; then
echo "${!prefix}"
fi
}

# Evaluate whether or not the glob behavior modifiers are enabled, and
# attempt to set the corresponding shopts for them.
echo "~~~ Checking for glob behavior modifiers"
if boolean_check "${BUILDKITE_PLUGIN_SHELLCHECK_RECURSIVE_GLOB:-false}"; then
set_shopt globstar && echo "Recursive globbing enabled"
fi

if boolean_check "${BUILDKITE_PLUGIN_SHELLCHECK_EXTENDED_GLOB:-false}"; then
set_shopt extglob && echo "Extended globbing enabled"
fi

IFS=$'\n\t'
files=()

# Evaluate all the globs and return the files that exist
for file in $(plugin_read_list FILES) ; do
if [[ -e $file ]] ; then
for file in $(plugin_read_list FILES); do
if [[ -e $file ]]; then
files+=("$file")
fi
done

if [[ -z ${files:-} ]] ; then
if [[ -z ${files:-} ]]; then
echo "No files found to shellcheck"
exit 1
fi
Expand All @@ -38,14 +97,14 @@ fi
# for pretty online log display (but someone can override this with an
# explicit `options: "--color=never"`)
options=("--color=always")
while IFS=$'\n' read -r option ; do
while IFS=$'\n' read -r option; do
options+=("$option")
done < <(plugin_read_list OPTIONS)

BUILDKITE_PLUGIN_SHELLCHECK_VERSION="${BUILDKITE_PLUGIN_SHELLCHECK_VERSION:-stable}"

echo "+++ Running shellcheck on ${#files[@]} files"
if docker run --rm -v "$PWD:/mnt" --workdir "/mnt" "koalaman/shellcheck:$BUILDKITE_PLUGIN_SHELLCHECK_VERSION" "${options[@]+${options[@]}}" "${files[@]}" ; then
if docker run --rm -v "$PWD:/mnt" --workdir "/mnt" "koalaman/shellcheck:$BUILDKITE_PLUGIN_SHELLCHECK_VERSION" "${options[@]+${options[@]}}" "${files[@]}"; then
echo "Files are ok ✅"
else
exit 1
Expand Down
6 changes: 6 additions & 0 deletions plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ requirements:
- docker
configuration:
properties:
recursive_glob:
type: boolean
default: false
extended_glob:
type: boolean
default: false
files:
type: [string, array]
minItems: 1
Expand Down
83 changes: 81 additions & 2 deletions tests/run.bats
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ load '/usr/local/lib/bats/load.bash'

@test "Shellcheck multiple files" {
export BUILDKITE_PLUGIN_SHELLCHECK_FILES_0="tests/testdata/test.sh"
export BUILDKITE_PLUGIN_SHELLCHECK_FILES_1="tests/testdata/subdir/*"
export BUILDKITE_PLUGIN_SHELLCHECK_FILES_1="tests/testdata/subdir/*.sh"
export BUILDKITE_PLUGIN_SHELLCHECK_FILES_2="missing"

stub docker \
Expand All @@ -38,6 +38,85 @@ load '/usr/local/lib/bats/load.bash'
unstub docker
}

@test "Shellcheck multiple files using recursive globbing enabled with 'true'" {
export BUILDKITE_PLUGIN_SHELLCHECK_RECURSIVE_GLOB=true
export BUILDKITE_PLUGIN_SHELLCHECK_FILES="**/*.sh"

stub docker \
"run --rm -v $PWD:/mnt --workdir /mnt koalaman/shellcheck:stable --color=always tests/testdata/recursive/subdir/stub.sh tests/testdata/subdir/llamas.sh tests/testdata/subdir/shell\ with\ a\ space.sh tests/testdata/test.sh : echo testing stub.sh test.sh llamas.sh shell with space.sh"

run "$PWD/hooks/command"

assert_success
assert_output --partial "Running shellcheck on 4 files"
assert_output --partial "testing stub.sh test.sh llamas.sh shell with space.sh"

unstub docker
}

@test "Shellcheck multiple files using extended globbing enabled with '1'" {
export BUILDKITE_PLUGIN_SHELLCHECK_EXTENDED_GLOB=1
export BUILDKITE_PLUGIN_SHELLCHECK_FILES="tests/testdata/subdir/*.+(sh|bash)"

stub docker \
"run --rm -v $PWD:/mnt --workdir /mnt koalaman/shellcheck:stable --color=always tests/testdata/subdir/llamas.sh tests/testdata/subdir/shell\ with\ a\ space.sh tests/testdata/subdir/stub.bash : echo testing llamas.sh shell with space.sh stub.bash"

run "$PWD/hooks/command"

assert_success
assert_output --partial "Running shellcheck on 3 files"
assert_output --partial "testing llamas.sh shell with space.sh stub.bash"

unstub docker
}

@test "Recursive globbing fails if recursive globbing is disabled with 'false'" {
export BUILDKITE_PLUGIN_SHELLCHECK_RECURSIVE_GLOB=false
export BUILDKITE_PLUGIN_SHELLCHECK_FILES="**/*.sh"

run "$PWD/hooks/command"

assert_failure
assert_output --partial "No files found to shellcheck"
}

@test "Extended globbing fails if extended globbing is disabled with 'FALSE'" {
export BUILDKITE_PLUGIN_SHELLCHECK_EXTENDED_GLOB=FALSE
export BUILDKITE_PLUGIN_SHELLCHECK_FILES="tests/testdata/subdir/*.+(sh|bash)"

run "$PWD/hooks/command"

assert_failure
assert_output --partial "No files found to shellcheck"
}

@test "Extended globbing fails if extended globbing is disabled with '0'" {
export BUILDKITE_PLUGIN_SHELLCHECK_EXTENDED_GLOB=0
export BUILDKITE_PLUGIN_SHELLCHECK_FILES="tests/testdata/subdir/*.+(sh|bash)"

run "$PWD/hooks/command"

assert_failure
assert_output --partial "No files found to shellcheck"
}

@test "Shellcheck multiple files using recursive and extended globbing enabled with 'true'" {
export BUILDKITE_PLUGIN_SHELLCHECK_RECURSIVE_GLOB=true
export BUILDKITE_PLUGIN_SHELLCHECK_EXTENDED_GLOB=true
export BUILDKITE_PLUGIN_SHELLCHECK_FILES="**/*.+(sh|bash)"

stub docker \
"run --rm -v $PWD:/mnt --workdir /mnt koalaman/shellcheck:stable --color=always tests/testdata/recursive/subdir/stub.bash tests/testdata/recursive/subdir/stub.sh tests/testdata/subdir/llamas.sh tests/testdata/subdir/shell\ with\ a\ space.sh tests/testdata/subdir/stub.bash tests/testdata/test.sh : echo testing stub.sh test.sh llamas.sh shell with space.sh"

run "$PWD/hooks/command"

assert_success
assert_output --partial "Running shellcheck on 6 files"
assert_output --partial "testing stub.sh test.sh llamas.sh shell with space.sh"

unstub docker
}

@test "Shellcheck a single file with single option" {
export BUILDKITE_PLUGIN_SHELLCHECK_FILES_0="tests/testdata/subdir/llamas.sh"
export BUILDKITE_PLUGIN_SHELLCHECK_OPTIONS_0="--exclude=SC2086"
Expand Down Expand Up @@ -76,7 +155,7 @@ load '/usr/local/lib/bats/load.bash'

@test "Shellcheck multiple files with multiple options" {
export BUILDKITE_PLUGIN_SHELLCHECK_FILES_0="tests/testdata/test.sh"
export BUILDKITE_PLUGIN_SHELLCHECK_FILES_1="tests/testdata/subdir/*"
export BUILDKITE_PLUGIN_SHELLCHECK_FILES_1="tests/testdata/subdir/*.sh"
export BUILDKITE_PLUGIN_SHELLCHECK_FILES_2="missing"
export BUILDKITE_PLUGIN_SHELLCHECK_OPTIONS_0="--exclude=SC2086"
export BUILDKITE_PLUGIN_SHELLCHECK_OPTIONS_1="--format=gcc"
Expand Down
2 changes: 2 additions & 0 deletions tests/testdata/recursive/subdir/stub.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
echo halb
2 changes: 2 additions & 0 deletions tests/testdata/recursive/subdir/stub.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
echo blah blah blah
2 changes: 2 additions & 0 deletions tests/testdata/subdir/stub.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
echo blah blah blah

0 comments on commit 03732ed

Please sign in to comment.