diff --git a/jq/gigasecond/.exercism/config.json b/jq/gigasecond/.exercism/config.json new file mode 100644 index 0000000..ae29eef --- /dev/null +++ b/jq/gigasecond/.exercism/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "gigasecond.jq" + ], + "test": [ + "test-gigasecond.bats" + ], + "example": [ + ".meta/example.jq" + ] + }, + "blurb": "Given a moment, determine the moment that would be after a gigasecond has passed.", + "source": "Chapter 9 in Chris Pine's online Learn to Program tutorial.", + "source_url": "https://pine.fm/LearnToProgram/?Chapter=09" +} diff --git a/jq/gigasecond/.exercism/metadata.json b/jq/gigasecond/.exercism/metadata.json new file mode 100644 index 0000000..bba88b0 --- /dev/null +++ b/jq/gigasecond/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"jq","exercise":"gigasecond","id":"32436c09d4d54b00a98f11eba8015388","url":"https://exercism.org/tracks/jq/exercises/gigasecond","handle":"cafkafk","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/jq/gigasecond/HELP.md b/jq/gigasecond/HELP.md new file mode 100644 index 0000000..134fbc0 --- /dev/null +++ b/jq/gigasecond/HELP.md @@ -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 gigasecond.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. \ No newline at end of file diff --git a/jq/gigasecond/README.md b/jq/gigasecond/README.md new file mode 100644 index 0000000..97648d5 --- /dev/null +++ b/jq/gigasecond/README.md @@ -0,0 +1,52 @@ +# Gigasecond + +Welcome to Gigasecond on Exercism's jq Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Introduction + +The way we measure time is kind of messy. +We have 60 seconds in a minute, and 60 minutes in an hour. +This comes from ancient Babylon, where they used 60 as the basis for their number system. +We have 24 hours in a day, 7 days in a week, and how many days in a month? +Well, for days in a month it depends not only on which month it is, but also on what type of calendar is used in the country you live in. + +What if, instead, we only use seconds to express time intervals? +Then we can use metric system prefixes for writing large numbers of seconds in more easily comprehensible quantities. + +- A food recipe might explain that you need to let the brownies cook in the oven for two kiloseconds (that's two thousand seconds). +- Perhaps you and your family would travel to somewhere exotic for two megaseconds (that's two million seconds). +- And if you and your spouse were married for _a thousand million_ seconds, you would celebrate your one gigasecond anniversary. + +~~~~exercism/note +If we ever colonize Mars or some other planet, measuring time is going to get even messier. +If someone says "year" do they mean a year on Earth or a year on Mars? + +The idea for this exercise came from the science fiction novel ["A Deepness in the Sky"][vinge-novel] by author Vernor Vinge. +In it the author uses the metric system as the basis for time measurements. + +[vinge-novel]: https://www.tor.com/2017/08/03/science-fiction-with-something-for-everyone-a-deepness-in-the-sky-by-vernor-vinge/ +~~~~ + +## Instructions + +Your task is to determine the date and time one gigasecond after a certain date. + +A gigasecond is one thousand million seconds. +That is a one with nine zeros after it. + +If you were born on _January 24th, 2015 at 22:00 (10:00:00pm)_, then you would be a gigasecond old on _October 2nd, 2046 at 23:46:40 (11:46:40pm)_. + +To solve this exercise in jq, you will need to use the [builtin datetime functions][date-funcs]. + +[date-funcs]: https://jqlang.github.io/jq/manual/v1.7/#dates + +## Source + +### Created by + +- @glennj + +### Based on + +Chapter 9 in Chris Pine's online Learn to Program tutorial. - https://pine.fm/LearnToProgram/?Chapter=09 \ No newline at end of file diff --git a/jq/gigasecond/bats-extra.bash b/jq/gigasecond/bats-extra.bash new file mode 100644 index 0000000..54d4807 --- /dev/null +++ b/jq/gigasecond/bats-extra.bash @@ -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 +# +# 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 +# . +# + +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 +} diff --git a/jq/gigasecond/bats-jq.bash b/jq/gigasecond/bats-jq.bash new file mode 100644 index 0000000..3f55da5 --- /dev/null +++ b/jq/gigasecond/bats-jq.bash @@ -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" +} diff --git a/jq/gigasecond/gigasecond.jq b/jq/gigasecond/gigasecond.jq new file mode 100644 index 0000000..583d649 --- /dev/null +++ b/jq/gigasecond/gigasecond.jq @@ -0,0 +1 @@ +.moment + ("Z", "T00:00:00Z")|fromdate? + 1e9 |todate[:-1] diff --git a/jq/gigasecond/test-gigasecond.bats b/jq/gigasecond/test-gigasecond.bats new file mode 100644 index 0000000..7434031 --- /dev/null +++ b/jq/gigasecond/test-gigasecond.bats @@ -0,0 +1,46 @@ +#!/usr/bin/env bats +load bats-extra +load bats-jq + +# Ensure date calculations are done using UTC time zone +export TZ=UTC + +@test 'date only specificaion of time' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + run jq -r -f gigasecond.jq <<< '{"moment": "2011-04-25"}' + + assert_success + assert_output "2043-01-01T01:46:40" +} + +@test 'second test for date only specification of time' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + run jq -r -f gigasecond.jq <<< '{"moment": "1977-06-13"}' + + assert_success + assert_output "2009-02-19T01:46:40" +} + +@test 'third test for date only specification of time' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + run jq -r -f gigasecond.jq <<< '{"moment": "1959-07-19"}' + + assert_success + assert_output "1991-03-27T01:46:40" +} + +@test 'full time specified' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + run jq -r -f gigasecond.jq <<< '{"moment": "2015-01-24T22:00:00"}' + + assert_success + assert_output "2046-10-02T23:46:40" +} + +@test 'full time with day roll-over' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + run jq -r -f gigasecond.jq <<< '{"moment": "2015-01-24T23:59:59"}' + + assert_success + assert_output "2046-10-03T01:46:39" +} diff --git a/jq/nucleotide-count/.exercism/config.json b/jq/nucleotide-count/.exercism/config.json new file mode 100644 index 0000000..a261dc5 --- /dev/null +++ b/jq/nucleotide-count/.exercism/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "nucleotide-count.jq" + ], + "test": [ + "test-nucleotide-count.bats" + ], + "example": [ + ".meta/example.jq" + ] + }, + "blurb": "Given a DNA string, compute how many times each nucleotide occurs in the string.", + "source": "The Calculating DNA Nucleotides_problem at Rosalind", + "source_url": "https://rosalind.info/problems/dna/" +} diff --git a/jq/nucleotide-count/.exercism/metadata.json b/jq/nucleotide-count/.exercism/metadata.json new file mode 100644 index 0000000..7b26a81 --- /dev/null +++ b/jq/nucleotide-count/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"jq","exercise":"nucleotide-count","id":"9d5bdac393f1422ebe94b4857abe6276","url":"https://exercism.org/tracks/jq/exercises/nucleotide-count","handle":"cafkafk","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/jq/nucleotide-count/HELP.md b/jq/nucleotide-count/HELP.md new file mode 100644 index 0000000..a25c90f --- /dev/null +++ b/jq/nucleotide-count/HELP.md @@ -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 nucleotide-count.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. \ No newline at end of file diff --git a/jq/nucleotide-count/README.md b/jq/nucleotide-count/README.md new file mode 100644 index 0000000..4fcbbef --- /dev/null +++ b/jq/nucleotide-count/README.md @@ -0,0 +1,38 @@ +# Nucleotide Count + +Welcome to Nucleotide Count on Exercism's jq Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Instructions + +Each of us inherits from our biological parents a set of chemical instructions known as DNA that influence how our bodies are constructed. +All known life depends on DNA! + +> Note: You do not need to understand anything about nucleotides or DNA to complete this exercise. + +DNA is a long chain of other chemicals and the most important are the four nucleotides, adenine, cytosine, guanine and thymine. +A single DNA chain can contain billions of these four nucleotides and the order in which they occur is important! +We call the order of these nucleotides in a bit of DNA a "DNA sequence". + +We represent a DNA sequence as an ordered collection of these four nucleotides and a common way to do that is with a string of characters such as "ATTACG" for a DNA sequence of 6 nucleotides. +'A' for adenine, 'C' for cytosine, 'G' for guanine, and 'T' for thymine. + +Given a string representing a DNA sequence, count how many of each nucleotide is present. +If the string contains characters that aren't A, C, G, or T then it is invalid and you should signal an error. + +For example: + +```text +"GATTACA" -> 'A': 3, 'C': 1, 'G': 1, 'T': 2 +"INVALID" -> error +``` + +## Source + +### Created by + +- @glennj + +### Based on + +The Calculating DNA Nucleotides_problem at Rosalind - https://rosalind.info/problems/dna/ \ No newline at end of file diff --git a/jq/nucleotide-count/bats-extra.bash b/jq/nucleotide-count/bats-extra.bash new file mode 100644 index 0000000..54d4807 --- /dev/null +++ b/jq/nucleotide-count/bats-extra.bash @@ -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 +# +# 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 +# . +# + +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 +} diff --git a/jq/nucleotide-count/bats-jq.bash b/jq/nucleotide-count/bats-jq.bash new file mode 100644 index 0000000..3f55da5 --- /dev/null +++ b/jq/nucleotide-count/bats-jq.bash @@ -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" +} diff --git a/jq/nucleotide-count/nucleotide-count.jq b/jq/nucleotide-count/nucleotide-count.jq new file mode 100644 index 0000000..8cc578b --- /dev/null +++ b/jq/nucleotide-count/nucleotide-count.jq @@ -0,0 +1,9 @@ +.strand + | reduce (.|ascii_upcase/"")[] as $n + ( + {A: 0, C: 0, G: 0, T: 0}; + if .|has("\($n)") + then .["\($n)"] += 1 + else "Invalid nucleotide in strand"|halt_error + end + ) diff --git a/jq/nucleotide-count/test-nucleotide-count.bats b/jq/nucleotide-count/test-nucleotide-count.bats new file mode 100644 index 0000000..80b27c4 --- /dev/null +++ b/jq/nucleotide-count/test-nucleotide-count.bats @@ -0,0 +1,83 @@ +#!/usr/bin/env bats +# generated on 2023-11-07T18:49:21Z +load bats-extra +load bats-jq + +assert_objects_equal() { + local result=$( + jq -n --argjson actual "$1" \ + --argjson expected "$2" \ + '$actual == $expected' + ) + [[ $result == "true" ]] +} + +@test 'empty strand' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -c -f nucleotide-count.jq << 'END_INPUT' + { + "strand": "" + } +END_INPUT + + assert_success + expected='{"A":0,"C":0,"G":0,"T":0}' + assert_objects_equal "$output" "$expected" +} + +@test 'can count one nucleotide in single-character input' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -c -f nucleotide-count.jq << 'END_INPUT' + { + "strand": "G" + } +END_INPUT + + assert_success + expected='{"A":0,"C":0,"G":1,"T":0}' + assert_objects_equal "$output" "$expected" +} + +@test 'strand with repeated nucleotide' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -c -f nucleotide-count.jq << 'END_INPUT' + { + "strand": "GGGGGGG" + } +END_INPUT + + assert_success + expected='{"A":0,"C":0,"G":7,"T":0}' + assert_objects_equal "$output" "$expected" +} + +@test 'strand with multiple nucleotides' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -c -f nucleotide-count.jq << 'END_INPUT' + { + "strand": "AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC" + } +END_INPUT + + assert_success + expected='{"A":20,"C":12,"G":17,"T":21}' + assert_objects_equal "$output" "$expected" +} + +@test 'strand with invalid nucleotides' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -c -f nucleotide-count.jq << 'END_INPUT' + { + "strand": "AGXXACT" + } +END_INPUT + + assert_failure + expected='Invalid nucleotide in strand' + assert_equal "$output" "$expected" +} diff --git a/jq/raindrops/.exercism/config.json b/jq/raindrops/.exercism/config.json new file mode 100644 index 0000000..2ca661a --- /dev/null +++ b/jq/raindrops/.exercism/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "raindrops.jq" + ], + "test": [ + "test-raindrops.bats" + ], + "example": [ + ".meta/example.jq" + ] + }, + "blurb": "Convert a number into its corresponding raindrop sounds - Pling, Plang and Plong.", + "source": "A variation on FizzBuzz, a famous technical interview question that is intended to weed out potential candidates. That question is itself derived from Fizz Buzz, a popular children's game for teaching division.", + "source_url": "https://en.wikipedia.org/wiki/Fizz_buzz" +} diff --git a/jq/raindrops/.exercism/metadata.json b/jq/raindrops/.exercism/metadata.json new file mode 100644 index 0000000..b5ad757 --- /dev/null +++ b/jq/raindrops/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"jq","exercise":"raindrops","id":"1d143aafe4324f8785666c5181bc576f","url":"https://exercism.org/tracks/jq/exercises/raindrops","handle":"cafkafk","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/jq/raindrops/HELP.md b/jq/raindrops/HELP.md new file mode 100644 index 0000000..fea7215 --- /dev/null +++ b/jq/raindrops/HELP.md @@ -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 raindrops.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. \ No newline at end of file diff --git a/jq/raindrops/README.md b/jq/raindrops/README.md new file mode 100644 index 0000000..8a33b0b --- /dev/null +++ b/jq/raindrops/README.md @@ -0,0 +1,66 @@ +# Raindrops + +Welcome to Raindrops on Exercism's jq Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Introduction + +Raindrops is a slightly more complex version of the FizzBuzz challenge, a classic interview question. + +## Instructions + +Your task is to convert a number into its corresponding raindrop sounds. + +If a given number: + +- is divisible by 3, add "Pling" to the result. +- is divisible by 5, add "Plang" to the result. +- is divisible by 7, add "Plong" to the result. +- **is not** divisible by 3, 5, or 7, the result should be the number as a string. + +## Examples + +- 28 is divisible by 7, but not 3 or 5, so the result would be `"Plong"`. +- 30 is divisible by 3 and 5, but not 7, so the result would be `"PlingPlang"`. +- 34 is not divisible by 3, 5, or 7, so the result would be `"34"`. + +~~~~exercism/note +A common way to test if one number is evenly divisible by another is to compare the [remainder][remainder] or [modulus][modulo] to zero. +Most languages provide operators or functions for one (or both) of these. + +[remainder]: https://exercism.org/docs/programming/operators/remainder +[modulo]: https://en.wikipedia.org/wiki/Modulo_operation +~~~~ + +## `jq` Tips + +The [`if-then-else` expression][if] will be helpful in this exercise. + +An example: + +```jq +8, 10, 12 | if . < 10 then "\(.) is less than ten" + elif . > 10 then "\(.) is more than ten" + else "\(.) equals ten" + end +``` + +outputs + +```json +"8 is less than ten" +"10 equals ten" +"12 is more than ten" +``` + +[if]: https://jqlang.github.io/jq/manual/v1.7/#if-then-else-end + +## Source + +### Created by + +- @glennj + +### Based on + +A variation on FizzBuzz, a famous technical interview question that is intended to weed out potential candidates. That question is itself derived from Fizz Buzz, a popular children's game for teaching division. - https://en.wikipedia.org/wiki/Fizz_buzz \ No newline at end of file diff --git a/jq/raindrops/bats-extra.bash b/jq/raindrops/bats-extra.bash new file mode 100644 index 0000000..54d4807 --- /dev/null +++ b/jq/raindrops/bats-extra.bash @@ -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 +# +# 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 +# . +# + +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 +} diff --git a/jq/raindrops/bats-jq.bash b/jq/raindrops/bats-jq.bash new file mode 100644 index 0000000..3f55da5 --- /dev/null +++ b/jq/raindrops/bats-jq.bash @@ -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" +} diff --git a/jq/raindrops/raindrops.jq b/jq/raindrops/raindrops.jq new file mode 100644 index 0000000..10c224f --- /dev/null +++ b/jq/raindrops/raindrops.jq @@ -0,0 +1 @@ +.number|([if . % 3 == 0 then "Pling" else null end, if . % 5 == 0 then "Plang" else null end, if . % 7 == 0 then "Plong" else null end]|add) // . diff --git a/jq/raindrops/test-raindrops.bats b/jq/raindrops/test-raindrops.bats new file mode 100644 index 0000000..99b2ae4 --- /dev/null +++ b/jq/raindrops/test-raindrops.bats @@ -0,0 +1,257 @@ +#!/usr/bin/env bats +# generated on 2022-11-02T20:59:38Z +load bats-extra +load bats-jq + +@test 'the sound for 1 is 1' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 1 + } +END_INPUT + + assert_success + expected='1' + assert_equal "$output" "$expected" +} + +@test 'the sound for 3 is Pling' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 3 + } +END_INPUT + + assert_success + expected='Pling' + assert_equal "$output" "$expected" +} + +@test 'the sound for 5 is Plang' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 5 + } +END_INPUT + + assert_success + expected='Plang' + assert_equal "$output" "$expected" +} + +@test 'the sound for 7 is Plong' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 7 + } +END_INPUT + + assert_success + expected='Plong' + assert_equal "$output" "$expected" +} + +@test 'the sound for 6 is Pling as it has a factor 3' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 6 + } +END_INPUT + + assert_success + expected='Pling' + assert_equal "$output" "$expected" +} + +@test '2 to the power 3 does not make a raindrop sound as 3 is the exponent not the base' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 8 + } +END_INPUT + + assert_success + expected='8' + assert_equal "$output" "$expected" +} + +@test 'the sound for 9 is Pling as it has a factor 3' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 9 + } +END_INPUT + + assert_success + expected='Pling' + assert_equal "$output" "$expected" +} + +@test 'the sound for 10 is Plang as it has a factor 5' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 10 + } +END_INPUT + + assert_success + expected='Plang' + assert_equal "$output" "$expected" +} + +@test 'the sound for 14 is Plong as it has a factor of 7' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 14 + } +END_INPUT + + assert_success + expected='Plong' + assert_equal "$output" "$expected" +} + +@test 'the sound for 15 is PlingPlang as it has factors 3 and 5' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 15 + } +END_INPUT + + assert_success + expected='PlingPlang' + assert_equal "$output" "$expected" +} + +@test 'the sound for 21 is PlingPlong as it has factors 3 and 7' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 21 + } +END_INPUT + + assert_success + expected='PlingPlong' + assert_equal "$output" "$expected" +} + +@test 'the sound for 25 is Plang as it has a factor 5' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 25 + } +END_INPUT + + assert_success + expected='Plang' + assert_equal "$output" "$expected" +} + +@test 'the sound for 27 is Pling as it has a factor 3' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 27 + } +END_INPUT + + assert_success + expected='Pling' + assert_equal "$output" "$expected" +} + +@test 'the sound for 35 is PlangPlong as it has factors 5 and 7' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 35 + } +END_INPUT + + assert_success + expected='PlangPlong' + assert_equal "$output" "$expected" +} + +@test 'the sound for 49 is Plong as it has a factor 7' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 49 + } +END_INPUT + + assert_success + expected='Plong' + assert_equal "$output" "$expected" +} + +@test 'the sound for 52 is 52' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 52 + } +END_INPUT + + assert_success + expected='52' + assert_equal "$output" "$expected" +} + +@test 'the sound for 105 is PlingPlangPlong as it has factors 3, 5 and 7' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 105 + } +END_INPUT + + assert_success + expected='PlingPlangPlong' + assert_equal "$output" "$expected" +} + +@test 'the sound for 3125 is Plang as it has a factor 5' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f raindrops.jq << 'END_INPUT' + { + "number": 3125 + } +END_INPUT + + assert_success + expected='Plang' + assert_equal "$output" "$expected" +} + diff --git a/jq/space-age/.exercism/config.json b/jq/space-age/.exercism/config.json new file mode 100644 index 0000000..0ec202e --- /dev/null +++ b/jq/space-age/.exercism/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "space-age.jq" + ], + "test": [ + "test-space-age.bats" + ], + "example": [ + ".meta/example.jq" + ] + }, + "blurb": "Given an age in seconds, calculate how old someone is in terms of a given planet's solar years.", + "source": "Partially inspired by Chapter 1 in Chris Pine's online Learn to Program tutorial.", + "source_url": "https://pine.fm/LearnToProgram/?Chapter=01" +} diff --git a/jq/space-age/.exercism/metadata.json b/jq/space-age/.exercism/metadata.json new file mode 100644 index 0000000..cc3882f --- /dev/null +++ b/jq/space-age/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"jq","exercise":"space-age","id":"70c9dce079bf441eb8f777fa8f20b1d5","url":"https://exercism.org/tracks/jq/exercises/space-age","handle":"cafkafk","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/jq/space-age/HELP.md b/jq/space-age/HELP.md new file mode 100644 index 0000000..9304648 --- /dev/null +++ b/jq/space-age/HELP.md @@ -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 space-age.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. \ No newline at end of file diff --git a/jq/space-age/README.md b/jq/space-age/README.md new file mode 100644 index 0000000..eb72ab4 --- /dev/null +++ b/jq/space-age/README.md @@ -0,0 +1,64 @@ +# Space Age + +Welcome to Space Age on Exercism's jq Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Introduction + +The year is 2525 and you've just embarked on a journey to visit all planets in the Solar System (Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus and Neptune). +The first stop is Mercury, where customs require you to fill out a form (bureaucracy is apparently _not_ Earth-specific). +As you hand over the form to the customs officer, they scrutinize it and frown. +"Do you _really_ expect me to believe you're just 50 years old? +You must be closer to 200 years old!" + +Amused, you wait for the customs officer to start laughing, but they appear to be dead serious. +You realize that you've entered your age in _Earth years_, but the officer expected it in _Mercury years_! +As Mercury's orbital period around the sun is significantly shorter than Earth, you're actually a lot older in Mercury years. +After some quick calculations, you're able to provide your age in Mercury Years. +The customs officer smiles, satisfied, and waves you through. +You make a mental note to pre-calculate your planet-specific age _before_ future customs checks, to avoid such mix-ups. + +~~~~exercism/note +If you're wondering why Pluto didn't make the cut, go watch [this YouTube video][pluto-video]. + +[pluto-video]: https://www.youtube.com/watch?v=Z_2gbGXzFbs +~~~~ + +## Instructions + +Given an age in seconds, calculate how old someone would be on a planet in our Solar System. + +One Earth year equals 365.25 Earth days, or 31,557,600 seconds. +If you were told someone was 1,000,000,000 seconds old, their age would be 31.69 Earth-years. + +For the other planets, you have to account for their orbital period in Earth Years: + +| Planet | Orbital period in Earth Years | +| ------- | ----------------------------- | +| Mercury | 0.2408467 | +| Venus | 0.61519726 | +| Earth | 1.0 | +| Mars | 1.8808158 | +| Jupiter | 11.862615 | +| Saturn | 29.447498 | +| Uranus | 84.016846 | +| Neptune | 164.79132 | + +~~~~exercism/note +The actual length of one complete orbit of the Earth around the sun is closer to 365.256 days (1 sidereal year). +The Gregorian calendar has, on average, 365.2425 days. +While not entirely accurate, 365.25 is the value used in this exercise. +See [Year on Wikipedia][year] for more ways to measure a year. + +[year]: https://en.wikipedia.org/wiki/Year#Summary +~~~~ + +## Source + +### Created by + +- @glennj + +### Based on + +Partially inspired by Chapter 1 in Chris Pine's online Learn to Program tutorial. - https://pine.fm/LearnToProgram/?Chapter=01 \ No newline at end of file diff --git a/jq/space-age/bats-extra.bash b/jq/space-age/bats-extra.bash new file mode 100644 index 0000000..54d4807 --- /dev/null +++ b/jq/space-age/bats-extra.bash @@ -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 +# +# 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 +# . +# + +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 +} diff --git a/jq/space-age/bats-jq.bash b/jq/space-age/bats-jq.bash new file mode 100644 index 0000000..3f55da5 --- /dev/null +++ b/jq/space-age/bats-jq.bash @@ -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" +} diff --git a/jq/space-age/space-age.jq b/jq/space-age/space-age.jq new file mode 100644 index 0000000..956dc4e --- /dev/null +++ b/jq/space-age/space-age.jq @@ -0,0 +1,16 @@ +def two_decimal: ((. * 100) | round) / 100; + +def ratios: + { + Mercury: 0.2408467, + Venus: 0.61519726, + Earth: 1.0, + Mars: 1.8808158, + Jupiter: 11.862615, + Saturn: 29.447498, + Uranus: 84.016846, + Neptune: 164.79132 + }[.] // ("not a planet"|halt_error); + +# seconds per day: 86400 (60*60*24) +.seconds/86400/(.planet|ratios*365.25)|two_decimal diff --git a/jq/space-age/test-space-age.bats b/jq/space-age/test-space-age.bats new file mode 100644 index 0000000..66d00d2 --- /dev/null +++ b/jq/space-age/test-space-age.bats @@ -0,0 +1,140 @@ +#!/usr/bin/env bats +# generated on 2022-11-02T20:59:49Z +load bats-extra +load bats-jq + +@test 'age on Earth' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f space-age.jq << 'END_INPUT' + { + "planet": "Earth", + "seconds": 1000000000 + } +END_INPUT + + assert_success + expected=31.69 + assert_equal "$output" "$expected" +} + +@test 'age on Mercury' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f space-age.jq << 'END_INPUT' + { + "planet": "Mercury", + "seconds": 2134835688 + } +END_INPUT + + assert_success + expected=280.88 + assert_equal "$output" "$expected" +} + +@test 'age on Venus' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f space-age.jq << 'END_INPUT' + { + "planet": "Venus", + "seconds": 189839836 + } +END_INPUT + + assert_success + expected=9.78 + assert_equal "$output" "$expected" +} + +@test 'age on Mars' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f space-age.jq << 'END_INPUT' + { + "planet": "Mars", + "seconds": 2129871239 + } +END_INPUT + + assert_success + expected=35.88 + assert_equal "$output" "$expected" +} + +@test 'age on Jupiter' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f space-age.jq << 'END_INPUT' + { + "planet": "Jupiter", + "seconds": 901876382 + } +END_INPUT + + assert_success + expected=2.41 + assert_equal "$output" "$expected" +} + +@test 'age on Saturn' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f space-age.jq << 'END_INPUT' + { + "planet": "Saturn", + "seconds": 2000000000 + } +END_INPUT + + assert_success + expected=2.15 + assert_equal "$output" "$expected" +} + +@test 'age on Uranus' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f space-age.jq << 'END_INPUT' + { + "planet": "Uranus", + "seconds": 1210123456 + } +END_INPUT + + assert_success + expected=0.46 + assert_equal "$output" "$expected" +} + +@test 'age on Neptune' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f space-age.jq << 'END_INPUT' + { + "planet": "Neptune", + "seconds": 1821023456 + } +END_INPUT + + assert_success + expected=0.35 + assert_equal "$output" "$expected" +} + +@test 'invalid planet causes error' { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + + run jq -r -f space-age.jq << 'END_INPUT' + { + "planet": "Sun", + "seconds": 680804807 + } +END_INPUT + + assert_failure + expected='not a planet' + assert_equal "$output" "$expected" +} +