1
1
Fork 0

feat(jq): grade-school, acronym

Signed-off-by: Christina Sørensen <christina@cafkafk.com>
This commit is contained in:
Christina Sørensen 2024-12-18 05:41:09 +01:00
parent f2d2ca29ad
commit 98da7a4c9e
Signed by: cafkafk
GPG key ID: 26C542FD97F965CE
17 changed files with 2266 additions and 1 deletions

View file

@ -1 +1,6 @@
.phrase | gsub("-"; " ") | gsub("\\p{P}"; "") | ascii_upcase / " " | map(./""|.[0]) | join("")
.phrase
| gsub("-"; " ")
| gsub("\\p{P}"; "")
| ascii_upcase / " "
| map(./""|.[0])
| join("")

View file

@ -0,0 +1,18 @@
{
"authors": [
"glennj"
],
"files": {
"solution": [
"grade-school.jq"
],
"test": [
"test-grade-school.bats"
],
"example": [
".meta/example.jq"
]
},
"blurb": "Given students' names along with the grade that they are in, create a roster for the school.",
"source": "A pairing session with Phil Battos at gSchool"
}

View file

@ -0,0 +1 @@
{"track":"jq","exercise":"grade-school","id":"aeda4dc15b0849be95501fa317bb6a8b","url":"https://exercism.org/tracks/jq/exercises/grade-school","handle":"cafkafk","is_requester":true,"auto_approve":false}

114
jq/grade-school/HELP.md Normal file
View file

