Skip to content

Commit

Permalink
boilerplate comments, take 2
Browse files Browse the repository at this point in the history
  • Loading branch information
glennj committed Mar 21, 2022
1 parent bae12cf commit 349f63f
Show file tree
Hide file tree
Showing 12 changed files with 238 additions and 42 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM ubuntu:20.04

RUN apt-get update && \
apt-get install -y bats ruby shellcheck && \
apt-get install -y bats ruby shellcheck jq && \
apt-get purge --auto-remove && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Expand Down
2 changes: 1 addition & 1 deletion bin/run-tests-in-docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set -e

# Synopsis:
# Test the test runner Docker image by running it against a predefined set of
# Test the test runner Docker image by running it against a predefined set of
# solutions with an expected output.
# The test runner Docker image is built automatically.

Expand Down
7 changes: 5 additions & 2 deletions bin/run-tests.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#!/usr/bin/env bash
set -e

# remove residue from previous runs
find tests/ -name \*_analysis.json \( -not -name expected_analysis.json \) -delete

# Synopsis:
# Test the test runner by running it against a predefined set of solutions
# Test the test runner by running it against a predefined set of solutions
# with an expected output.

# Output:
Expand Down Expand Up @@ -35,7 +38,7 @@ END_TEMPLATE
slug=$(basename "$dir")
# shellcheck disable=SC2059
printf "$test_template"'\n' "$slug" "$slug"
done
done
} > tests/all_tests.bats

# now run it
Expand Down
71 changes: 37 additions & 34 deletions bin/run.sh
Original file line number Diff line number Diff line change
@@ -1,55 +1,58 @@
#!/usr/bin/env bash

shopt -s extglob

usage() {
printf '%s\n' "$*"
printf '%s\n' "Usage: ${0##*/} slug inputDir outputDir"
printf '%s\n' "Usage: ${0##*/} slug input_dir output_dir"
exit 1
}

analyze() {
local opts=(
"--shell=bash"
"--external-sources"
"--check-sourced"
"--norc"
"--format=json1"
)
(
cd "$inDir" >/dev/null || exit 1
shellcheck "${opts[@]}" "${filename}.sh"
)
}

process() {
# this reads stdin and emits to stdout
ruby bin/process-shellcheck-output.rb
die() {
echo "$*" >&2
exit 1
}

validate() {
if ! [[ -d "$inDir" && -r "$inDir" ]]; then
usage "Error: not a dir or not readable: ${inDir@Q}"
validate_input() {
if ! [[ -d "$in_dir" && -r "$in_dir" ]]; then
usage "Error: not a dir or not readable: ${in_dir@Q}"
fi
if ! [[ -d "$outDir" && -w "$outDir" ]]; then
usage "Error: not a dir or not writable: ${outDir@Q}"
if ! [[ -d "$out_dir" && -w "$out_dir" ]]; then
usage "Error: not a dir or not writable: ${out_dir@Q}"
fi
local source="$inDir/${filename}.sh"
local source="$in_dir/${snake_slug}.sh"
if ! [[ -f $source && -r $source ]]; then
usage "Error: not a file or not readable: ${source@Q}"
fi
}

invoke_analysis() {
local module=$1
if [[ -f "lib/$module/analysis.bash" ]]; then
bash "lib/$module/analysis.bash" "$in_dir" "$out_dir" "$snake_slug" \
|| die "$module analysis exits non-zero."
fi
}

main() {
local slug=$1
local inDir=${2:-.}
local outDir=${3:-.}
local filename=${slug//-/_}

validate

analyze \
| tee "$outDir/analysis.out" \
| process \
| tee "$outDir/analysis.json"
local in_dir=${2:-.}
local out_dir=${3:-.}
local snake_slug=${slug//-/_}

validate_input

# now run the checks. There will be (up to) 3 scripts executed:
#
# 1. shellcheck -- run this first, to seed the analysis.json file
# 2. general, including checking for boiler-plate comments
# 3. exercise-specific
#
# Each script will merge its results into the "analysis.json" output file.

invoke_analysis shellcheck
invoke_analysis general
invoke_analysis "$slug"
}

main "$@"
77 changes: 77 additions & 0 deletions lib/general/analysis.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env bash

# Check for boilerplate comments
boilerplate() {
local comment='# *** PLEASE REMOVE THESE COMMENTS BEFORE SUBMITTING YOUR SOLUTION ***'
grep -Fxq "$comment" "$filename" \
&& echo '{"comment": "bash.general.boilerplate_comment", "type": "actionable"}'
}

# Other check functions can go here:
# ...


# ------------------------------------------------------------
# Merge results into analysis.json generated by the shellcheck analysis.

comments_array_as_json() {
jq -n --jsonargs '{comments: $ARGS.positional}' "$@"
}

write_json() {
local -i shellcheck_comments
local temp

# How many shellcheck comments were found?
shellcheck_comments=$(jq '.comments | length' "$out_dir/analysis.json")

case "$shellcheck_comments,${#general_comments[@]}" in
0,0)
jq -n '{
summary: "Congrats! No suggestions",
comments: []
}' > "$out_dir/analysis.json"
;;
0,*)
comments_array_as_json "${general_comments[@]}" \
| jq '.summary = "Some comments"' \
> "$out_dir"/analysis.json
;;
*,0)
# no need to alter analysis.json
: ;;
*,*)
temp=$(mktemp)