@ -0,0 +1,114 @@
# Help
## Running the tests
Each exercise contains a test file.
Run the tests using the `bats` program.
```bash
bats test-hello-world.bats
```
`bats` will need to be installed.
See the [Testing on the Bash track][bash] page for instructions to install `bats` for your system.
### bats is implemented in bash
The bats file is a bash script, with some special functions recognized by the `bats` command.
You'll see some tests that look like
```sh
jq -f some-exercise.jq <<< "{some,json,here}"
```
That `<<<` syntax is a bash [Here String][here-string].
It sends the string on the right-hand side into the standard input of the program on the left-hand side.
It is ([approximately][so]) the same as
```sh
echo "{some,json,here}" | jq -f some-exercise.jq
```
## Help for assert functions
The tests use functions from the [bats-assert][bats-assert] library.
Help for the various `assert*` functions can be found there.
## Skipped tests
Solving an exercise means making all its tests pass.
By default, only one test (the first one) is executed when you run the tests.
This is intentional, as it allows you to focus on just making that one test pass.
Once it passes, you can enable the next test by commenting out or removing the
[[ $BATS_RUN_SKIPPED == true ]] || skip
annotations prepending other tests.
## Overriding skips
To run all tests, including the ones with `skip` annotations, you can run:
```bash
BATS_RUN_SKIPPED=true bats test-some-exercise.bats
```
It can be convenient to use a wrapper function to save on typing: in `bash` you can do:
```bash
bats() {
BATS_RUN_SKIPPED=true command bats *.bats
}
```
Then run tests with just:
```bash
bats
```
## Debugging in `jq`
`jq` comes with a handy [`debug`][debug] filter.
Use it while you are developing your exercise solutions to inspect the data that is currently in the jq pipline.
See the [debugging doc][debugging] for more details.
[bash]: https://exercism.org/docs/tracks/bash/tests
[bats-assert]: https://github.com/bats-core/bats-assert
[here-string]: https://www.gnu.org/software/bash/manual/bash.html#Here-Strings
[so]: https://unix.stackexchange.com/a/80372/4667
[debug]: https://jqlang.github.io/jq/manual/v1.7/#debug
[debugging]: https://exercism.org/docs/tracks/jq/debugging
## Submitting your solution
You can submit your solution using the `exercism submit grade-school.jq` command.
This command will upload your solution to the Exercism website and print the solution page's URL.
It's possible to submit an incomplete solution which allows you to:
- See how others have completed the exercise
- Request help from a mentor
## Need to get help?
If you'd like help solving the exercise, check the following pages:
- The [jq track's documentation](https://exercism.org/docs/tracks/jq)
- The [jq track's programming category on the forum](https://forum.exercism.org/c/programming/jq)
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
## Need more help?
- Go to the [Exercism Community forum](https://forum.exercism.org) to get support and ask questions (or just chat!)
- Use the [Exercism Support](https://forum.exercism.org/c/support/8) category if you face any issues with working in the web editor, or downloading or submitting your exercises locally.
- Use the [Programming:jq](https://forum.exercism.org/c/programming/jq/133) category for jq-specific topics.
- Join the community on [Exercism's Discord server](https://exercism.org/r/discord).
- [StackOverflow](https://stackoverflow.com/questions/tagged/jq) can be used to search for your problem and see if it has been answered already.
You can also ask and answer questions.
- [Github issue tracker](https://github.com/exercism/jq/issues) is where we track our development and maintainance of `jq` exercises in exercism.
If none of the above links help you, feel free to post an issue here.

36
jq/grade-school/README.md Normal file
View file

@ -0,0 +1,36 @@
# Grade School
Welcome to Grade School on Exercism's jq Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
Given students' names along with the grade that they are in, create a roster for the school.
In the end, you should be able to:
- Add a student's name to the roster for a grade
- "Add Jim to grade 2."
- "OK."
- Get a list of all students enrolled in a grade
- "Which students are in grade 2?"
- "We've only got Jim just now."
- Get a sorted list of all students in all grades.
Grades should sort as 1, 2, 3, etc., and students within a grade should be sorted alphabetically by name.
- "Who all is enrolled in school right now?"
- "Let me think.
We have Anna, Barb, and Charlie in grade 1, Alex, Peter, and Zoe in grade 2 and Jim in grade 5.
So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe and Jim"
Note that all our students only have one name (It's a small town, what do you want?) and each student cannot be added more than once to a grade or the roster.
In fact, when a test attempts to add the same student more than once, your implementation should indicate that this is incorrect.
## Source
### Created by
- @glennj
### Based on
A pairing session with Phil Battos at gSchool

View file

@ -0,0 +1,637 @@
# This is the source code for bats-support and bats-assert, concatenated
# * https://github.com/bats-core/bats-support
# * https://github.com/bats-core/bats-assert
#
# Comments have been removed to save space. See the git repos for full source code.
############################################################
#
# bats-support - Supporting library for Bats test helpers
#
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
#
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the
# public domain worldwide. This software is distributed without any
# warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication
# along with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
fail() {
(( $# == 0 )) && batslib_err || batslib_err "$@"
return 1
}
batslib_is_caller() {
local -i is_mode_direct=1
# Handle options.
while (( $# > 0 )); do
case "$1" in
-i|--indirect) is_mode_direct=0; shift ;;
--) shift; break ;;
*) break ;;
esac
done
# Arguments.
local -r func="$1"
# Check call stack.
if (( is_mode_direct )); then
[[ $func == "${FUNCNAME[2]}" ]] && return 0
else
local -i depth
for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do
[[ $func == "${FUNCNAME[$depth]}" ]] && return 0
done
fi
return 1
}
batslib_err() {
{ if (( $# > 0 )); then
echo "$@"
else
cat -
fi
} >&2
}
batslib_count_lines() {
local -i n_lines=0
local line
while IFS='' read -r line || [[ -n $line ]]; do
(( ++n_lines ))
done < <(printf '%s' "$1")
echo "$n_lines"
}
batslib_is_single_line() {
for string in "$@"; do
(( $(batslib_count_lines "$string") > 1 )) && return 1
done
return 0
}
batslib_get_max_single_line_key_width() {
local -i max_len=-1
while (( $# != 0 )); do
local -i key_len="${#1}"
batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len"
shift 2
done
echo "$max_len"
}
batslib_print_kv_single() {
local -ir col_width="$1"; shift
while (( $# != 0 )); do
printf '%-*s : %s\n' "$col_width" "$1" "$2"
shift 2
done
}
batslib_print_kv_multi() {
while (( $# != 0 )); do
printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )"
printf '%s\n' "$2"
shift 2
done
}
batslib_print_kv_single_or_multi() {
local -ir width="$1"; shift
local -a pairs=( "$@" )
local -a values=()
local -i i
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
values+=( "${pairs[$i]}" )
done
if batslib_is_single_line "${values[@]}"; then
batslib_print_kv_single "$width" "${pairs[@]}"
else
local -i i
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )"
done
batslib_print_kv_multi "${pairs[@]}"
fi
}
batslib_prefix() {
local -r prefix="${1:- }"
local line
while IFS='' read -r line || [[ -n $line ]]; do
printf '%s%s\n' "$prefix" "$line"
done
}
batslib_mark() {
local -r symbol="$1"; shift
# Sort line numbers.
set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" )
local line
local -i idx=0
while IFS='' read -r line || [[ -n $line ]]; do
if (( ${1:--1} == idx )); then
printf '%s\n' "${symbol}${line:${#symbol}}"
shift
else
printf '%s\n' "$line"
fi
(( ++idx ))
done
}
batslib_decorate() {
echo
echo "-- $1 --"
cat -
echo '--'
echo
}
############################################################
assert() {
if ! "$@"; then
batslib_print_kv_single 10 'expression' "$*" \
| batslib_decorate 'assertion failed' \
| fail
fi
}
assert_equal() {
if [[ $1 != "$2" ]]; then
batslib_print_kv_single_or_multi 8 \
'expected' "$2" \
'actual' "$1" \
| batslib_decorate 'values do not equal' \
| fail
fi
}
assert_failure() {
: "${output?}"
: "${status?}"
(( $# > 0 )) && local -r expected="$1"
if (( status == 0 )); then
batslib_print_kv_single_or_multi 6 'output' "$output" \
| batslib_decorate 'command succeeded, but it was expected to fail' \
| fail
elif (( $# > 0 )) && (( status != expected )); then
{ local -ir width=8
batslib_print_kv_single "$width" \
'expected' "$expected" \
'actual' "$status"
batslib_print_kv_single_or_multi "$width" \
'output' "$output"
} \
| batslib_decorate 'command failed as expected, but status differs' \
| fail
fi
}
assert_line() {
local -i is_match_line=0
local -i is_mode_partial=0
local -i is_mode_regexp=0
: "${lines?}"
# Handle options.
while (( $# > 0 )); do
case "$1" in
-n|--index)
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
echo "\`--index' requires an integer argument: \`$2'" \
| batslib_decorate 'ERROR: assert_line' \
| fail
return $?
fi
is_match_line=1
local -ri idx="$2"
shift 2
;;
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: assert_line' \
| fail
return $?
fi
# Arguments.
local -r expected="$1"
if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$expected'" \
| batslib_decorate 'ERROR: assert_line' \
| fail
return $?
fi
# Matching.
if (( is_match_line )); then
# Specific line.
if (( is_mode_regexp )); then
if ! [[ ${lines[$idx]} =~ $expected ]]; then
batslib_print_kv_single 6 \
'index' "$idx" \
'regexp' "$expected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'regular expression does not match line' \
| fail
fi
elif (( is_mode_partial )); then
if [[ ${lines[$idx]} != *"$expected"* ]]; then
batslib_print_kv_single 9 \
'index' "$idx" \
'substring' "$expected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line does not contain substring' \
| fail
fi
else
if [[ ${lines[$idx]} != "$expected" ]]; then
batslib_print_kv_single 8 \
'index' "$idx" \
'expected' "$expected" \
'actual' "${lines[$idx]}" \
| batslib_decorate 'line differs' \
| fail
fi
fi
else
# Contained in output.
if (( is_mode_regexp )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} =~ $expected ]] && return 0
done
{ local -ar single=( 'regexp' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
} \
| batslib_decorate 'no output line matches regular expression' \
| fail
elif (( is_mode_partial )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} == *"$expected"* ]] && return 0
done
{ local -ar single=( 'substring' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
} \
| batslib_decorate 'no output line contains substring' \
| fail
else
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} == "$expected" ]] && return 0
done
{ local -ar single=( 'line' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
} \
| batslib_decorate 'output does not contain line' \
| fail
fi
fi
}
assert_output() {
local -i is_mode_partial=0
local -i is_mode_regexp=0
local -i is_mode_nonempty=0
local -i use_stdin=0
: "${output?}"
# Handle options.
if (( $# == 0 )); then
is_mode_nonempty=1
fi
while (( $# > 0 )); do
case "$1" in
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
-|--stdin) use_stdin=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: assert_output' \
| fail
return $?
fi
# Arguments.
local expected
if (( use_stdin )); then
expected="$(cat -)"
else
expected="${1-}"
fi
# Matching.
if (( is_mode_nonempty )); then
if [ -z "$output" ]; then
echo 'expected non-empty output, but output was empty' \
| batslib_decorate 'no output' \
| fail
fi
elif (( is_mode_regexp )); then
if [[ '' =~ $expected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$expected'" \
| batslib_decorate 'ERROR: assert_output' \
| fail
elif ! [[ $output =~ $expected ]]; then
batslib_print_kv_single_or_multi 6 \
'regexp' "$expected" \
'output' "$output" \
| batslib_decorate 'regular expression does not match output' \
| fail
fi
elif (( is_mode_partial )); then
if [[ $output != *"$expected"* ]]; then
batslib_print_kv_single_or_multi 9 \
'substring' "$expected" \
'output' "$output" \
| batslib_decorate 'output does not contain substring' \
| fail
fi
else
if [[ $output != "$expected" ]]; then
batslib_print_kv_single_or_multi 8 \
'expected' "$expected" \
'actual' "$output" \
| batslib_decorate 'output differs' \
| fail
fi
fi
}
assert_success() {
: "${output?}"
: "${status?}"
if (( status != 0 )); then
{ local -ir width=6
batslib_print_kv_single "$width" 'status' "$status"
batslib_print_kv_single_or_multi "$width" 'output' "$output"
} \
| batslib_decorate 'command failed' \
| fail
fi
}
refute() {
if "$@"; then
batslib_print_kv_single 10 'expression' "$*" \
| batslib_decorate 'assertion succeeded, but it was expected to fail' \
| fail
fi
}
refute_line() {
local -i is_match_line=0
local -i is_mode_partial=0
local -i is_mode_regexp=0
: "${lines?}"
# Handle options.
while (( $# > 0 )); do
case "$1" in
-n|--index)
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
echo "\`--index' requires an integer argument: \`$2'" \
| batslib_decorate 'ERROR: refute_line' \
| fail
return $?
fi
is_match_line=1
local -ri idx="$2"
shift 2
;;
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: refute_line' \
| fail
return $?
fi
# Arguments.
local -r unexpected="$1"
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$unexpected'" \
| batslib_decorate 'ERROR: refute_line' \
| fail
return $?
fi
# Matching.
if (( is_match_line )); then
# Specific line.
if (( is_mode_regexp )); then
if [[ ${lines[$idx]} =~ $unexpected ]]; then
batslib_print_kv_single 6 \
'index' "$idx" \
'regexp' "$unexpected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'regular expression should not match line' \
| fail
fi
elif (( is_mode_partial )); then
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
batslib_print_kv_single 9 \
'index' "$idx" \
'substring' "$unexpected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line should not contain substring' \
| fail
fi
else
if [[ ${lines[$idx]} == "$unexpected" ]]; then
batslib_print_kv_single 5 \
'index' "$idx" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line should differ' \
| fail
fi
fi
else
# Line contained in output.
if (( is_mode_regexp )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} =~ $unexpected ]]; then
{ local -ar single=( 'regexp' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
if batslib_is_single_line "${may_be_multi[1]}"; then
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'no line should match the regular expression' \
| fail
return $?
fi
done
elif (( is_mode_partial )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
{ local -ar single=( 'substring' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
if batslib_is_single_line "${may_be_multi[1]}"; then
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'no line should contain substring' \
| fail
return $?
fi
done
else
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} == "$unexpected" ]]; then
{ local -ar single=( 'line' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
if batslib_is_single_line "${may_be_multi[1]}"; then
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'line should not be in output' \
| fail
return $?
fi
done
fi
fi
}
refute_output() {
local -i is_mode_partial=0
local -i is_mode_regexp=0
local -i is_mode_empty=0
local -i use_stdin=0
: "${output?}"
# Handle options.
if (( $# == 0 )); then
is_mode_empty=1
fi
while (( $# > 0 )); do
case "$1" in
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
-|--stdin) use_stdin=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: refute_output' \
| fail
return $?
fi
# Arguments.
local unexpected
if (( use_stdin )); then
unexpected="$(cat -)"
else
unexpected="${1-}"
fi
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$unexpected'" \
| batslib_decorate 'ERROR: refute_output' \
| fail
return $?
fi
# Matching.
if (( is_mode_empty )); then
if [ -n "$output" ]; then
batslib_print_kv_single_or_multi 6 \
'output' "$output" \
| batslib_decorate 'output non-empty, but expected no output' \
| fail
fi
elif (( is_mode_regexp )); then
if [[ $output =~ $unexpected ]]; then
batslib_print_kv_single_or_multi 6 \
'regexp' "$unexpected" \
'output' "$output" \
| batslib_decorate 'regular expression should not match output' \
| fail
fi
elif (( is_mode_partial )); then
if [[ $output == *"$unexpected"* ]]; then
batslib_print_kv_single_or_multi 9 \
'substring' "$unexpected" \
'output' "$output" \
| batslib_decorate 'output should not contain substring' \
| fail
fi
else
if [[ $output == "$unexpected" ]]; then
batslib_print_kv_single_or_multi 6 \
'output' "$output" \
| batslib_decorate 'output equals, but it was expected to differ' \
| fail
fi
fi
}

View file

@ -0,0 +1,29 @@
#!/usr/bin/env bash
#
# `bats-core` will consume both stdout and stderr for the `run` command's output.
# However `jq` prints its DEBUG output on stderr.
#
# Lines starting with `["DEBUG:",` will be prefixed with a hash and printed on file descriptor 3.
# Other lines on stderr will remain on stderr for bats to consume.
#
# See `bats-core` docs:
# - "Printing to the terminal", https://bats-core.readthedocs.io/en/stable/writing-tests.html#printing-to-the-terminal
# - "File descriptor 3", https://bats-core.readthedocs.io/en/stable/writing-tests.html#file-descriptor-3-read-this-if-bats-hangs
jq() {
local output stderr rc line
stderr=$(mktemp)
output=$(command jq "$@" 2> "$stderr")
rc=$?
while IFS= read -r line || [[ -n $line ]]; do
if [[ $line == '["DEBUG:",'* ]]; then
echo "# $line" >&3
else
echo "$line" >&2
fi
done < "$stderr"
rm -f "$stderr"
echo "$output"
return "$rc"
}

View file

@ -0,0 +1,17 @@
if .property == "grade" then (
.input.desiredGrade as $target
| .input.students
| unique_by(.[0])
| map(select(.[1]==$target))
| map(.[0])
| sort
)
elif .property == "roster" then (
.input.students
| unique_by(.[0])
| map({name: .[0], grade: .[1]})
| sort_by(.name)
| sort_by(.grade)
| map(.name)
)
end

View file

@ -0,0 +1,462 @@
#!/usr/bin/env bats
# generated on 2024-07-23T00:57:34Z
load bats-extra
load bats-jq
@test 'Roster is empty when no student is added' {
#
run jq -c -f grade-school.jq << 'END_INPUT'
{
"property": "roster",
"input": {
"students": []
}
}
END_INPUT
assert_success
expected='[]'
assert_equal "$output" "$expected"
}
@test 'Student is added to the roster' {
run jq -c -f grade-school.jq << 'END_INPUT'
{
"property": "roster",
"input": {
"students": [
[
"Aimee",
2
]
]
}
}
END_INPUT
assert_success
expected='["Aimee"]'
assert_equal "$output" "$expected"
}
@test 'Multiple students in the same grade are added to the roster' {
run jq -c -f grade-school.jq << 'END_INPUT'
{
"property": "roster",
"input": {
"students": [
[
"Blair",
2
],
[
"James",
2
],
[
"Paul",
2
]
]
}
}
END_INPUT
assert_success
expected='["Blair","James","Paul"]'
assert_equal "$output" "$expected"
}
@test 'Student not added to same grade in the roster more than once' {
run jq -c -f grade-school.jq << 'END_INPUT'
{
"property": "roster",
"input": {
"students": [
[
"Blair",
2
],
[
"James",
2
],
[
"James",
2
],
[
"Paul",
2
]
]
}
}
END_INPUT
assert_success
expected='["Blair","James","Paul"]'
assert_equal "$output" "$expected"
}
@test 'Students in multiple grades are added to the roster' {
run jq -c -f grade-school.jq << 'END_INPUT'
{
"property": "roster",
"input": {
"students": [
[
"Chelsea",
3
],
[
"Logan",
7
]
]
}
}
END_INPUT
assert_success
expected='["Chelsea","Logan"]'
assert_equal "$output" "$expected"
}
@test 'Student not added to multiple grades in the roster' {
run jq -c -f grade-school.jq << 'END_INPUT'
{
"property": "roster",
"input": {
"students": [
[
"Blair",
2
],
[
"James",
2
],
[
"James",
3
],
[
"Paul",
3
]
]
}
}
END_INPUT
assert_success
expected='["Blair","James","Paul"]'
assert_equal "$output" "$expected"
}
@test 'Students are sorted by grades in the roster' {
run jq -c -f grade-school.jq << 'END_INPUT'
{
"property": "roster",
"input": {
"students": [
[
"Jim",
3
],
[
"Peter",
2
],
[
"Anna",
1
]
]
}
}
END_INPUT
assert_success
expected='["Anna","Peter","Jim"]'
assert_equal "$output" "$expected"
}
@test 'Students are sorted by name in the roster' {
run jq -c -f grade-school.jq << 'END_INPUT'
{
"property": "roster",
"input": {
"students": [
[
"Peter",
2
],
[
"Zoe",
2
],
[
"Alex",
2
]
]
}
}
END_INPUT
assert_success
expected='["Alex","Peter","Zoe"]'
assert_equal "$output" "$expected"
}
@test 'Students are sorted by grades and then by name in the roster' {
run jq -c -f grade-school.jq << 'END_INPUT'
{
"property": "roster",
"input": {
"students": [
[
"Peter",
2
],
[
"Anna",
1
],
[
"Barb",
1
],
[
"Zoe",
2
],
[
"Alex",
2
],
[
"Jim",
3
],
[
"Charlie",
1
]
]
}
}
END_INPUT
assert_success
expected='["Anna","Barb","Charlie","Alex","Peter","Zoe","Jim"]'
assert_equal "$output" "$expected"
}
@test 'Grade is empty if no students in the roster' {
run jq -c -f grade-school.jq << 'END_INPUT'
{
"property": "grade",
"input": {
"students": [],
"desiredGrade": 1
}
}
END_INPUT
assert_success
expected='[]'
assert_equal "$output" "$expected"
}
@test 'Grade is empty if no students in that grade' {
run jq -c -f grade-school.jq << 'END_INPUT'
{
"property": "grade",
"input": {
"students": [
[
"Peter",
2
],
[
"Zoe",
2
],
[
"Alex",
2
],
[
"Jim",
3
]
],
"desiredGrade": 1
}
}
END_INPUT
assert_success
expected='[]'
assert_equal "$output" "$expected"
}
@test 'Student not added to same grade more than once' {
run jq -c -f grade-school.jq << 'END_INPUT'
{
"property": "grade",
"input": {
"students": [
[
"Blair",
2
],
[
"James",
2
],
[
"James",
2
],
[
"Paul",
2
]
],
"desiredGrade": 2
}
}
END_INPUT
assert_success
expected='["Blair","James","Paul"]'
assert_equal "$output" "$expected"
}
@test 'Student not added to multiple grades' {
run jq -c -f grade-school.jq << 'END_INPUT'
{
"property": "grade",
"input": {
"students": [
[
"Blair",
2
],
[
"James",
2
],
[
"James",
3
],
[
"Paul",
3
]
],
"desiredGrade": 2
}
}
END_INPUT
assert_success
expected='["Blair","James"]'
assert_equal "$output" "$expected"
}
@test 'Student not added to other grade for multiple grades' {
run jq -c -f grade-school.jq << 'END_INPUT'
{
"property": "grade",
"input": {
"students": [
[
"Blair",
2
],
[
"James",
2
],
[
"James",
3
],
[
"Paul",
3
]
],
"desiredGrade": 3
}
}
END_INPUT
assert_success
expected='["Paul"]'
assert_equal "$output" "$expected"
}
@test 'Students are sorted by name in a grade' {
run jq -c -f grade-school.jq << 'END_INPUT'
{
"property": "grade",
"input": {
"students": [
[
"Franklin",
5
],
[
"Bradley",
5
],
[
"Jeff",
1
]
],
"desiredGrade": 5
}
}
END_INPUT
assert_success
expected='["Bradley","Franklin"]'
assert_equal "$output" "$expected"
}

View file

@ -0,0 +1,19 @@
{
"authors": [
"glennj"
],
"files": {
"solution": [
"proverb.jq"
],
"test": [
"test-proverb.bats"
],
"example": [
".meta/example.jq"
]
},
"blurb": "For want of a horseshoe nail, a kingdom was lost, or so the saying goes. Output the full text of this proverbial rhyme.",
"source": "Wikipedia",
"source_url": "https://en.wikipedia.org/wiki/For_Want_of_a_Nail"
}

View file

@ -0,0 +1 @@
{"track":"jq","exercise":"proverb","id":"fd531621f76644938acdfcbaab43d94a","url":"https://exercism.org/tracks/jq/exercises/proverb","handle":"cafkafk","is_requester":true,"auto_approve":false}

114
jq/proverb/HELP.md Normal file
View file

@ -0,0 +1,114 @@
# Help
## Running the tests
Each exercise contains a test file.
Run the tests using the `bats` program.
```bash
bats test-hello-world.bats
```
`bats` will need to be installed.
See the [Testing on the Bash track][bash] page for instructions to install `bats` for your system.
### bats is implemented in bash
The bats file is a bash script, with some special functions recognized by the `bats` command.
You'll see some tests that look like
```sh
jq -f some-exercise.jq <<< "{some,json,here}"
```
That `<<<` syntax is a bash [Here String][here-string].
It sends the string on the right-hand side into the standard input of the program on the left-hand side.
It is ([approximately][so]) the same as
```sh
echo "{some,json,here}" | jq -f some-exercise.jq
```
## Help for assert functions
The tests use functions from the [bats-assert][bats-assert] library.
Help for the various `assert*` functions can be found there.
## Skipped tests
Solving an exercise means making all its tests pass.
By default, only one test (the first one) is executed when you run the tests.
This is intentional, as it allows you to focus on just making that one test pass.
Once it passes, you can enable the next test by commenting out or removing the
[[ $BATS_RUN_SKIPPED == true ]] || skip
annotations prepending other tests.
## Overriding skips
To run all tests, including the ones with `skip` annotations, you can run:
```bash
BATS_RUN_SKIPPED=true bats test-some-exercise.bats
```
It can be convenient to use a wrapper function to save on typing: in `bash` you can do:
```bash
bats() {
BATS_RUN_SKIPPED=true command bats *.bats
}
```
Then run tests with just:
```bash
bats
```
## Debugging in `jq`
`jq` comes with a handy [`debug`][debug] filter.
Use it while you are developing your exercise solutions to inspect the data that is currently in the jq pipline.
See the [debugging doc][debugging] for more details.
[bash]: https://exercism.org/docs/tracks/bash/tests
[bats-assert]: https://github.com/bats-core/bats-assert
[here-string]: https://www.gnu.org/software/bash/manual/bash.html#Here-Strings
[so]: https://unix.stackexchange.com/a/80372/4667
[debug]: https://jqlang.github.io/jq/manual/v1.7/#debug
[debugging]: https://exercism.org/docs/tracks/jq/debugging
## Submitting your solution
You can submit your solution using the `exercism submit proverb.jq` command.
This command will upload your solution to the Exercism website and print the solution page's URL.
It's possible to submit an incomplete solution which allows you to:
- See how others have completed the exercise
- Request help from a mentor
## Need to get help?
If you'd like help solving the exercise, check the following pages:
- The [jq track's documentation](https://exercism.org/docs/tracks/jq)
- The [jq track's programming category on the forum](https://forum.exercism.org/c/programming/jq)
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
## Need more help?
- Go to the [Exercism Community forum](https://forum.exercism.org) to get support and ask questions (or just chat!)
- Use the [Exercism Support](https://forum.exercism.org/c/support/8) category if you face any issues with working in the web editor, or downloading or submitting your exercises locally.
- Use the [Programming:jq](https://forum.exercism.org/c/programming/jq/133) category for jq-specific topics.
- Join the community on [Exercism's Discord server](https://exercism.org/r/discord).
- [StackOverflow](https://stackoverflow.com/questions/tagged/jq) can be used to search for your problem and see if it has been answered already.
You can also ask and answer questions.
- [Github issue tracker](https://github.com/exercism/jq/issues) is where we track our development and maintainance of `jq` exercises in exercism.
If none of the above links help you, feel free to post an issue here.

34
jq/proverb/README.md Normal file
View file

@ -0,0 +1,34 @@
# Proverb
Welcome to Proverb on Exercism's jq Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
For want of a horseshoe nail, a kingdom was lost, or so the saying goes.
Given a list of inputs, generate the relevant proverb.
For example, given the list `["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"]`, you will output the full text of this proverbial rhyme:
```text
For want of a nail the shoe was lost.
For want of a shoe the horse was lost.
For want of a horse the rider was lost.
For want of a rider the message was lost.
For want of a message the battle was lost.
For want of a battle the kingdom was lost.
And all for the want of a nail.
```
Note that the list of inputs may vary; your solution should be able to handle lists of arbitrary length and content.
No line of the output text should be a static, unchanging string; all should vary according to the input given.
## Source
### Created by
- @glennj
### Based on
Wikipedia - https://en.wikipedia.org/wiki/For_Want_of_a_Nail

637
jq/proverb/bats-extra.bash Normal file
View file

@ -0,0 +1,637 @@
# This is the source code for bats-support and bats-assert, concatenated
# * https://github.com/bats-core/bats-support
# * https://github.com/bats-core/bats-assert
#
# Comments have been removed to save space. See the git repos for full source code.
############################################################
#
# bats-support - Supporting library for Bats test helpers
#
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
#
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the
# public domain worldwide. This software is distributed without any
# warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication
# along with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
fail() {
(( $# == 0 )) && batslib_err || batslib_err "$@"
return 1
}
batslib_is_caller() {
local -i is_mode_direct=1
# Handle options.
while (( $# > 0 )); do
case "$1" in
-i|--indirect) is_mode_direct=0; shift ;;
--) shift; break ;;
*) break ;;
esac
done
# Arguments.
local -r func="$1"
# Check call stack.
if (( is_mode_direct )); then
[[ $func == "${FUNCNAME[2]}" ]] && return 0
else
local -i depth
for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do
[[ $func == "${FUNCNAME[$depth]}" ]] && return 0
done
fi
return 1
}
batslib_err() {
{ if (( $# > 0 )); then
echo "$@"
else
cat -
fi
} >&2
}
batslib_count_lines() {
local -i n_lines=0
local line
while IFS='' read -r line || [[ -n $line ]]; do
(( ++n_lines ))
done < <(printf '%s' "$1")
echo "$n_lines"
}
batslib_is_single_line() {
for string in "$@"; do
(( $(batslib_count_lines "$string") > 1 )) && return 1
done
return 0
}
batslib_get_max_single_line_key_width() {
local -i max_len=-1
while (( $# != 0 )); do
local -i key_len="${#1}"
batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len"
shift 2
done
echo "$max_len"
}
batslib_print_kv_single() {
local -ir col_width="$1"; shift
while (( $# != 0 )); do
printf '%-*s : %s\n' "$col_width" "$1" "$2"
shift 2
done
}
batslib_print_kv_multi() {
while (( $# != 0 )); do
printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )"
printf '%s\n' "$2"
shift 2
done
}
batslib_print_kv_single_or_multi() {
local -ir width="$1"; shift
local -a pairs=( "$@" )
local -a values=()
local -i i
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
values+=( "${pairs[$i]}" )
done
if batslib_is_single_line "${values[@]}"; then
batslib_print_kv_single "$width" "${pairs[@]}"
else
local -i i
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )"
done
batslib_print_kv_multi "${pairs[@]}"
fi
}
batslib_prefix() {
local -r prefix="${1:- }"
local line
while IFS='' read -r line || [[ -n $line ]]; do
printf '%s%s\n' "$prefix" "$line"
done
}
batslib_mark() {
local -r symbol="$1"; shift
# Sort line numbers.
set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" )
local line
local -i idx=0
while IFS='' read -r line || [[ -n $line ]]; do
if (( ${1:--1} == idx )); then
printf '%s\n' "${symbol}${line:${#symbol}}"
shift
else
printf '%s\n' "$line"
fi
(( ++idx ))
done
}
batslib_decorate() {
echo
echo "-- $1 --"
cat -
echo '--'
echo
}
############################################################
assert() {
if ! "$@"; then
batslib_print_kv_single 10 'expression' "$*" \
| batslib_decorate 'assertion failed' \
| fail
fi
}
assert_equal() {
if [[ $1 != "$2" ]]; then
batslib_print_kv_single_or_multi 8 \
'expected' "$2" \
'actual' "$1" \
| batslib_decorate 'values do not equal' \
| fail
fi
}
assert_failure() {
: "${output?}"
: "${status?}"
(( $# > 0 )) && local -r expected="$1"
if (( status == 0 )); then
batslib_print_kv_single_or_multi 6 'output' "$output" \
| batslib_decorate 'command succeeded, but it was expected to fail' \
| fail
elif (( $# > 0 )) && (( status != expected )); then
{ local -ir width=8
batslib_print_kv_single "$width" \
'expected' "$expected" \
'actual' "$status"
batslib_print_kv_single_or_multi "$width" \
'output' "$output"
} \
| batslib_decorate 'command failed as expected, but status differs' \
| fail
fi
}
assert_line() {
local -i is_match_line=0
local -i is_mode_partial=0
local -i is_mode_regexp=0
: "${lines?}"
# Handle options.
while (( $# > 0 )); do
case "$1" in
-n|--index)
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
echo "\`--index' requires an integer argument: \`$2'" \
| batslib_decorate 'ERROR: assert_line' \
| fail
return $?
fi
is_match_line=1
local -ri idx="$2"
shift 2
;;
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: assert_line' \
| fail
return $?
fi
# Arguments.
local -r expected="$1"
if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$expected'" \
| batslib_decorate 'ERROR: assert_line' \
| fail
return $?
fi
# Matching.
if (( is_match_line )); then
# Specific line.
if (( is_mode_regexp )); then
if ! [[ ${lines[$idx]} =~ $expected ]]; then
batslib_print_kv_single 6 \
'index' "$idx" \
'regexp' "$expected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'regular expression does not match line' \
| fail
fi
elif (( is_mode_partial )); then
if [[ ${lines[$idx]} != *"$expected"* ]]; then
batslib_print_kv_single 9 \
'index' "$idx" \
'substring' "$expected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line does not contain substring' \
| fail
fi
else
if [[ ${lines[$idx]} != "$expected" ]]; then
batslib_print_kv_single 8 \
'index' "$idx" \
'expected' "$expected" \
'actual' "${lines[$idx]}" \
| batslib_decorate 'line differs' \
| fail
fi
fi
else
# Contained in output.
if (( is_mode_regexp )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} =~ $expected ]] && return 0
done
{ local -ar single=( 'regexp' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
} \
| batslib_decorate 'no output line matches regular expression' \
| fail
elif (( is_mode_partial )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} == *"$expected"* ]] && return 0
done
{ local -ar single=( 'substring' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
} \
| batslib_decorate 'no output line contains substring' \
| fail
else
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} == "$expected" ]] && return 0
done
{ local -ar single=( 'line' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
} \
| batslib_decorate 'output does not contain line' \
| fail
fi
fi
}
assert_output() {
local -i is_mode_partial=0
local -i is_mode_regexp=0
local -i is_mode_nonempty=0
local -i use_stdin=0
: "${output?}"
# Handle options.
if (( $# == 0 )); then
is_mode_nonempty=1
fi
while (( $# > 0 )); do
case "$1" in
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
-|--stdin) use_stdin=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: assert_output' \
| fail
return $?
fi
# Arguments.
local expected
if (( use_stdin )); then
expected="$(cat -)"
else
expected="${1-}"
fi
# Matching.
if (( is_mode_nonempty )); then
if [ -z "$output" ]; then
echo 'expected non-empty output, but output was empty' \
| batslib_decorate 'no output' \
| fail
fi
elif (( is_mode_regexp )); then
if [[ '' =~ $expected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$expected'" \
| batslib_decorate 'ERROR: assert_output' \
| fail
elif ! [[ $output =~ $expected ]]; then
batslib_print_kv_single_or_multi 6 \
'regexp' "$expected" \
'output' "$output" \
| batslib_decorate 'regular expression does not match output' \
| fail
fi
elif (( is_mode_partial )); then
if [[ $output != *"$expected"* ]]; then
batslib_print_kv_single_or_multi 9 \
'substring' "$expected" \
'output' "$output" \
| batslib_decorate 'output does not contain substring' \
| fail
fi
else
if [[ $output != "$expected" ]]; then
batslib_print_kv_single_or_multi 8 \
'expected' "$expected" \
'actual' "$output" \
| batslib_decorate 'output differs' \
| fail
fi
fi
}
assert_success() {
: "${output?}"
: "${status?}"
if (( status != 0 )); then
{ local -ir width=6
batslib_print_kv_single "$width" 'status' "$status"
batslib_print_kv_single_or_multi "$width" 'output' "$output"
} \
| batslib_decorate 'command failed' \
| fail
fi
}
refute() {
if "$@"; then
batslib_print_kv_single 10 'expression' "$*" \
| batslib_decorate 'assertion succeeded, but it was expected to fail' \
| fail
fi
}
refute_line() {
local -i is_match_line=0
local -i is_mode_partial=0
local -i is_mode_regexp=0
: "${lines?}"
# Handle options.
while (( $# > 0 )); do
case "$1" in
-n|--index)
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
echo "\`--index' requires an integer argument: \`$2'" \
| batslib_decorate 'ERROR: refute_line' \
| fail
return $?
fi
is_match_line=1
local -ri idx="$2"
shift 2
;;
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: refute_line' \
| fail
return $?
fi
# Arguments.
local -r unexpected="$1"
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$unexpected'" \
| batslib_decorate 'ERROR: refute_line' \
| fail
return $?
fi
# Matching.
if (( is_match_line )); then
# Specific line.
if (( is_mode_regexp )); then
if [[ ${lines[$idx]} =~ $unexpected ]]; then
batslib_print_kv_single 6 \
'index' "$idx" \
'regexp' "$unexpected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'regular expression should not match line' \
| fail
fi
elif (( is_mode_partial )); then
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
batslib_print_kv_single 9 \
'index' "$idx" \
'substring' "$unexpected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line should not contain substring' \
| fail
fi
else
if [[ ${lines[$idx]} == "$unexpected" ]]; then
batslib_print_kv_single 5 \
'index' "$idx" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line should differ' \
| fail
fi
fi
else
# Line contained in output.
if (( is_mode_regexp )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} =~ $unexpected ]]; then
{ local -ar single=( 'regexp' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
if batslib_is_single_line "${may_be_multi[1]}"; then
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'no line should match the regular expression' \
| fail
return $?
fi
done
elif (( is_mode_partial )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
{ local -ar single=( 'substring' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
if batslib_is_single_line "${may_be_multi[1]}"; then
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'no line should contain substring' \
| fail
return $?
fi
done
else
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} == "$unexpected" ]]; then
{ local -ar single=( 'line' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
if batslib_is_single_line "${may_be_multi[1]}"; then
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'line should not be in output' \
| fail
return $?
fi
done
fi
fi
}
refute_output() {
local -i is_mode_partial=0
local -i is_mode_regexp=0
local -i is_mode_empty=0
local -i use_stdin=0
: "${output?}"
# Handle options.
if (( $# == 0 )); then
is_mode_empty=1
fi
while (( $# > 0 )); do
case "$1" in
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
-|--stdin) use_stdin=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: refute_output' \
| fail
return $?
fi
# Arguments.
local unexpected
if (( use_stdin )); then
unexpected="$(cat -)"
else
unexpected="${1-}"
fi
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$unexpected'" \
| batslib_decorate 'ERROR: refute_output' \
| fail
return $?
fi
# Matching.
if (( is_mode_empty )); then
if [ -n "$output" ]; then
batslib_print_kv_single_or_multi 6 \
'output' "$output" \
| batslib_decorate 'output non-empty, but expected no output' \
| fail
fi
elif (( is_mode_regexp )); then
if [[ $output =~ $unexpected ]]; then
batslib_print_kv_single_or_multi 6 \
'regexp' "$unexpected" \
'output' "$output" \
| batslib_decorate 'regular expression should not match output' \
| fail
fi
elif (( is_mode_partial )); then
if [[ $output == *"$unexpected"* ]]; then
batslib_print_kv_single_or_multi 9 \
'substring' "$unexpected" \
'output' "$output" \
| batslib_decorate 'output should not contain substring' \
| fail
fi
else
if [[ $output == "$unexpected" ]]; then
batslib_print_kv_single_or_multi 6 \
'output' "$output" \
| batslib_decorate 'output equals, but it was expected to differ' \
| fail
fi
fi
}

29
jq/proverb/bats-jq.bash Normal file
View file

@ -0,0 +1,29 @@
#!/usr/bin/env bash
#
# `bats-core` will consume both stdout and stderr for the `run` command's output.
# However `jq` prints its DEBUG output on stderr.
#
# Lines starting with `["DEBUG:",` will be prefixed with a hash and printed on file descriptor 3.
# Other lines on stderr will remain on stderr for bats to consume.
#
# See `bats-core` docs:
# - "Printing to the terminal", https://bats-core.readthedocs.io/en/stable/writing-tests.html#printing-to-the-terminal
# - "File descriptor 3", https://bats-core.readthedocs.io/en/stable/writing-tests.html#file-descriptor-3-read-this-if-bats-hangs
jq() {
local output stderr rc line
stderr=$(mktemp)
output=$(command jq "$@" 2> "$stderr")
rc=$?
while IFS= read -r line || [[ -n $line ]]; do
if [[ $line == '["DEBUG:",'* ]]; then
echo "# $line" >&3
else
echo "$line" >&2
fi
done < "$stderr"
rm -f "$stderr"
echo "$output"
return "$rc"
}

1
jq/proverb/proverb.jq Normal file
View file

@ -0,0 +1 @@
"Remove this line and implement your solution" | halt_error

View file

@ -0,0 +1,111 @@
#!/usr/bin/env bats
# generated on 2022-11-02T20:59:37Z
load bats-extra
load bats-jq
@test 'zero pieces' {
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run jq -c -f proverb.jq << 'END_INPUT'
{
"strings": []
}
END_INPUT
assert_success
expected='[]'
assert_equal "$output" "$expected"
}
@test 'one piece' {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run jq -c -f proverb.jq << 'END_INPUT'
{
"strings": [
"nail"
]
}
END_INPUT
assert_success
expected='["And all for the want of a nail."]'
assert_equal "$output" "$expected"
}
@test 'two pieces' {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run jq -c -f proverb.jq << 'END_INPUT'
{
"strings": [
"nail",
"shoe"
]
}
END_INPUT
assert_success
expected='["For want of a nail the shoe was lost.","And all for the want of a nail."]'
assert_equal "$output" "$expected"
}
@test 'three pieces' {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run jq -c -f proverb.jq << 'END_INPUT'
{
"strings": [
"nail",
"shoe",
"horse"
]
}
END_INPUT
assert_success
expected='["For want of a nail the shoe was lost.","For want of a shoe the horse was lost.","And all for the want of a nail."]'
assert_equal "$output" "$expected"
}
@test 'full proverb' {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run jq -c -f proverb.jq << 'END_INPUT'
{
"strings": [
"nail",
"shoe",
"horse",
"rider",
"message",
"battle",
"kingdom"
]
}
END_INPUT
assert_success
expected='["For want of a nail the shoe was lost.","For want of a shoe the horse was lost.","For want of a horse the rider was lost.","For want of a rider the message was lost.","For want of a message the battle was lost.","For want of a battle the kingdom was lost.","And all for the want of a nail."]'
assert_equal "$output" "$expected"
}
@test 'four pieces modernized' {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run jq -c -f proverb.jq << 'END_INPUT'
{
"strings": [
"pin",
"gun",
"soldier",
"battle"
]
}
END_INPUT
assert_success
expected='["For want of a pin the gun was lost.","For want of a gun the soldier was lost.","For want of a soldier the battle was lost.","And all for the want of a pin."]'
assert_equal "$output" "$expected"
}