# grab the summary from the shellcheck analysis
summary=$(jq -r .summary "$out_dir/analysis.json")

# Thanks to https://stackoverflow.com/a/36218044/7552
jq --arg sum "$summary" --slurp '{
summary: $sum,
comments: (reduce .[] as $item ([]; . + $item.comments))
}' \
"$out_dir/analysis.json" \
<(comments_array_as_json "${general_comments[@]}") \
> "$temp" && mv "$temp" "$out_dir/analysis.json"
;;
esac
}

# ------------------------------------------------------------
analyze() {
local in_dir=$1 out_dir=$2 snake_slug=$3
local filename="$in_dir/${snake_slug}.sh"
local general_comments=()
local IFS=,

json=$(boilerplate)
[[ -n $json ]] && general_comments+=("$json")

# other checks can go here to add to the "general_comments" array
# ....

write_json
}

analyze "$@"
25 changes: 25 additions & 0 deletions lib/shellcheck/analysis.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash

# Call out to shellcheck for common mistakes and advice

set -e

analyze() {
local in_dir=$1 out_dir=$2 snake_slug=$3
local opts=(
"--shell=bash"
"--external-sources"
"--check-sourced"
"--norc"
"--format=json1"
)
(
cd "$in_dir" > /dev/null || exit 1
shellcheck "${opts[@]}" "${snake_slug}.sh"
) \
| tee "$out_dir/analysis.out" \
| ruby bin/process-shellcheck-output.rb \
| tee "$out_dir/analysis.json"
}

analyze "$@"
10 changes: 10 additions & 0 deletions lib/two-fer/analysis.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bash

analyze() {
local in_dir=$1 out_dir=$2 snake_slug=$3

# TODO add checks
true
}

analyze "$@"
26 changes: 26 additions & 0 deletions tests/boilerplate/boilerplate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash

# The following comments should help you get started:
# - Bash is flexible. You may use functions or write a "raw" script.
#
# - Complex code can be made easier to read by breaking it up
# into functions, however this is sometimes overkill in bash.
#
# - You can find links about good style and other resources
# for Bash in './README.md'. It came with this exercise.
#
# Example:
# # other functions here
# # ...
# # ...
#
# main () {
# # your main function code here
# }
#
# # call main with all of the positional arguments
# main "$@"
#
# *** PLEASE REMOVE THESE COMMENTS BEFORE SUBMITTING YOUR SOLUTION ***

echo "Hello, world!"
9 changes: 9 additions & 0 deletions tests/boilerplate/expected_analysis.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"comments": [
{
"comment": "bash.general.boilerplate_comment",
"type": "actionable"
}
],
"summary": "Some comments"
}
27 changes: 27 additions & 0 deletions tests/boilerplate_plus_shellcheck/boilerplate_plus_shellcheck.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash

# The following comments should help you get started:
# - Bash is flexible. You may use functions or write a "raw" script.
#
# - Complex code can be made easier to read by breaking it up
# into functions, however this is sometimes overkill in bash.
#
# - You can find links about good style and other resources
# for Bash in './README.md'. It came with this exercise.
#
# Example:
# # other functions here
# # ...
# # ...
#
# main () {
# # your main function code here
# }
#
# # call main with all of the positional arguments
# main "$@"
#
# *** PLEASE REMOVE THESE COMMENTS BEFORE SUBMITTING YOUR SOLUTION ***

echo "Hello, world!"
echo "$(date)"
18 changes: 18 additions & 0 deletions tests/boilerplate_plus_shellcheck/expected_analysis.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"summary": "Shellcheck suggestions",
"comments": [
{
"comment": "bash.shellcheck.generic",
"type": "informative",
"params": {
"message": "Useless echo? Instead of 'echo $(cmd)', just use 'cmd'.",
"code": 2005,
"lines": "line 27"
}
},
{
"comment": "bash.general.boilerplate_comment",
"type": "actionable"
}
]
}
6 changes: 2 additions & 4 deletions tests/ok/expected_analysis.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
"summary": "Congrats! No Shellcheck suggestions",
"comments": [

]
"summary": "Congrats! No suggestions",
"comments": []
}

0 comments on commit 349f63f

Please sign in to comment.