Compare commits
14 Commits
693945e316
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8296c79f68 | |||
| 5c52e8e34d | |||
| 83538317ee | |||
| b44d1e941f | |||
| 33b8f2e204 | |||
| 888b84bef9 | |||
| 6db87ea8f4 | |||
| 6d6148d66d | |||
| 9dea7a1fcc | |||
| 09bf8bc515 | |||
| f26ebd97f2 | |||
| 0b66c3cdf7 | |||
|
|
8db600e59a | ||
| ffb3f4804b |
22
bash/darts/.exercism/config.json
Normal file
22
bash/darts/.exercism/config.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"glennj"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"guygastineau",
|
||||||
|
"IsaacG"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"darts.sh"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"darts.bats"
|
||||||
|
],
|
||||||
|
"example": [
|
||||||
|
".meta/example.sh"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"blurb": "Write a function that returns the earned points in a single toss of a Darts game.",
|
||||||
|
"source": "Inspired by an exercise created by a professor Della Paolera in Argentina"
|
||||||
|
}
|
||||||
1
bash/darts/.exercism/metadata.json
Normal file
1
bash/darts/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"bash","exercise":"darts","id":"83aa4ddf89f342908c0014fb73a1a3f4","url":"https://exercism.org/tracks/bash/exercises/darts","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||||
105
bash/darts/HELP.md
Normal file
105
bash/darts/HELP.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
Each exercise contains a test file.
|
||||||
|
Run the tests using the `bats` program.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats hello_world.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
`bats` will need to be installed.
|
||||||
|
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
|
||||||
|
|
||||||
|
[tests]: https://exercism.org/docs/tracks/bash/tests
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
[bats-assert]: https://github.com/bats-core/bats-assert
|
||||||
|
|
||||||
|
## 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 next annotation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
[[ $BATS_RUN_SKIPPED == true ]] || skip
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overriding skips
|
||||||
|
|
||||||
|
To run all tests, including the ones with `skip` annotations, you can run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BATS_RUN_SKIPPED=true bats exercise_name.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
It can be convenient to use a wrapper function to save on typing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats() {
|
||||||
|
BATS_RUN_SKIPPED=true command bats *.bats
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run tests with just:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit darts.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
|
||||||
|
- [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.
|
||||||
|
|
||||||
|
Check your code for syntax errors: paste your code into
|
||||||
|
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
|
||||||
|
|
||||||
|
Stack Overflow will be your first stop for bash questions.
|
||||||
|
|
||||||
|
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
|
||||||
|
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
|
||||||
|
* the "Books and Resources" section is particularly useful.
|
||||||
|
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
|
||||||
|
|
||||||
|
## External utilities
|
||||||
|
|
||||||
|
`bash` is a language to write "scripts" -- programs that can call
|
||||||
|
external tools, such as
|
||||||
|
[`sed`](https://www.gnu.org/software/sed/),
|
||||||
|
[`awk`](https://www.gnu.org/software/gawk/),
|
||||||
|
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
|
||||||
|
and even programs written in other programming languages,
|
||||||
|
like [`Python`](https://www.python.org/).
|
||||||
|
This track does not restrict the usage of these utilities, and as long
|
||||||
|
as your solution is portable between systems and does not require
|
||||||
|
installation of third party applications, feel free to use them to solve
|
||||||
|
the exercise.
|
||||||
|
|
||||||
|
For an extra challenge, if you would like to have a better understanding of
|
||||||
|
the language, try to re-implement the solution in pure bash, without using
|
||||||
|
any external tools. There are some types of problems that bash cannot solve,
|
||||||
|
such as floating point arithmetic and manipulating dates: for those, you
|
||||||
|
must call out to an external tool.
|
||||||
49
bash/darts/README.md
Normal file
49
bash/darts/README.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Darts
|
||||||
|
|
||||||
|
Welcome to Darts on Exercism's Bash Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Write a function that returns the earned points in a single toss of a Darts game.
|
||||||
|
|
||||||
|
[Darts][darts] is a game where players throw darts at a [target][darts-target].
|
||||||
|
|
||||||
|
In our particular instance of the game, the target rewards 4 different amounts of points, depending on where the dart lands:
|
||||||
|
|
||||||
|
- If the dart lands outside the target, player earns no points (0 points).
|
||||||
|
- If the dart lands in the outer circle of the target, player earns 1 point.
|
||||||
|
- If the dart lands in the middle circle of the target, player earns 5 points.
|
||||||
|
- If the dart lands in the inner circle of the target, player earns 10 points.
|
||||||
|
|
||||||
|
The outer circle has a radius of 10 units (this is equivalent to the total radius for the entire target), the middle circle a radius of 5 units, and the inner circle a radius of 1.
|
||||||
|
Of course, they are all centered at the same point — that is, the circles are [concentric][] defined by the coordinates (0, 0).
|
||||||
|
|
||||||
|
Write a function that given a point in the target (defined by its [Cartesian coordinates][cartesian-coordinates] `x` and `y`, where `x` and `y` are [real][real-numbers]), returns the correct amount earned by a dart landing at that point.
|
||||||
|
|
||||||
|
[darts]: https://en.wikipedia.org/wiki/Darts
|
||||||
|
[darts-target]: https://en.wikipedia.org/wiki/Darts#/media/File:Darts_in_a_dartboard.jpg
|
||||||
|
[concentric]: https://mathworld.wolfram.com/ConcentricCircles.html
|
||||||
|
[cartesian-coordinates]: https://www.mathsisfun.com/data/cartesian-coordinates.html
|
||||||
|
[real-numbers]: https://www.mathsisfun.com/numbers/real-numbers.html
|
||||||
|
|
||||||
|
## Floating Point Arithmetic
|
||||||
|
|
||||||
|
This particular exercise, since it deals with floating point arithmetic, is
|
||||||
|
natural to rely on external tools (see below). As an extra challenging
|
||||||
|
challenge, find a way to implement this with plain bash.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @glennj
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @guygastineau
|
||||||
|
- @IsaacG
|
||||||
|
|
||||||
|
### Based on
|
||||||
|
|
||||||
|
Inspired by an exercise created by a professor Della Paolera in Argentina
|
||||||
637
bash/darts/bats-extra.bash
Normal file
637
bash/darts/bats-extra.bash
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
# This is the source code for bats-support and bats-assert, concatenated
|
||||||
|
# * https://github.com/bats-core/bats-support
|
||||||
|
# * https://github.com/bats-core/bats-assert
|
||||||
|
#
|
||||||
|
# Comments have been removed to save space. See the git repos for full source code.
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
#
|
||||||
|
# bats-support - Supporting library for Bats test helpers
|
||||||
|
#
|
||||||
|
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
|
||||||
|
#
|
||||||
|
# To the extent possible under law, the author(s) have dedicated all
|
||||||
|
# copyright and related and neighboring rights to this software to the
|
||||||
|
# public domain worldwide. This software is distributed without any
|
||||||
|
# warranty.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the CC0 Public Domain Dedication
|
||||||
|
# along with this software. If not, see
|
||||||
|
# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
(( $# == 0 )) && batslib_err || batslib_err "$@"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_caller() {
|
||||||
|
local -i is_mode_direct=1
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-i|--indirect) is_mode_direct=0; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r func="$1"
|
||||||
|
|
||||||
|
# Check call stack.
|
||||||
|
if (( is_mode_direct )); then
|
||||||
|
[[ $func == "${FUNCNAME[2]}" ]] && return 0
|
||||||
|
else
|
||||||
|
local -i depth
|
||||||
|
for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do
|
||||||
|
[[ $func == "${FUNCNAME[$depth]}" ]] && return 0
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_err() {
|
||||||
|
{ if (( $# > 0 )); then
|
||||||
|
echo "$@"
|
||||||
|
else
|
||||||
|
cat -
|
||||||
|
fi
|
||||||
|
} >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_count_lines() {
|
||||||
|
local -i n_lines=0
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
(( ++n_lines ))
|
||||||
|
done < <(printf '%s' "$1")
|
||||||
|
echo "$n_lines"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_single_line() {
|
||||||
|
for string in "$@"; do
|
||||||
|
(( $(batslib_count_lines "$string") > 1 )) && return 1
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_get_max_single_line_key_width() {
|
||||||
|
local -i max_len=-1
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
local -i key_len="${#1}"
|
||||||
|
batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
echo "$max_len"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single() {
|
||||||
|
local -ir col_width="$1"; shift
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%-*s : %s\n' "$col_width" "$1" "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_multi() {
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )"
|
||||||
|
printf '%s\n' "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single_or_multi() {
|
||||||
|
local -ir width="$1"; shift
|
||||||
|
local -a pairs=( "$@" )
|
||||||
|
|
||||||
|
local -a values=()
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
values+=( "${pairs[$i]}" )
|
||||||
|
done
|
||||||
|
|
||||||
|
if batslib_is_single_line "${values[@]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${pairs[@]}"
|
||||||
|
else
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )"
|
||||||
|
done
|
||||||
|
batslib_print_kv_multi "${pairs[@]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_prefix() {
|
||||||
|
local -r prefix="${1:- }"
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
printf '%s%s\n' "$prefix" "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_mark() {
|
||||||
|
local -r symbol="$1"; shift
|
||||||
|
# Sort line numbers.
|
||||||
|
set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" )
|
||||||
|
|
||||||
|
local line
|
||||||
|
local -i idx=0
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
if (( ${1:--1} == idx )); then
|
||||||
|
printf '%s\n' "${symbol}${line:${#symbol}}"
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
printf '%s\n' "$line"
|
||||||
|
fi
|
||||||
|
(( ++idx ))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_decorate() {
|
||||||
|
echo
|
||||||
|
echo "-- $1 --"
|
||||||
|
cat -
|
||||||
|
echo '--'
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
assert() {
|
||||||
|
if ! "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal() {
|
||||||
|
if [[ $1 != "$2" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$2" \
|
||||||
|
'actual' "$1" \
|
||||||
|
| batslib_decorate 'values do not equal' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_failure() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
(( $# > 0 )) && local -r expected="$1"
|
||||||
|
if (( status == 0 )); then
|
||||||
|
batslib_print_kv_single_or_multi 6 'output' "$output" \
|
||||||
|
| batslib_decorate 'command succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
elif (( $# > 0 )) && (( status != expected )); then
|
||||||
|
{ local -ir width=8
|
||||||
|
batslib_print_kv_single "$width" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" \
|
||||||
|
'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed as expected, but status differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r expected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if ! [[ ${lines[$idx]} =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression does not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single 8 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} =~ $expected ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'regexp' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line matches regular expression' \
|
||||||
|
| fail
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == *"$expected"* ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'substring' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line contains substring' \
|
||||||
|
| fail
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == "$expected" ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'line' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'output does not contain line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_nonempty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_nonempty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local expected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
expected="$(cat -)"
|
||||||
|
else
|
||||||
|
expected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_nonempty )); then
|
||||||
|
if [ -z "$output" ]; then
|
||||||
|
echo 'expected non-empty output, but output was empty' \
|
||||||
|
| batslib_decorate 'no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
elif ! [[ $output =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression does not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$output" \
|
||||||
|
| batslib_decorate 'output differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_success() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
if (( status != 0 )); then
|
||||||
|
{ local -ir width=6
|
||||||
|
batslib_print_kv_single "$width" 'status' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" 'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute() {
|
||||||
|
if "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r unexpected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression should not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single 5 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Line contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
{ local -ar single=( 'regexp' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should match the regular expression' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
{ local -ar single=( 'substring' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should contain substring' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
{ local -ar single=( 'line' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'line should not be in output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_empty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_empty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local unexpected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
unexpected="$(cat -)"
|
||||||
|
else
|
||||||
|
unexpected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_empty )); then
|
||||||
|
if [ -n "$output" ]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output non-empty, but expected no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ $output =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression should not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output equals, but it was expected to differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
125
bash/darts/darts.bats
Normal file
125
bash/darts/darts.bats
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
load bats-extra
|
||||||
|
|
||||||
|
# local version: 2.2.0.1
|
||||||
|
|
||||||
|
@test "Missed target" {
|
||||||
|
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh -9 9
|
||||||
|
assert_success
|
||||||
|
assert_output "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "On the outer circle" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh 0 10
|
||||||
|
assert_success
|
||||||
|
assert_output "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "On the middle circle" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh -5 0
|
||||||
|
assert_success
|
||||||
|
assert_output "5"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "On the inner circle" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh 0 -1
|
||||||
|
assert_success
|
||||||
|
assert_output "10"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Exactly on centre" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh 0 0
|
||||||
|
assert_success
|
||||||
|
assert_output "10"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Near the centre" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh -0.1 -0.1
|
||||||
|
assert_success
|
||||||
|
assert_output "10"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Just within the inner circle" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh 0.7 0.7
|
||||||
|
assert_success
|
||||||
|
assert_output "10"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Just outside the inner circle" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh 0.8 -0.8
|
||||||
|
assert_success
|
||||||
|
assert_output "5"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Just within the middle circle" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh -3.5 3.5
|
||||||
|
assert_success
|
||||||
|
assert_output "5"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Just outside the middle circle" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh -3.6 -3.6
|
||||||
|
assert_success
|
||||||
|
assert_output "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Just within the outer circle" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh -7.0 7.0
|
||||||
|
assert_success
|
||||||
|
assert_output "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Just outside the outer circle" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh 7.1 -7.1
|
||||||
|
assert_success
|
||||||
|
assert_output "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Asymmetric position between the inner and middle circles" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh 0.5 -4
|
||||||
|
assert_success
|
||||||
|
assert_output "5"
|
||||||
|
}
|
||||||
|
|
||||||
|
# bash-specific test: Input validation
|
||||||
|
|
||||||
|
@test "invalid args: no args" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh
|
||||||
|
assert_failure
|
||||||
|
assert_output # there is _some_ output
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "invalid args: only 1 arg" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh 10
|
||||||
|
assert_failure
|
||||||
|
assert_output # there is _some_ output
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "invalid args: first arg non-numeric" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh foo 10
|
||||||
|
assert_failure
|
||||||
|
assert_output # there is _some_ output
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "invalid args: second arg non-numeric" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash darts.sh 10 bar
|
||||||
|
assert_failure
|
||||||
|
assert_output # there is _some_ output
|
||||||
|
}
|
||||||
26
bash/darts/darts.sh
Normal file
26
bash/darts/darts.sh
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
x=$1
|
||||||
|
y=$2
|
||||||
|
distance=$(echo "sqrt($x^2 + $y^2)" | bc -l)
|
||||||
|
|
||||||
|
if [[ $# -ne 2 ]]; then
|
||||||
|
echo "# there is _some_ output"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if ! [[ $x =~ ^[+-]?[0-9]+(\.[0-9]+)?$ ]] || ! [[ $y =~ ^[+-]?[0-9]+(\.[0-9]+)?$ ]]; then
|
||||||
|
echo "# there is _some_ output"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( $(echo "$distance > 10" | bc -l) )); then
|
||||||
|
echo "0"
|
||||||
|
elif (( $(echo "$distance > 5" | bc -l) )); then
|
||||||
|
echo "1"
|
||||||
|
elif (( $(echo "$distance > 1" | bc -l) )); then
|
||||||
|
echo "5"
|
||||||
|
else
|
||||||
|
echo "10"
|
||||||
|
fi
|
||||||
23
bash/dnd-character/.exercism/config.json
Normal file
23
bash/dnd-character/.exercism/config.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"glennj"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"guygastineau",
|
||||||
|
"IsaacG"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"dnd_character.sh"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"dnd_character.bats"
|
||||||
|
],
|
||||||
|
"example": [
|
||||||
|
".meta/example.sh"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"blurb": "Randomly generate Dungeons & Dragons characters.",
|
||||||
|
"source": "Simon Shine, Erik Schierboom",
|
||||||
|
"source_url": "https://github.com/exercism/problem-specifications/issues/616#issuecomment-437358945"
|
||||||
|
}
|
||||||
1
bash/dnd-character/.exercism/metadata.json
Normal file
1
bash/dnd-character/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"bash","exercise":"dnd-character","id":"c00402df3e994390a5f6cfba07ad644f","url":"https://exercism.org/tracks/bash/exercises/dnd-character","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||||
105
bash/dnd-character/HELP.md
Normal file
105
bash/dnd-character/HELP.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
Each exercise contains a test file.
|
||||||
|
Run the tests using the `bats` program.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats hello_world.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
`bats` will need to be installed.
|
||||||
|
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
|
||||||
|
|
||||||
|
[tests]: https://exercism.org/docs/tracks/bash/tests
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
[bats-assert]: https://github.com/bats-core/bats-assert
|
||||||
|
|
||||||
|
## 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 next annotation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
[[ $BATS_RUN_SKIPPED == true ]] || skip
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overriding skips
|
||||||
|
|
||||||
|
To run all tests, including the ones with `skip` annotations, you can run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BATS_RUN_SKIPPED=true bats exercise_name.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
It can be convenient to use a wrapper function to save on typing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats() {
|
||||||
|
BATS_RUN_SKIPPED=true command bats *.bats
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run tests with just:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit dnd_character.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
|
||||||
|
- [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.
|
||||||
|
|
||||||
|
Check your code for syntax errors: paste your code into
|
||||||
|
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
|
||||||
|
|
||||||
|
Stack Overflow will be your first stop for bash questions.
|
||||||
|
|
||||||
|
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
|
||||||
|
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
|
||||||
|
* the "Books and Resources" section is particularly useful.
|
||||||
|
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
|
||||||
|
|
||||||
|
## External utilities
|
||||||
|
|
||||||
|
`bash` is a language to write "scripts" -- programs that can call
|
||||||
|
external tools, such as
|
||||||
|
[`sed`](https://www.gnu.org/software/sed/),
|
||||||
|
[`awk`](https://www.gnu.org/software/gawk/),
|
||||||
|
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
|
||||||
|
and even programs written in other programming languages,
|
||||||
|
like [`Python`](https://www.python.org/).
|
||||||
|
This track does not restrict the usage of these utilities, and as long
|
||||||
|
as your solution is portable between systems and does not require
|
||||||
|
installation of third party applications, feel free to use them to solve
|
||||||
|
the exercise.
|
||||||
|
|
||||||
|
For an extra challenge, if you would like to have a better understanding of
|
||||||
|
the language, try to re-implement the solution in pure bash, without using
|
||||||
|
any external tools. There are some types of problems that bash cannot solve,
|
||||||
|
such as floating point arithmetic and manipulating dates: for those, you
|
||||||
|
must call out to an external tool.
|
||||||
51
bash/dnd-character/README.md
Normal file
51
bash/dnd-character/README.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# D&D Character
|
||||||
|
|
||||||
|
Welcome to D&D Character on Exercism's Bash Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
For a game of [Dungeons & Dragons][dnd], each player starts by generating a character they can play with.
|
||||||
|
This character has, among other things, six abilities; strength, dexterity, constitution, intelligence, wisdom and charisma.
|
||||||
|
These six abilities have scores that are determined randomly.
|
||||||
|
You do this by rolling four 6-sided dice and record the sum of the largest three dice.
|
||||||
|
You do this six times, once for each ability.
|
||||||
|
|
||||||
|
Your character's initial hitpoints are 10 + your character's constitution modifier.
|
||||||
|
You find your character's constitution modifier by subtracting 10 from your character's constitution, divide by 2 and round down.
|
||||||
|
|
||||||
|
Write a random character generator that follows the rules above.
|
||||||
|
|
||||||
|
For example, the six throws of four dice may look like:
|
||||||
|
|
||||||
|
- 5, 3, 1, 6: You discard the 1 and sum 5 + 3 + 6 = 14, which you assign to strength.
|
||||||
|
- 3, 2, 5, 3: You discard the 2 and sum 3 + 5 + 3 = 11, which you assign to dexterity.
|
||||||
|
- 1, 1, 1, 1: You discard the 1 and sum 1 + 1 + 1 = 3, which you assign to constitution.
|
||||||
|
- 2, 1, 6, 6: You discard the 1 and sum 2 + 6 + 6 = 14, which you assign to intelligence.
|
||||||
|
- 3, 5, 3, 4: You discard the 3 and sum 5 + 3 + 4 = 12, which you assign to wisdom.
|
||||||
|
- 6, 6, 6, 6: You discard the 6 and sum 6 + 6 + 6 = 18, which you assign to charisma.
|
||||||
|
|
||||||
|
Because constitution is 3, the constitution modifier is -4 and the hitpoints are 6.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Most programming languages feature (pseudo-)random generators, but few programming languages are designed to roll dice.
|
||||||
|
One such language is [Troll][troll].
|
||||||
|
|
||||||
|
[dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons
|
||||||
|
[troll]: https://di.ku.dk/Ansatte/?pure=da%2Fpublications%2Ftroll-a-language-for-specifying-dicerolls(84a45ff0-068b-11df-825d-000ea68e967b)%2Fexport.html
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @glennj
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @guygastineau
|
||||||
|
- @IsaacG
|
||||||
|
|
||||||
|
### Based on
|
||||||
|
|
||||||
|
Simon Shine, Erik Schierboom - https://github.com/exercism/problem-specifications/issues/616#issuecomment-437358945
|
||||||
637
bash/dnd-character/bats-extra.bash
Normal file
637
bash/dnd-character/bats-extra.bash
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
# This is the source code for bats-support and bats-assert, concatenated
|
||||||
|
# * https://github.com/bats-core/bats-support
|
||||||
|
# * https://github.com/bats-core/bats-assert
|
||||||
|
#
|
||||||
|
# Comments have been removed to save space. See the git repos for full source code.
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
#
|
||||||
|
# bats-support - Supporting library for Bats test helpers
|
||||||
|
#
|
||||||
|
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
|
||||||
|
#
|
||||||
|
# To the extent possible under law, the author(s) have dedicated all
|
||||||
|
# copyright and related and neighboring rights to this software to the
|
||||||
|
# public domain worldwide. This software is distributed without any
|
||||||
|
# warranty.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the CC0 Public Domain Dedication
|
||||||
|
# along with this software. If not, see
|
||||||
|
# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
(( $# == 0 )) && batslib_err || batslib_err "$@"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_caller() {
|
||||||
|
local -i is_mode_direct=1
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-i|--indirect) is_mode_direct=0; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r func="$1"
|
||||||
|
|
||||||
|
# Check call stack.
|
||||||
|
if (( is_mode_direct )); then
|
||||||
|
[[ $func == "${FUNCNAME[2]}" ]] && return 0
|
||||||
|
else
|
||||||
|
local -i depth
|
||||||
|
for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do
|
||||||
|
[[ $func == "${FUNCNAME[$depth]}" ]] && return 0
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_err() {
|
||||||
|
{ if (( $# > 0 )); then
|
||||||
|
echo "$@"
|
||||||
|
else
|
||||||
|
cat -
|
||||||
|
fi
|
||||||
|
} >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_count_lines() {
|
||||||
|
local -i n_lines=0
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
(( ++n_lines ))
|
||||||
|
done < <(printf '%s' "$1")
|
||||||
|
echo "$n_lines"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_single_line() {
|
||||||
|
for string in "$@"; do
|
||||||
|
(( $(batslib_count_lines "$string") > 1 )) && return 1
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_get_max_single_line_key_width() {
|
||||||
|
local -i max_len=-1
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
local -i key_len="${#1}"
|
||||||
|
batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
echo "$max_len"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single() {
|
||||||
|
local -ir col_width="$1"; shift
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%-*s : %s\n' "$col_width" "$1" "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_multi() {
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )"
|
||||||
|
printf '%s\n' "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single_or_multi() {
|
||||||
|
local -ir width="$1"; shift
|
||||||
|
local -a pairs=( "$@" )
|
||||||
|
|
||||||
|
local -a values=()
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
values+=( "${pairs[$i]}" )
|
||||||
|
done
|
||||||
|
|
||||||
|
if batslib_is_single_line "${values[@]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${pairs[@]}"
|
||||||
|
else
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )"
|
||||||
|
done
|
||||||
|
batslib_print_kv_multi "${pairs[@]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_prefix() {
|
||||||
|
local -r prefix="${1:- }"
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
printf '%s%s\n' "$prefix" "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_mark() {
|
||||||
|
local -r symbol="$1"; shift
|
||||||
|
# Sort line numbers.
|
||||||
|
set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" )
|
||||||
|
|
||||||
|
local line
|
||||||
|
local -i idx=0
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
if (( ${1:--1} == idx )); then
|
||||||
|
printf '%s\n' "${symbol}${line:${#symbol}}"
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
printf '%s\n' "$line"
|
||||||
|
fi
|
||||||
|
(( ++idx ))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_decorate() {
|
||||||
|
echo
|
||||||
|
echo "-- $1 --"
|
||||||
|
cat -
|
||||||
|
echo '--'
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
assert() {
|
||||||
|
if ! "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal() {
|
||||||
|
if [[ $1 != "$2" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$2" \
|
||||||
|
'actual' "$1" \
|
||||||
|
| batslib_decorate 'values do not equal' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_failure() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
(( $# > 0 )) && local -r expected="$1"
|
||||||
|
if (( status == 0 )); then
|
||||||
|
batslib_print_kv_single_or_multi 6 'output' "$output" \
|
||||||
|
| batslib_decorate 'command succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
elif (( $# > 0 )) && (( status != expected )); then
|
||||||
|
{ local -ir width=8
|
||||||
|
batslib_print_kv_single "$width" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" \
|
||||||
|
'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed as expected, but status differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r expected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if ! [[ ${lines[$idx]} =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression does not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single 8 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} =~ $expected ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'regexp' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line matches regular expression' \
|
||||||
|
| fail
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == *"$expected"* ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'substring' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line contains substring' \
|
||||||
|
| fail
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == "$expected" ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'line' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'output does not contain line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_nonempty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_nonempty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local expected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
expected="$(cat -)"
|
||||||
|
else
|
||||||
|
expected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_nonempty )); then
|
||||||
|
if [ -z "$output" ]; then
|
||||||
|
echo 'expected non-empty output, but output was empty' \
|
||||||
|
| batslib_decorate 'no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
elif ! [[ $output =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression does not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$output" \
|
||||||
|
| batslib_decorate 'output differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_success() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
if (( status != 0 )); then
|
||||||
|
{ local -ir width=6
|
||||||
|
batslib_print_kv_single "$width" 'status' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" 'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute() {
|
||||||
|
if "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r unexpected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression should not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single 5 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Line contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
{ local -ar single=( 'regexp' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should match the regular expression' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
{ local -ar single=( 'substring' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should contain substring' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
{ local -ar single=( 'line' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'line should not be in output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_empty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_empty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local unexpected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
unexpected="$(cat -)"
|
||||||
|
else
|
||||||
|
unexpected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_empty )); then
|
||||||
|
if [ -n "$output" ]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output non-empty, but expected no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ $output =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression should not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output equals, but it was expected to differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
169
bash/dnd-character/dnd_character.bats
Normal file
169
bash/dnd-character/dnd_character.bats
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
load bats-extra
|
||||||
|
|
||||||
|
# local version: 1.1.0.0
|
||||||
|
|
||||||
|
# usage: dnd_character.sh modifier n
|
||||||
|
# -> output expected modifier
|
||||||
|
|
||||||
|
# usage: dnd_character.sh generate
|
||||||
|
# -> output each characteristic and ability value, one per line
|
||||||
|
|
||||||
|
|
||||||
|
# ability modifier
|
||||||
|
|
||||||
|
@test "ability modifier for score 3 is -4" {
|
||||||
|
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh modifier 3
|
||||||
|
assert_success
|
||||||
|
assert_output "-4"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ability modifier for score 4 is -3" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh modifier 4
|
||||||
|
assert_success
|
||||||
|
assert_output "-3"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ability modifier for score 5 is -3" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh modifier 5
|
||||||
|
assert_success
|
||||||
|
assert_output "-3"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ability modifier for score 6 is -2" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh modifier 6
|
||||||
|
assert_success
|
||||||
|
assert_output "-2"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ability modifier for score 7 is -2" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh modifier 7
|
||||||
|
assert_success
|
||||||
|
assert_output "-2"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ability modifier for score 8 is -1" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh modifier 8
|
||||||
|
assert_success
|
||||||
|
assert_output "-1"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ability modifier for score 9 is -1" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh modifier 9
|
||||||
|
assert_success
|
||||||
|
assert_output "-1"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ability modifier for score 10 is 0" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh modifier 10
|
||||||
|
assert_success
|
||||||
|
assert_output "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ability modifier for score 11 is 0" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh modifier 11
|
||||||
|
assert_success
|
||||||
|
assert_output "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ability modifier for score 12 is +1" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh modifier 12
|
||||||
|
assert_success
|
||||||
|
assert_output "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ability modifier for score 13 is +1" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh modifier 13
|
||||||
|
assert_success
|
||||||
|
assert_output "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ability modifier for score 14 is +2" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh modifier 14
|
||||||
|
assert_success
|
||||||
|
assert_output "2"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ability modifier for score 15 is +2" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh modifier 15
|
||||||
|
assert_success
|
||||||
|
assert_output "2"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ability modifier for score 16 is +3" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh modifier 16
|
||||||
|
assert_success
|
||||||
|
assert_output "3"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ability modifier for score 17 is +3" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh modifier 17
|
||||||
|
assert_success
|
||||||
|
assert_output "3"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ability modifier for score 18 is +4" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh modifier 18
|
||||||
|
assert_success
|
||||||
|
assert_output "4"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# generate a character, validate expected output
|
||||||
|
|
||||||
|
@test "generate a character" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash dnd_character.sh generate
|
||||||
|
assert_success
|
||||||
|
# these don't have to appear in any particular order
|
||||||
|
assert_line --regexp '^strength [[:digit:]]{1,2}$'
|
||||||
|
assert_line --regexp '^dexterity [[:digit:]]{1,2}$'
|
||||||
|
assert_line --regexp '^constitution [[:digit:]]{1,2}$'
|
||||||
|
assert_line --regexp '^intelligence [[:digit:]]{1,2}$'
|
||||||
|
assert_line --regexp '^wisdom [[:digit:]]{1,2}$'
|
||||||
|
assert_line --regexp '^charisma [[:digit:]]{1,2}$'
|
||||||
|
assert_line --regexp '^hitpoints [[:digit:]]{1,2}$'
|
||||||
|
# no other output: `run` populates the `lines` array
|
||||||
|
assert_equal ${#lines[@]} 7
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Usage: between $val $low $high
|
||||||
|
# Value is between low (inclusive) and high (inclusive).
|
||||||
|
between() {
|
||||||
|
(( $2 <= $1 && $1 <= $3 ))
|
||||||
|
}
|
||||||
|
|
||||||
|
# random ability is within range
|
||||||
|
@test "validate ability range and hitpoint value" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
for i in {1..50}; do
|
||||||
|
while read c v; do
|
||||||
|
if [[ $c == "hitpoints" ]]; then
|
||||||
|
hits=$v
|
||||||
|
else
|
||||||
|
assert between "$v" 3 18
|
||||||
|
[[ $c == "constitution" ]] && const=$v
|
||||||
|
fi
|
||||||
|
done < <(bash dnd_character.sh generate)
|
||||||
|
|
||||||
|
const_mod=$(bash dnd_character.sh modifier "$const")
|
||||||
|
assert_equal $((10 + const_mod)) "$hits"
|
||||||
|
done
|
||||||
|
}
|
||||||
20
bash/dnd-character/dnd_character.sh
Normal file
20
bash/dnd-character/dnd_character.sh
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
mod() {
|
||||||
|
echo $(bc <<< "($1/2)-5")
|
||||||
|
}
|
||||||
|
|
||||||
|
order=$1
|
||||||
|
|
||||||
|
if [[ $order == "modifier" ]]; then
|
||||||
|
mod "$2"
|
||||||
|
else
|
||||||
|
echo "strength $(($RANDOM%15+3))"
|
||||||
|
echo "dexterity $(($RANDOM%15+3))"
|
||||||
|
constitution=$(($RANDOM%15+3))
|
||||||
|
echo "constitution ${constitution}"
|
||||||
|
echo "intelligence $(($RANDOM%15+3))"
|
||||||
|
echo "wisdom $(($RANDOM%15+3))"
|
||||||
|
echo "charisma $(($RANDOM%15+3))"
|
||||||
|
echo "hitpoints $(($(mod "$constitution")+10))"
|
||||||
|
fi
|
||||||
25
bash/grep/.exercism/config.json
Normal file
25
bash/grep/.exercism/config.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"glennj"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"bkhl",
|
||||||
|
"guygastineau",
|
||||||
|
"IsaacG",
|
||||||
|
"kotp"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"grep.sh"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"grep.bats"
|
||||||
|
],
|
||||||
|
"example": [
|
||||||
|
".meta/example.sh"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"blurb": "Search a file for lines matching a regular expression pattern. Return the line number and contents of each matching line.",
|
||||||
|
"source": "Conversation with Nate Foster.",
|
||||||
|
"source_url": "https://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf"
|
||||||
|
}
|
||||||
1
bash/grep/.exercism/metadata.json
Normal file
1
bash/grep/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"bash","exercise":"grep","id":"f17326cb951d4357a2394a24d217a5f3","url":"https://exercism.org/tracks/bash/exercises/grep","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||||
105
bash/grep/HELP.md
Normal file
105
bash/grep/HELP.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
Each exercise contains a test file.
|
||||||
|
Run the tests using the `bats` program.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats hello_world.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
`bats` will need to be installed.
|
||||||
|
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
|
||||||
|
|
||||||
|
[tests]: https://exercism.org/docs/tracks/bash/tests
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
[bats-assert]: https://github.com/bats-core/bats-assert
|
||||||
|
|
||||||
|
## 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 next annotation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
[[ $BATS_RUN_SKIPPED == true ]] || skip
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overriding skips
|
||||||
|
|
||||||
|
To run all tests, including the ones with `skip` annotations, you can run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BATS_RUN_SKIPPED=true bats exercise_name.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
It can be convenient to use a wrapper function to save on typing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats() {
|
||||||
|
BATS_RUN_SKIPPED=true command bats *.bats
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run tests with just:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit grep.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
|
||||||
|
- [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.
|
||||||
|
|
||||||
|
Check your code for syntax errors: paste your code into
|
||||||
|
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
|
||||||
|
|
||||||
|
Stack Overflow will be your first stop for bash questions.
|
||||||
|
|
||||||
|
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
|
||||||
|
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
|
||||||
|
* the "Books and Resources" section is particularly useful.
|
||||||
|
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
|
||||||
|
|
||||||
|
## External utilities
|
||||||
|
|
||||||
|
`bash` is a language to write "scripts" -- programs that can call
|
||||||
|
external tools, such as
|
||||||
|
[`sed`](https://www.gnu.org/software/sed/),
|
||||||
|
[`awk`](https://www.gnu.org/software/gawk/),
|
||||||
|
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
|
||||||
|
and even programs written in other programming languages,
|
||||||
|
like [`Python`](https://www.python.org/).
|
||||||
|
This track does not restrict the usage of these utilities, and as long
|
||||||
|
as your solution is portable between systems and does not require
|
||||||
|
installation of third party applications, feel free to use them to solve
|
||||||
|
the exercise.
|
||||||
|
|
||||||
|
For an extra challenge, if you would like to have a better understanding of
|
||||||
|
the language, try to re-implement the solution in pure bash, without using
|
||||||
|
any external tools. There are some types of problems that bash cannot solve,
|
||||||
|
such as floating point arithmetic and manipulating dates: for those, you
|
||||||
|
must call out to an external tool.
|
||||||
65
bash/grep/README.md
Normal file
65
bash/grep/README.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Grep
|
||||||
|
|
||||||
|
Welcome to Grep on Exercism's Bash Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Search files for lines matching a search string and return all matching lines.
|
||||||
|
|
||||||
|
The Unix [`grep`][grep] command searches files for lines that match a regular expression.
|
||||||
|
Your task is to implement a simplified `grep` command, which supports searching for fixed strings.
|
||||||
|
|
||||||
|
The `grep` command takes three arguments:
|
||||||
|
|
||||||
|
1. The string to search for.
|
||||||
|
2. Zero or more flags for customizing the command's behavior.
|
||||||
|
3. One or more files to search in.
|
||||||
|
|
||||||
|
It then reads the contents of the specified files (in the order specified), finds the lines that contain the search string, and finally returns those lines in the order in which they were found.
|
||||||
|
When searching in multiple files, each matching line is prepended by the file name and a colon (':').
|
||||||
|
|
||||||
|
## Flags
|
||||||
|
|
||||||
|
The `grep` command supports the following flags:
|
||||||
|
|
||||||
|
- `-n` Prepend the line number and a colon (':') to each line in the output, placing the number after the filename (if present).
|
||||||
|
- `-l` Output only the names of the files that contain at least one matching line.
|
||||||
|
- `-i` Match using a case-insensitive comparison.
|
||||||
|
- `-v` Invert the program -- collect all lines that fail to match.
|
||||||
|
- `-x` Search only for lines where the search string matches the entire line.
|
||||||
|
|
||||||
|
[grep]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html
|
||||||
|
|
||||||
|
## To `grep` or not to `grep`, that is the question
|
||||||
|
|
||||||
|
Although this exercise can be trivially solved by simply passing the
|
||||||
|
arguments to `grep`, implement this exercise using bash only. The aim
|
||||||
|
of this exercism track is to learn how to use bash builtin commands to solve
|
||||||
|
problems.
|
||||||
|
|
||||||
|
To solve this exercise, you'll need to:
|
||||||
|
|
||||||
|
* parse command line arguments: [`getopts`](https://stackoverflow.com/tags/getopts/info) is useful for this.
|
||||||
|
* iterate over the lines of a file: this is [bash FAQ #1](https://mywiki.wooledge.org/BashFAQ/001)
|
||||||
|
* use regular expression matching: bash can do this using the `=~` operator
|
||||||
|
within [`[[ ... ]]`](https://www.gnu.org/software/bash/manual/bash.html#index-_005b_005b)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @glennj
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @bkhl
|
||||||
|
- @guygastineau
|
||||||
|
- @IsaacG
|
||||||
|
- @kotp
|
||||||
|
|
||||||
|
### Based on
|
||||||
|
|
||||||
|
Conversation with Nate Foster. - https://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf
|
||||||
637
bash/grep/bats-extra.bash
Normal file
637
bash/grep/bats-extra.bash
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
# This is the source code for bats-support and bats-assert, concatenated
|
||||||
|
# * https://github.com/bats-core/bats-support
|
||||||
|
# * https://github.com/bats-core/bats-assert
|
||||||
|
#
|
||||||
|
# Comments have been removed to save space. See the git repos for full source code.
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
#
|
||||||
|
# bats-support - Supporting library for Bats test helpers
|
||||||
|
#
|
||||||
|
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
|
||||||
|
#
|
||||||
|
# To the extent possible under law, the author(s) have dedicated all
|
||||||
|
# copyright and related and neighboring rights to this software to the
|
||||||
|
# public domain worldwide. This software is distributed without any
|
||||||
|
# warranty.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the CC0 Public Domain Dedication
|
||||||
|
# along with this software. If not, see
|
||||||
|
# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
(( $# == 0 )) && batslib_err || batslib_err "$@"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_caller() {
|
||||||
|
local -i is_mode_direct=1
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-i|--indirect) is_mode_direct=0; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r func="$1"
|
||||||
|
|
||||||
|
# Check call stack.
|
||||||
|
if (( is_mode_direct )); then
|
||||||
|
[[ $func == "${FUNCNAME[2]}" ]] && return 0
|
||||||
|
else
|
||||||
|
local -i depth
|
||||||
|
for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do
|
||||||
|
[[ $func == "${FUNCNAME[$depth]}" ]] && return 0
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_err() {
|
||||||
|
{ if (( $# > 0 )); then
|
||||||
|
echo "$@"
|
||||||
|
else
|
||||||
|
cat -
|
||||||
|
fi
|
||||||
|
} >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_count_lines() {
|
||||||
|
local -i n_lines=0
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
(( ++n_lines ))
|
||||||
|
done < <(printf '%s' "$1")
|
||||||
|
echo "$n_lines"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_single_line() {
|
||||||
|
for string in "$@"; do
|
||||||
|
(( $(batslib_count_lines "$string") > 1 )) && return 1
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_get_max_single_line_key_width() {
|
||||||
|
local -i max_len=-1
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
local -i key_len="${#1}"
|
||||||
|
batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
echo "$max_len"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single() {
|
||||||
|
local -ir col_width="$1"; shift
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%-*s : %s\n' "$col_width" "$1" "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_multi() {
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )"
|
||||||
|
printf '%s\n' "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single_or_multi() {
|
||||||
|
local -ir width="$1"; shift
|
||||||
|
local -a pairs=( "$@" )
|
||||||
|
|
||||||
|
local -a values=()
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
values+=( "${pairs[$i]}" )
|
||||||
|
done
|
||||||
|
|
||||||
|
if batslib_is_single_line "${values[@]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${pairs[@]}"
|
||||||
|
else
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )"
|
||||||
|
done
|
||||||
|
batslib_print_kv_multi "${pairs[@]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_prefix() {
|
||||||
|
local -r prefix="${1:- }"
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
printf '%s%s\n' "$prefix" "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_mark() {
|
||||||
|
local -r symbol="$1"; shift
|
||||||
|
# Sort line numbers.
|
||||||
|
set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" )
|
||||||
|
|
||||||
|
local line
|
||||||
|
local -i idx=0
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
if (( ${1:--1} == idx )); then
|
||||||
|
printf '%s\n' "${symbol}${line:${#symbol}}"
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
printf '%s\n' "$line"
|
||||||
|
fi
|
||||||
|
(( ++idx ))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_decorate() {
|
||||||
|
echo
|
||||||
|
echo "-- $1 --"
|
||||||
|
cat -
|
||||||
|
echo '--'
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
assert() {
|
||||||
|
if ! "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal() {
|
||||||
|
if [[ $1 != "$2" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$2" \
|
||||||
|
'actual' "$1" \
|
||||||
|
| batslib_decorate 'values do not equal' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_failure() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
(( $# > 0 )) && local -r expected="$1"
|
||||||
|
if (( status == 0 )); then
|
||||||
|
batslib_print_kv_single_or_multi 6 'output' "$output" \
|
||||||
|
| batslib_decorate 'command succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
elif (( $# > 0 )) && (( status != expected )); then
|
||||||
|
{ local -ir width=8
|
||||||
|
batslib_print_kv_single "$width" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" \
|
||||||
|
'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed as expected, but status differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r expected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if ! [[ ${lines[$idx]} =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression does not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single 8 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} =~ $expected ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'regexp' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line matches regular expression' \
|
||||||
|
| fail
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == *"$expected"* ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'substring' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line contains substring' \
|
||||||
|
| fail
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == "$expected" ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'line' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'output does not contain line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_nonempty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_nonempty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local expected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
expected="$(cat -)"
|
||||||
|
else
|
||||||
|
expected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_nonempty )); then
|
||||||
|
if [ -z "$output" ]; then
|
||||||
|
echo 'expected non-empty output, but output was empty' \
|
||||||
|
| batslib_decorate 'no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
elif ! [[ $output =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression does not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$output" \
|
||||||
|
| batslib_decorate 'output differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_success() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
if (( status != 0 )); then
|
||||||
|
{ local -ir width=6
|
||||||
|
batslib_print_kv_single "$width" 'status' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" 'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute() {
|
||||||
|
if "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r unexpected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression should not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single 5 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Line contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
{ local -ar single=( 'regexp' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should match the regular expression' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
{ local -ar single=( 'substring' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should contain substring' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
{ local -ar single=( 'line' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'line should not be in output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_empty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_empty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local unexpected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
unexpected="$(cat -)"
|
||||||
|
else
|
||||||
|
unexpected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_empty )); then
|
||||||
|
if [ -n "$output" ]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output non-empty, but expected no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ $output =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression should not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output equals, but it was expected to differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
377
bash/grep/grep.bats
Normal file
377
bash/grep/grep.bats
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
load bats-extra
|
||||||
|
|
||||||
|
# local version: 1.2.0.0
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
cat > iliad.txt << END_ILIAD
|
||||||
|
Achilles sing, O Goddess! Peleus' son;
|
||||||
|
His wrath pernicious, who ten thousand woes
|
||||||
|
Caused to Achaia's host, sent many a soul
|
||||||
|
Illustrious into Ades premature,
|
||||||
|
And Heroes gave (so stood the will of Jove)
|
||||||
|
To dogs and to all ravening fowls a prey,
|
||||||
|
When fierce dispute had separated once
|
||||||
|
The noble Chief Achilles from the son
|
||||||
|
Of Atreus, Agamemnon, King of men.
|
||||||
|
END_ILIAD
|
||||||
|
cat > midsummer-night.txt << END_MIDSUMMER
|
||||||
|
I do entreat your grace to pardon me.
|
||||||
|
I know not by what power I am made bold,
|
||||||
|
Nor how it may concern my modesty,
|
||||||
|
In such a presence here to plead my thoughts;
|
||||||
|
But I beseech your grace that I may know
|
||||||
|
The worst that may befall me in this case,
|
||||||
|
If I refuse to wed Demetrius.
|
||||||
|
END_MIDSUMMER
|
||||||
|
cat > paradise-lost.txt << END_PARADISE
|
||||||
|
Of Mans First Disobedience, and the Fruit
|
||||||
|
Of that Forbidden Tree, whose mortal tast
|
||||||
|
Brought Death into the World, and all our woe,
|
||||||
|
With loss of Eden, till one greater Man
|
||||||
|
Restore us, and regain the blissful Seat,
|
||||||
|
Sing Heav'nly Muse, that on the secret top
|
||||||
|
Of Oreb, or of Sinai, didst inspire
|
||||||
|
That Shepherd, who first taught the chosen Seed
|
||||||
|
END_PARADISE
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown() {
|
||||||
|
rm iliad.txt midsummer-night.txt paradise-lost.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test grepping a single file
|
||||||
|
|
||||||
|
@test "One file, one match, no flags" {
|
||||||
|
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="Of Atreus, Agamemnon, King of men."
|
||||||
|
pattern="Agamemnon"
|
||||||
|
flags=()
|
||||||
|
files=(iliad.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "One file, one match, print line numbers flag" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="2:Of that Forbidden Tree, whose mortal tast"
|
||||||
|
pattern="Forbidden"
|
||||||
|
flags=(-n)
|
||||||
|
files=(paradise-lost.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "One file, one match, case-insensitive flag" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="Of that Forbidden Tree, whose mortal tast"
|
||||||
|
pattern="FORBIDDEN"
|
||||||
|
flags=(-i)
|
||||||
|
files=(paradise-lost.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "One file, one match, print file names flag" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="paradise-lost.txt"
|
||||||
|
pattern="Forbidden"
|
||||||
|
flags=(-l)
|
||||||
|
files=(paradise-lost.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "One file, one match, match entire lines flag" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="With loss of Eden, till one greater Man"
|
||||||
|
pattern="With loss of Eden, till one greater Man"
|
||||||
|
flags=(-x)
|
||||||
|
files=(paradise-lost.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "One file, one match, multiple flags" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="9:Of Atreus, Agamemnon, King of men."
|
||||||
|
pattern="OF ATREUS, Agamemnon, KIng of MEN."
|
||||||
|
flags=(-n -i -x)
|
||||||
|
files=(iliad.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "One file, several matches, no flags" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="Nor how it may concern my modesty,
|
||||||
|
But I beseech your grace that I may know
|
||||||
|
The worst that may befall me in this case,"
|
||||||
|
pattern="may"
|
||||||
|
flags=()
|
||||||
|
files=(midsummer-night.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "One file, several matches, print line numbers flag" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="3:Nor how it may concern my modesty,
|
||||||
|
5:But I beseech your grace that I may know
|
||||||
|
6:The worst that may befall me in this case,"
|
||||||
|
pattern="may"
|
||||||
|
flags=(-n)
|
||||||
|
files=(midsummer-night.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@test "One file, several matches, match entire lines flag" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected=""
|
||||||
|
pattern="may"
|
||||||
|
flags=(-x)
|
||||||
|
files=(midsummer-night.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "One file, several matches, case-insensitive flag" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="Achilles sing, O Goddess! Peleus' son;
|
||||||
|
The noble Chief Achilles from the son"
|
||||||
|
pattern="ACHILLES"
|
||||||
|
flags=(-i)
|
||||||
|
files=(iliad.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "One file, several matches, inverted flag" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="Brought Death into the World, and all our woe,
|
||||||
|
With loss of Eden, till one greater Man
|
||||||
|
Restore us, and regain the blissful Seat,
|
||||||
|
Sing Heav'nly Muse, that on the secret top
|
||||||
|
That Shepherd, who first taught the chosen Seed"
|
||||||
|
pattern="Of"
|
||||||
|
flags=(-v)
|
||||||
|
files=(paradise-lost.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "One file, no matches, various flags" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected=""
|
||||||
|
pattern="Gandalf"
|
||||||
|
flags=(-n -l -x -i)
|
||||||
|
files=(iliad.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "One file, one match, file flag takes precedence over line flag" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="iliad.txt"
|
||||||
|
pattern="ten"
|
||||||
|
flags=(-n -l)
|
||||||
|
files=(iliad.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "One file, several matches, inverted and match entire lines flags" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="Achilles sing, O Goddess! Peleus' son;
|
||||||
|
His wrath pernicious, who ten thousand woes
|
||||||
|
Caused to Achaia's host, sent many a soul
|
||||||
|
And Heroes gave (so stood the will of Jove)
|
||||||
|
To dogs and to all ravening fowls a prey,
|
||||||
|
When fierce dispute had separated once
|
||||||
|
The noble Chief Achilles from the son
|
||||||
|
Of Atreus, Agamemnon, King of men."
|
||||||
|
pattern="Illustrious into Ades premature,"
|
||||||
|
flags=(-x -v)
|
||||||
|
files=(iliad.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Multiple files
|
||||||
|
|
||||||
|
@test "Multiple files, one match, no flags" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="iliad.txt:Of Atreus, Agamemnon, King of men."
|
||||||
|
pattern="Agamemnon"
|
||||||
|
flags=()
|
||||||
|
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Multiple files, several matches, no flags" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="midsummer-night.txt:Nor how it may concern my modesty,
|
||||||
|
midsummer-night.txt:But I beseech your grace that I may know
|
||||||
|
midsummer-night.txt:The worst that may befall me in this case,"
|
||||||
|
pattern="may"
|
||||||
|
flags=()
|
||||||
|
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Multiple files, several matches, print line numbers flag" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="midsummer-night.txt:5:But I beseech your grace that I may know
|
||||||
|
midsummer-night.txt:6:The worst that may befall me in this case,
|
||||||
|
paradise-lost.txt:2:Of that Forbidden Tree, whose mortal tast
|
||||||
|
paradise-lost.txt:6:Sing Heav'nly Muse, that on the secret top"
|
||||||
|
pattern="that"
|
||||||
|
flags=(-n)
|
||||||
|
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Multiple files, one match, print file names flag" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="iliad.txt
|
||||||
|
paradise-lost.txt"
|
||||||
|
pattern="who"
|
||||||
|
flags=(-l)
|
||||||
|
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Multiple files, several matches, case-insensitive flag" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="iliad.txt:Caused to Achaia's host, sent many a soul
|
||||||
|
iliad.txt:Illustrious into Ades premature,
|
||||||
|
iliad.txt:And Heroes gave (so stood the will of Jove)
|
||||||
|
iliad.txt:To dogs and to all ravening fowls a prey,
|
||||||
|
midsummer-night.txt:I do entreat your grace to pardon me.
|
||||||
|
midsummer-night.txt:In such a presence here to plead my thoughts;
|
||||||
|
midsummer-night.txt:If I refuse to wed Demetrius.
|
||||||
|
paradise-lost.txt:Brought Death into the World, and all our woe,
|
||||||
|
paradise-lost.txt:Restore us, and regain the blissful Seat,
|
||||||
|
paradise-lost.txt:Sing Heav'nly Muse, that on the secret top"
|
||||||
|
pattern="TO"
|
||||||
|
flags=(-i)
|
||||||
|
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Multiple files, several matches, inverted flag" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="iliad.txt:Achilles sing, O Goddess! Peleus' son;
|
||||||
|
iliad.txt:The noble Chief Achilles from the son
|
||||||
|
midsummer-night.txt:If I refuse to wed Demetrius."
|
||||||
|
pattern="a"
|
||||||
|
flags=(-v)
|
||||||
|
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Multiple files, one match, match entire lines flag" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="midsummer-night.txt:But I beseech your grace that I may know"
|
||||||
|
pattern="But I beseech your grace that I may know"
|
||||||
|
flags=(-x)
|
||||||
|
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Multiple files, one match, multiple flags" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="paradise-lost.txt:4:With loss of Eden, till one greater Man"
|
||||||
|
pattern="WITH LOSS OF EDEN, TILL ONE GREATER MAN"
|
||||||
|
flags=(-n -i -x)
|
||||||
|
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Multiple files, no matches, various flags" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected=""
|
||||||
|
pattern="Frodo"
|
||||||
|
flags=(-n -l -i -x)
|
||||||
|
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Multiple files, several matches, file flag takes precedence over line number flag" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="iliad.txt
|
||||||
|
paradise-lost.txt"
|
||||||
|
pattern="who"
|
||||||
|
flags=(-n -l)
|
||||||
|
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Multiple files, several matches, inverted and match entire lines flags" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="iliad.txt:Achilles sing, O Goddess! Peleus' son;
|
||||||
|
iliad.txt:His wrath pernicious, who ten thousand woes
|
||||||
|
iliad.txt:Caused to Achaia's host, sent many a soul
|
||||||
|
iliad.txt:And Heroes gave (so stood the will of Jove)
|
||||||
|
iliad.txt:To dogs and to all ravening fowls a prey,
|
||||||
|
iliad.txt:When fierce dispute had separated once
|
||||||
|
iliad.txt:The noble Chief Achilles from the son
|
||||||
|
iliad.txt:Of Atreus, Agamemnon, King of men.
|
||||||
|
midsummer-night.txt:I do entreat your grace to pardon me.
|
||||||
|
midsummer-night.txt:I know not by what power I am made bold,
|
||||||
|
midsummer-night.txt:Nor how it may concern my modesty,
|
||||||
|
midsummer-night.txt:In such a presence here to plead my thoughts;
|
||||||
|
midsummer-night.txt:But I beseech your grace that I may know
|
||||||
|
midsummer-night.txt:The worst that may befall me in this case,
|
||||||
|
midsummer-night.txt:If I refuse to wed Demetrius.
|
||||||
|
paradise-lost.txt:Of Mans First Disobedience, and the Fruit
|
||||||
|
paradise-lost.txt:Of that Forbidden Tree, whose mortal tast
|
||||||
|
paradise-lost.txt:Brought Death into the World, and all our woe,
|
||||||
|
paradise-lost.txt:With loss of Eden, till one greater Man
|
||||||
|
paradise-lost.txt:Restore us, and regain the blissful Seat,
|
||||||
|
paradise-lost.txt:Sing Heav'nly Muse, that on the secret top
|
||||||
|
paradise-lost.txt:Of Oreb, or of Sinai, didst inspire
|
||||||
|
paradise-lost.txt:That Shepherd, who first taught the chosen Seed"
|
||||||
|
pattern="Illustrious into Ades premature,"
|
||||||
|
flags=(-x -v)
|
||||||
|
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
|
||||||
|
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
58
bash/grep/grep.sh
Normal file
58
bash/grep/grep.sh
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# parsing CLI options
|
||||||
|
while getopts :nlivx option; do
|
||||||
|
case "$option" in
|
||||||
|
# include line numbers in output
|
||||||
|
n) linenr=1 ;;
|
||||||
|
# only file names containing matching lines are printed
|
||||||
|
l) filename=1 ;;
|
||||||
|
# case insentivity
|
||||||
|
i) shopt -s nocasematch ;;
|
||||||
|
# matching lines are inverted (i.e., lines that do not match)
|
||||||
|
v) invert=1 ;;
|
||||||
|
# pattern should match whole line
|
||||||
|
x) entire=1 ;;
|
||||||
|
# other options are invalid and exit
|
||||||
|
\?) echo Invalid option.; exit 1;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# shifts the command-line arguments so that the remaining arguments
|
||||||
|
# are just the pattern and the files to search in
|
||||||
|
shift $((OPTIND-1))
|
||||||
|
|
||||||
|
pattern=$1
|
||||||
|
shift
|
||||||
|
files=( "$@" )
|
||||||
|
# if entire is set to 1, modifies the pattern to match the entire line
|
||||||
|
# by anchoring it with ^ (beginning of line) and $ (end of line)
|
||||||
|
(( entire )) && pattern="^$pattern$"
|
||||||
|
|
||||||
|
for file in "${files[@]}"; do
|
||||||
|
count=0
|
||||||
|
while read -r line
|
||||||
|
do
|
||||||
|
(( count++ ))
|
||||||
|
out=
|
||||||
|
if (( invert )); then
|
||||||
|
! [[ ${line} =~ ${pattern} ]] && out="$line"
|
||||||
|
else
|
||||||
|
[[ ${line} =~ ${pattern} ]] && out="$line"
|
||||||
|
fi
|
||||||
|
# if filename is set to 1, prints the file name and breaks out
|
||||||
|
# of the loop after the first matching line in each file
|
||||||
|
if (( filename )) && [[ -n $out ]]; then
|
||||||
|
echo $file;
|
||||||
|
break;
|
||||||
|
fi
|
||||||
|
if (( linenr )) && [[ -n $out ]]; then
|
||||||
|
out="$count":$out;
|
||||||
|
fi
|
||||||
|
if (( ${#files[@]} > 1 )) && [[ -n $out ]]; then
|
||||||
|
out="$file":$out;
|
||||||
|
fi
|
||||||
|
[[ -n $out ]] && echo $out;
|
||||||
|
done < "$file"
|
||||||
|
done
|
||||||
|
exit 0
|
||||||
24
bash/matching-brackets/.exercism/config.json
Normal file
24
bash/matching-brackets/.exercism/config.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"glennj"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"bkhl",
|
||||||
|
"guygastineau",
|
||||||
|
"IsaacG",
|
||||||
|
"kotp"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"matching_brackets.sh"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"matching_brackets.bats"
|
||||||
|
],
|
||||||
|
"example": [
|
||||||
|
".meta/example.sh"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"blurb": "Make sure the brackets and braces all match.",
|
||||||
|
"source": "Ginna Baker"
|
||||||
|
}
|
||||||
1
bash/matching-brackets/.exercism/metadata.json
Normal file
1
bash/matching-brackets/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"bash","exercise":"matching-brackets","id":"b50475a6d5ae42daa2f76faee41171ae","url":"https://exercism.org/tracks/bash/exercises/matching-brackets","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||||
105
bash/matching-brackets/HELP.md
Normal file
105
bash/matching-brackets/HELP.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
Each exercise contains a test file.
|
||||||
|
Run the tests using the `bats` program.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats hello_world.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
`bats` will need to be installed.
|
||||||
|
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
|
||||||
|
|
||||||
|
[tests]: https://exercism.org/docs/tracks/bash/tests
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
[bats-assert]: https://github.com/bats-core/bats-assert
|
||||||
|
|
||||||
|
## 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 next annotation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
[[ $BATS_RUN_SKIPPED == true ]] || skip
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overriding skips
|
||||||
|
|
||||||
|
To run all tests, including the ones with `skip` annotations, you can run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BATS_RUN_SKIPPED=true bats exercise_name.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
It can be convenient to use a wrapper function to save on typing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats() {
|
||||||
|
BATS_RUN_SKIPPED=true command bats *.bats
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run tests with just:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit matching_brackets.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
|
||||||
|
- [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.
|
||||||
|
|
||||||
|
Check your code for syntax errors: paste your code into
|
||||||
|
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
|
||||||
|
|
||||||
|
Stack Overflow will be your first stop for bash questions.
|
||||||
|
|
||||||
|
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
|
||||||
|
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
|
||||||
|
* the "Books and Resources" section is particularly useful.
|
||||||
|
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
|
||||||
|
|
||||||
|
## External utilities
|
||||||
|
|
||||||
|
`bash` is a language to write "scripts" -- programs that can call
|
||||||
|
external tools, such as
|
||||||
|
[`sed`](https://www.gnu.org/software/sed/),
|
||||||
|
[`awk`](https://www.gnu.org/software/gawk/),
|
||||||
|
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
|
||||||
|
and even programs written in other programming languages,
|
||||||
|
like [`Python`](https://www.python.org/).
|
||||||
|
This track does not restrict the usage of these utilities, and as long
|
||||||
|
as your solution is portable between systems and does not require
|
||||||
|
installation of third party applications, feel free to use them to solve
|
||||||
|
the exercise.
|
||||||
|
|
||||||
|
For an extra challenge, if you would like to have a better understanding of
|
||||||
|
the language, try to re-implement the solution in pure bash, without using
|
||||||
|
any external tools. There are some types of problems that bash cannot solve,
|
||||||
|
such as floating point arithmetic and manipulating dates: for those, you
|
||||||
|
must call out to an external tool.
|
||||||
26
bash/matching-brackets/README.md
Normal file
26
bash/matching-brackets/README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Matching Brackets
|
||||||
|
|
||||||
|
Welcome to Matching Brackets on Exercism's Bash Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Given a string containing brackets `[]`, braces `{}`, parentheses `()`, or any combination thereof, verify that any and all pairs are matched and nested correctly.
|
||||||
|
The string may also contain other characters, which for the purposes of this exercise should be ignored.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @glennj
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @bkhl
|
||||||
|
- @guygastineau
|
||||||
|
- @IsaacG
|
||||||
|
- @kotp
|
||||||
|
|
||||||
|
### Based on
|
||||||
|
|
||||||
|
Ginna Baker
|
||||||
637
bash/matching-brackets/bats-extra.bash
Normal file
637
bash/matching-brackets/bats-extra.bash
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
# This is the source code for bats-support and bats-assert, concatenated
|
||||||
|
# * https://github.com/bats-core/bats-support
|
||||||
|
# * https://github.com/bats-core/bats-assert
|
||||||
|
#
|
||||||
|
# Comments have been removed to save space. See the git repos for full source code.
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
#
|
||||||
|
# bats-support - Supporting library for Bats test helpers
|
||||||
|
#
|
||||||
|
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
|
||||||
|
#
|
||||||
|
# To the extent possible under law, the author(s) have dedicated all
|
||||||
|
# copyright and related and neighboring rights to this software to the
|
||||||
|
# public domain worldwide. This software is distributed without any
|
||||||
|
# warranty.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the CC0 Public Domain Dedication
|
||||||
|
# along with this software. If not, see
|
||||||
|
# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
(( $# == 0 )) && batslib_err || batslib_err "$@"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_caller() {
|
||||||
|
local -i is_mode_direct=1
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-i|--indirect) is_mode_direct=0; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r func="$1"
|
||||||
|
|
||||||
|
# Check call stack.
|
||||||
|
if (( is_mode_direct )); then
|
||||||
|
[[ $func == "${FUNCNAME[2]}" ]] && return 0
|
||||||
|
else
|
||||||
|
local -i depth
|
||||||
|
for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do
|
||||||
|
[[ $func == "${FUNCNAME[$depth]}" ]] && return 0
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_err() {
|
||||||
|
{ if (( $# > 0 )); then
|
||||||
|
echo "$@"
|
||||||
|
else
|
||||||
|
cat -
|
||||||
|
fi
|
||||||
|
} >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_count_lines() {
|
||||||
|
local -i n_lines=0
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
(( ++n_lines ))
|
||||||
|
done < <(printf '%s' "$1")
|
||||||
|
echo "$n_lines"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_single_line() {
|
||||||
|
for string in "$@"; do
|
||||||
|
(( $(batslib_count_lines "$string") > 1 )) && return 1
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_get_max_single_line_key_width() {
|
||||||
|
local -i max_len=-1
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
local -i key_len="${#1}"
|
||||||
|
batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
echo "$max_len"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single() {
|
||||||
|
local -ir col_width="$1"; shift
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%-*s : %s\n' "$col_width" "$1" "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_multi() {
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )"
|
||||||
|
printf '%s\n' "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single_or_multi() {
|
||||||
|
local -ir width="$1"; shift
|
||||||
|
local -a pairs=( "$@" )
|
||||||
|
|
||||||
|
local -a values=()
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
values+=( "${pairs[$i]}" )
|
||||||
|
done
|
||||||
|
|
||||||
|
if batslib_is_single_line "${values[@]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${pairs[@]}"
|
||||||
|
else
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )"
|
||||||
|
done
|
||||||
|
batslib_print_kv_multi "${pairs[@]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_prefix() {
|
||||||
|
local -r prefix="${1:- }"
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
printf '%s%s\n' "$prefix" "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_mark() {
|
||||||
|
local -r symbol="$1"; shift
|
||||||
|
# Sort line numbers.
|
||||||
|
set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" )
|
||||||
|
|
||||||
|
local line
|
||||||
|
local -i idx=0
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
if (( ${1:--1} == idx )); then
|
||||||
|
printf '%s\n' "${symbol}${line:${#symbol}}"
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
printf '%s\n' "$line"
|
||||||
|
fi
|
||||||
|
(( ++idx ))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_decorate() {
|
||||||
|
echo
|
||||||
|
echo "-- $1 --"
|
||||||
|
cat -
|
||||||
|
echo '--'
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
assert() {
|
||||||
|
if ! "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal() {
|
||||||
|
if [[ $1 != "$2" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$2" \
|
||||||
|
'actual' "$1" \
|
||||||
|
| batslib_decorate 'values do not equal' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_failure() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
(( $# > 0 )) && local -r expected="$1"
|
||||||
|
if (( status == 0 )); then
|
||||||
|
batslib_print_kv_single_or_multi 6 'output' "$output" \
|
||||||
|
| batslib_decorate 'command succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
elif (( $# > 0 )) && (( status != expected )); then
|
||||||
|
{ local -ir width=8
|
||||||
|
batslib_print_kv_single "$width" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" \
|
||||||
|
'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed as expected, but status differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r expected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if ! [[ ${lines[$idx]} =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression does not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single 8 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} =~ $expected ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'regexp' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line matches regular expression' \
|
||||||
|
| fail
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == *"$expected"* ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'substring' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line contains substring' \
|
||||||
|
| fail
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == "$expected" ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'line' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'output does not contain line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_nonempty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_nonempty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local expected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
expected="$(cat -)"
|
||||||
|
else
|
||||||
|
expected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_nonempty )); then
|
||||||
|
if [ -z "$output" ]; then
|
||||||
|
echo 'expected non-empty output, but output was empty' \
|
||||||
|
| batslib_decorate 'no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
elif ! [[ $output =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression does not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$output" \
|
||||||
|
| batslib_decorate 'output differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_success() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
if (( status != 0 )); then
|
||||||
|
{ local -ir width=6
|
||||||
|
batslib_print_kv_single "$width" 'status' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" 'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute() {
|
||||||
|
if "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r unexpected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression should not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single 5 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Line contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
{ local -ar single=( 'regexp' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should match the regular expression' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
{ local -ar single=( 'substring' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should contain substring' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
{ local -ar single=( 'line' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'line should not be in output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_empty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_empty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local unexpected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
unexpected="$(cat -)"
|
||||||
|
else
|
||||||
|
unexpected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_empty )); then
|
||||||
|
if [ -n "$output" ]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output non-empty, but expected no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ $output =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression should not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output equals, but it was expected to differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
145
bash/matching-brackets/matching_brackets.bats
Normal file
145
bash/matching-brackets/matching_brackets.bats
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
load bats-extra
|
||||||
|
|
||||||
|
# local version: 2.0.0.0
|
||||||
|
|
||||||
|
|
||||||
|
@test "paired square brackets" {
|
||||||
|
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "[]"
|
||||||
|
assert_success
|
||||||
|
assert_output "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "empty string" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh ""
|
||||||
|
assert_success
|
||||||
|
assert_output "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "unpaired brackets" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "[["
|
||||||
|
assert_success
|
||||||
|
assert_output "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "wrong ordered brackets" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "}{"
|
||||||
|
assert_success
|
||||||
|
assert_output "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "wrong closing bracket" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "{]"
|
||||||
|
assert_success
|
||||||
|
assert_output "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "paired with whitespace" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "{ }"
|
||||||
|
assert_success
|
||||||
|
assert_output "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "partially paired brackets" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "{[])"
|
||||||
|
assert_success
|
||||||
|
assert_output "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "simple nested brackets" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "{[]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "several paired brackets" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "{}[]"
|
||||||
|
assert_success
|
||||||
|
assert_output "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "paired and nested brackets" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "([{}({}[])])"
|
||||||
|
assert_success
|
||||||
|
assert_output "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "unopened closing brackets" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "{[)][]}"
|
||||||
|
assert_success
|
||||||
|
assert_output "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "unpaired and nested brackets" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "([{])"
|
||||||
|
assert_success
|
||||||
|
assert_output "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "paired and wrong nested brackets" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "[({]})"
|
||||||
|
assert_success
|
||||||
|
assert_output "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "paired and wrong nested brackets but innermost are correct" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "[({}])"
|
||||||
|
assert_success
|
||||||
|
assert_output "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "paired and incomplete brackets" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "{}["
|
||||||
|
assert_success
|
||||||
|
assert_output "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "too many closing brackets" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "[]]"
|
||||||
|
assert_success
|
||||||
|
assert_output "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "early unexpected brackets" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh ")()"
|
||||||
|
assert_success
|
||||||
|
assert_output "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "early mismatched brackets" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "{)()"
|
||||||
|
assert_success
|
||||||
|
assert_output "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "math expression" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "(((185 + 223.85) * 15) - 543)/2"
|
||||||
|
assert_success
|
||||||
|
assert_output "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "complex latex expression" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash matching_brackets.sh "\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \\end{array}\\right)"
|
||||||
|
assert_success
|
||||||
|
assert_output "true"
|
||||||
|
}
|
||||||
20
bash/matching-brackets/matching_brackets.sh
Normal file
20
bash/matching-brackets/matching_brackets.sh
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
input="$1"
|
||||||
|
|
||||||
|
# Remove all characters except brackets, braces, and parentheses
|
||||||
|
cleaned_input=$(echo "$input" | tr -cd '[]{}()')
|
||||||
|
|
||||||
|
# Keep replacing any valid pair of brackets, braces, or parentheses with an empty string
|
||||||
|
while [[ $cleaned_input =~ (\(\)|\{\}|\[\]) ]]; do
|
||||||
|
cleaned_input=${cleaned_input//\(\)/}
|
||||||
|
cleaned_input=${cleaned_input//\{\}/}
|
||||||
|
cleaned_input=${cleaned_input//\[\]/}
|
||||||
|
done
|
||||||
|
|
||||||
|
# If the cleaned string is empty, all pairs are matched and nested correctly
|
||||||
|
if [[ -z $cleaned_input ]]; then
|
||||||
|
echo "true"
|
||||||
|
else
|
||||||
|
echo "false"
|
||||||
|
fi
|
||||||
25
bash/proverb/.exercism/config.json
Normal file
25
bash/proverb/.exercism/config.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"glennj"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"bkhl",
|
||||||
|
"guygastineau",
|
||||||
|
"IsaacG",
|
||||||
|
"kotp"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"proverb.sh"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"proverb.bats"
|
||||||
|
],
|
||||||
|
"example": [
|
||||||
|
".meta/example.sh"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"blurb": "For want of a horseshoe nail, a kingdom was lost, or so the saying goes. Output the full text of this proverbial rhyme.",
|
||||||
|
"source": "Wikipedia",
|
||||||
|
"source_url": "https://en.wikipedia.org/wiki/For_Want_of_a_Nail"
|
||||||
|
}
|
||||||
1
bash/proverb/.exercism/metadata.json
Normal file
1
bash/proverb/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"bash","exercise":"proverb","id":"b9604697f68f45c0a89bd7b3ebb75cd5","url":"https://exercism.org/tracks/bash/exercises/proverb","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||||
105
bash/proverb/HELP.md
Normal file
105
bash/proverb/HELP.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
Each exercise contains a test file.
|
||||||
|
Run the tests using the `bats` program.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats hello_world.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
`bats` will need to be installed.
|
||||||
|
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
|
||||||
|
|
||||||
|
[tests]: https://exercism.org/docs/tracks/bash/tests
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
[bats-assert]: https://github.com/bats-core/bats-assert
|
||||||
|
|
||||||
|
## 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 next annotation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
[[ $BATS_RUN_SKIPPED == true ]] || skip
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overriding skips
|
||||||
|
|
||||||
|
To run all tests, including the ones with `skip` annotations, you can run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BATS_RUN_SKIPPED=true bats exercise_name.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
It can be convenient to use a wrapper function to save on typing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats() {
|
||||||
|
BATS_RUN_SKIPPED=true command bats *.bats
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run tests with just:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit proverb.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
|
||||||
|
- [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.
|
||||||
|
|
||||||
|
Check your code for syntax errors: paste your code into
|
||||||
|
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
|
||||||
|
|
||||||
|
Stack Overflow will be your first stop for bash questions.
|
||||||
|
|
||||||
|
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
|
||||||
|
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
|
||||||
|
* the "Books and Resources" section is particularly useful.
|
||||||
|
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
|
||||||
|
|
||||||
|
## External utilities
|
||||||
|
|
||||||
|
`bash` is a language to write "scripts" -- programs that can call
|
||||||
|
external tools, such as
|
||||||
|
[`sed`](https://www.gnu.org/software/sed/),
|
||||||
|
[`awk`](https://www.gnu.org/software/gawk/),
|
||||||
|
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
|
||||||
|
and even programs written in other programming languages,
|
||||||
|
like [`Python`](https://www.python.org/).
|
||||||
|
This track does not restrict the usage of these utilities, and as long
|
||||||
|
as your solution is portable between systems and does not require
|
||||||
|
installation of third party applications, feel free to use them to solve
|
||||||
|
the exercise.
|
||||||
|
|
||||||
|
For an extra challenge, if you would like to have a better understanding of
|
||||||
|
the language, try to re-implement the solution in pure bash, without using
|
||||||
|
any external tools. There are some types of problems that bash cannot solve,
|
||||||
|
such as floating point arithmetic and manipulating dates: for those, you
|
||||||
|
must call out to an external tool.
|
||||||
41
bash/proverb/README.md
Normal file
41
bash/proverb/README.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Proverb
|
||||||
|
|
||||||
|
Welcome to Proverb on Exercism's Bash Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
For want of a horseshoe nail, a kingdom was lost, or so the saying goes.
|
||||||
|
|
||||||
|
Given a list of inputs, generate the relevant proverb.
|
||||||
|
For example, given the list `["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"]`, you will output the full text of this proverbial rhyme:
|
||||||
|
|
||||||
|
```text
|
||||||
|
For want of a nail the shoe was lost.
|
||||||
|
For want of a shoe the horse was lost.
|
||||||
|
For want of a horse the rider was lost.
|
||||||
|
For want of a rider the message was lost.
|
||||||
|
For want of a message the battle was lost.
|
||||||
|
For want of a battle the kingdom was lost.
|
||||||
|
And all for the want of a nail.
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the list of inputs may vary; your solution should be able to handle lists of arbitrary length and content.
|
||||||
|
No line of the output text should be a static, unchanging string; all should vary according to the input given.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @glennj
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @bkhl
|
||||||
|
- @guygastineau
|
||||||
|
- @IsaacG
|
||||||
|
- @kotp
|
||||||
|
|
||||||
|
### Based on
|
||||||
|
|
||||||
|
Wikipedia - https://en.wikipedia.org/wiki/For_Want_of_a_Nail
|
||||||
637
bash/proverb/bats-extra.bash
Normal file
637
bash/proverb/bats-extra.bash
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
# This is the source code for bats-support and bats-assert, concatenated
|
||||||
|
# * https://github.com/bats-core/bats-support
|
||||||
|
# * https://github.com/bats-core/bats-assert
|
||||||
|
#
|
||||||
|
# Comments have been removed to save space. See the git repos for full source code.
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
#
|
||||||
|
# bats-support - Supporting library for Bats test helpers
|
||||||
|
#
|
||||||
|
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
|
||||||
|
#
|
||||||
|
# To the extent possible under law, the author(s) have dedicated all
|
||||||
|
# copyright and related and neighboring rights to this software to the
|
||||||
|
# public domain worldwide. This software is distributed without any
|
||||||
|
# warranty.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the CC0 Public Domain Dedication
|
||||||
|
# along with this software. If not, see
|
||||||
|
# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
(( $# == 0 )) && batslib_err || batslib_err "$@"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_caller() {
|
||||||
|
local -i is_mode_direct=1
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-i|--indirect) is_mode_direct=0; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r func="$1"
|
||||||
|
|
||||||
|
# Check call stack.
|
||||||
|
if (( is_mode_direct )); then
|
||||||
|
[[ $func == "${FUNCNAME[2]}" ]] && return 0
|
||||||
|
else
|
||||||
|
local -i depth
|
||||||
|
for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do
|
||||||
|
[[ $func == "${FUNCNAME[$depth]}" ]] && return 0
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_err() {
|
||||||
|
{ if (( $# > 0 )); then
|
||||||
|
echo "$@"
|
||||||
|
else
|
||||||
|
cat -
|
||||||
|
fi
|
||||||
|
} >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_count_lines() {
|
||||||
|
local -i n_lines=0
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
(( ++n_lines ))
|
||||||
|
done < <(printf '%s' "$1")
|
||||||
|
echo "$n_lines"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_single_line() {
|
||||||
|
for string in "$@"; do
|
||||||
|
(( $(batslib_count_lines "$string") > 1 )) && return 1
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_get_max_single_line_key_width() {
|
||||||
|
local -i max_len=-1
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
local -i key_len="${#1}"
|
||||||
|
batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
echo "$max_len"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single() {
|
||||||
|
local -ir col_width="$1"; shift
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%-*s : %s\n' "$col_width" "$1" "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_multi() {
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )"
|
||||||
|
printf '%s\n' "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single_or_multi() {
|
||||||
|
local -ir width="$1"; shift
|
||||||
|
local -a pairs=( "$@" )
|
||||||
|
|
||||||
|
local -a values=()
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
values+=( "${pairs[$i]}" )
|
||||||
|
done
|
||||||
|
|
||||||
|
if batslib_is_single_line "${values[@]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${pairs[@]}"
|
||||||
|
else
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )"
|
||||||
|
done
|
||||||
|
batslib_print_kv_multi "${pairs[@]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_prefix() {
|
||||||
|
local -r prefix="${1:- }"
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
printf '%s%s\n' "$prefix" "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_mark() {
|
||||||
|
local -r symbol="$1"; shift
|
||||||
|
# Sort line numbers.
|
||||||
|
set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" )
|
||||||
|
|
||||||
|
local line
|
||||||
|
local -i idx=0
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
if (( ${1:--1} == idx )); then
|
||||||
|
printf '%s\n' "${symbol}${line:${#symbol}}"
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
printf '%s\n' "$line"
|
||||||
|
fi
|
||||||
|
(( ++idx ))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_decorate() {
|
||||||
|
echo
|
||||||
|
echo "-- $1 --"
|
||||||
|
cat -
|
||||||
|
echo '--'
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
assert() {
|
||||||
|
if ! "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal() {
|
||||||
|
if [[ $1 != "$2" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$2" \
|
||||||
|
'actual' "$1" \
|
||||||
|
| batslib_decorate 'values do not equal' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_failure() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
(( $# > 0 )) && local -r expected="$1"
|
||||||
|
if (( status == 0 )); then
|
||||||
|
batslib_print_kv_single_or_multi 6 'output' "$output" \
|
||||||
|
| batslib_decorate 'command succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
elif (( $# > 0 )) && (( status != expected )); then
|
||||||
|
{ local -ir width=8
|
||||||
|
batslib_print_kv_single "$width" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" \
|
||||||
|
'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed as expected, but status differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r expected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if ! [[ ${lines[$idx]} =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression does not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single 8 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} =~ $expected ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'regexp' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line matches regular expression' \
|
||||||
|
| fail
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == *"$expected"* ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'substring' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line contains substring' \
|
||||||
|
| fail
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == "$expected" ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'line' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'output does not contain line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_nonempty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_nonempty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local expected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
expected="$(cat -)"
|
||||||
|
else
|
||||||
|
expected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_nonempty )); then
|
||||||
|
if [ -z "$output" ]; then
|
||||||
|
echo 'expected non-empty output, but output was empty' \
|
||||||
|
| batslib_decorate 'no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
elif ! [[ $output =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression does not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$output" \
|
||||||
|
| batslib_decorate 'output differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_success() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
if (( status != 0 )); then
|
||||||
|
{ local -ir width=6
|
||||||
|
batslib_print_kv_single "$width" 'status' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" 'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute() {
|
||||||
|
if "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r unexpected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression should not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single 5 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Line contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
{ local -ar single=( 'regexp' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should match the regular expression' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
{ local -ar single=( 'substring' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should contain substring' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
{ local -ar single=( 'line' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'line should not be in output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_empty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_empty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local unexpected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
unexpected="$(cat -)"
|
||||||
|
else
|
||||||
|
unexpected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_empty )); then
|
||||||
|
if [ -n "$output" ]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output non-empty, but expected no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ $output =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression should not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output equals, but it was expected to differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
108
bash/proverb/proverb.bats
Normal file
108
bash/proverb/proverb.bats
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
load bats-extra
|
||||||
|
|
||||||
|
# local version: 1.1.0.1
|
||||||
|
|
||||||
|
@test "zero pieces" {
|
||||||
|
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected=""
|
||||||
|
run bash proverb.sh
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "one piece" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected=$(cat <<END
|
||||||
|
And all for the want of a nail.
|
||||||
|
END
|
||||||
|
)
|
||||||
|
run bash proverb.sh nail
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "two pieces" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected=$(cat <<END
|
||||||
|
For want of a nail the shoe was lost.
|
||||||
|
And all for the want of a nail.
|
||||||
|
END
|
||||||
|
)
|
||||||
|
run bash proverb.sh nail shoe
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "three pieces" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected=$(cat <<END
|
||||||
|
For want of a nail the shoe was lost.
|
||||||
|
For want of a shoe the horse was lost.
|
||||||
|
And all for the want of a nail.
|
||||||
|
END
|
||||||
|
)
|
||||||
|
run bash proverb.sh nail shoe horse
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@test "full proverb" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected=$(cat <<END
|
||||||
|
For want of a nail the shoe was lost.
|
||||||
|
For want of a shoe the horse was lost.
|
||||||
|
For want of a horse the rider was lost.
|
||||||
|
For want of a rider the message was lost.
|
||||||
|
For want of a message the battle was lost.
|
||||||
|
For want of a battle the kingdom was lost.
|
||||||
|
And all for the want of a nail.
|
||||||
|
END
|
||||||
|
)
|
||||||
|
run bash proverb.sh nail shoe horse rider message battle kingdom
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "four pieces modernized" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected=$(cat <<END
|
||||||
|
For want of a pin the gun was lost.
|
||||||
|
For want of a gun the soldier was lost.
|
||||||
|
For want of a soldier the battle was lost.
|
||||||
|
And all for the want of a pin.
|
||||||
|
END
|
||||||
|
)
|
||||||
|
run bash proverb.sh pin gun soldier battle
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
# bash-specific tests: Focus the student's attention on the effects of
|
||||||
|
# word splitting and filename expansion:
|
||||||
|
# https://www.gnu.org/software/bash/manual/bash.html#Shell-Expansions
|
||||||
|
|
||||||
|
@test "items with whitespace" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected=$(cat <<END
|
||||||
|
For want of a rusty nail the horse shoe was lost.
|
||||||
|
And all for the want of a rusty nail.
|
||||||
|
END
|
||||||
|
)
|
||||||
|
run bash proverb.sh "rusty nail" "horse shoe"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "shell globbing character" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected=$(cat <<END
|
||||||
|
For want of a quotes the * was lost.
|
||||||
|
And all for the want of a quotes.
|
||||||
|
END
|
||||||
|
)
|
||||||
|
run bash proverb.sh quotes "*"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
25
bash/proverb/proverb.sh
Normal file
25
bash/proverb/proverb.sh
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
inputs=("$@")
|
||||||
|
length=${#inputs[@]}
|
||||||
|
last_index=$((length - 1))
|
||||||
|
|
||||||
|
# Loop through the 'inputs' array, excluding the last element
|
||||||
|
for ((i=0; i<last_index; i++)); do
|
||||||
|
current=${inputs[i]}
|
||||||
|
next=${inputs[i+1]}
|
||||||
|
|
||||||
|
# Check if the current element is empty
|
||||||
|
if [[ -z $current ]]; then
|
||||||
|
echo "And all for the want of a $next."
|
||||||
|
else
|
||||||
|
echo "For want of a $current the $next was lost."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if the first element is empty
|
||||||
|
if [[ -z ${inputs[0]} ]]; then
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo "And all for the want of a ${inputs[0]}."
|
||||||
|
fi
|
||||||
25
bash/resistor-color-duo/.exercism/config.json
Normal file
25
bash/resistor-color-duo/.exercism/config.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"glennj"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"bkhl",
|
||||||
|
"guygastineau",
|
||||||
|
"IsaacG",
|
||||||
|
"kotp"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"resistor_color_duo.sh"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"resistor_color_duo.bats"
|
||||||
|
],
|
||||||
|
"example": [
|
||||||
|
".meta/example.sh"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"blurb": "Convert color codes, as used on resistors, to a numeric value.",
|
||||||
|
"source": "Maud de Vries, Erik Schierboom",
|
||||||
|
"source_url": "https://github.com/exercism/problem-specifications/issues/1464"
|
||||||
|
}
|
||||||
1
bash/resistor-color-duo/.exercism/metadata.json
Normal file
1
bash/resistor-color-duo/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"bash","exercise":"resistor-color-duo","id":"7e7f83b104f4456080120e237c9cbda1","url":"https://exercism.org/tracks/bash/exercises/resistor-color-duo","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||||
105
bash/resistor-color-duo/HELP.md
Normal file
105
bash/resistor-color-duo/HELP.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
Each exercise contains a test file.
|
||||||
|
Run the tests using the `bats` program.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats hello_world.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
`bats` will need to be installed.
|
||||||
|
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
|
||||||
|
|
||||||
|
[tests]: https://exercism.org/docs/tracks/bash/tests
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
[bats-assert]: https://github.com/bats-core/bats-assert
|
||||||
|
|
||||||
|
## 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 next annotation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
[[ $BATS_RUN_SKIPPED == true ]] || skip
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overriding skips
|
||||||
|
|
||||||
|
To run all tests, including the ones with `skip` annotations, you can run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BATS_RUN_SKIPPED=true bats exercise_name.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
It can be convenient to use a wrapper function to save on typing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats() {
|
||||||
|
BATS_RUN_SKIPPED=true command bats *.bats
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run tests with just:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit resistor_color_duo.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
|
||||||
|
- [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.
|
||||||
|
|
||||||
|
Check your code for syntax errors: paste your code into
|
||||||
|
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
|
||||||
|
|
||||||
|
Stack Overflow will be your first stop for bash questions.
|
||||||
|
|
||||||
|
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
|
||||||
|
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
|
||||||
|
* the "Books and Resources" section is particularly useful.
|
||||||
|
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
|
||||||
|
|
||||||
|
## External utilities
|
||||||
|
|
||||||
|
`bash` is a language to write "scripts" -- programs that can call
|
||||||
|
external tools, such as
|
||||||
|
[`sed`](https://www.gnu.org/software/sed/),
|
||||||
|
[`awk`](https://www.gnu.org/software/gawk/),
|
||||||
|
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
|
||||||
|
and even programs written in other programming languages,
|
||||||
|
like [`Python`](https://www.python.org/).
|
||||||
|
This track does not restrict the usage of these utilities, and as long
|
||||||
|
as your solution is portable between systems and does not require
|
||||||
|
installation of third party applications, feel free to use them to solve
|
||||||
|
the exercise.
|
||||||
|
|
||||||
|
For an extra challenge, if you would like to have a better understanding of
|
||||||
|
the language, try to re-implement the solution in pure bash, without using
|
||||||
|
any external tools. There are some types of problems that bash cannot solve,
|
||||||
|
such as floating point arithmetic and manipulating dates: for those, you
|
||||||
|
must call out to an external tool.
|
||||||
55
bash/resistor-color-duo/README.md
Normal file
55
bash/resistor-color-duo/README.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Resistor Color Duo
|
||||||
|
|
||||||
|
Welcome to Resistor Color Duo on Exercism's Bash Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
If you want to build something using a Raspberry Pi, you'll probably use _resistors_.
|
||||||
|
For this exercise, you need to know two things about them:
|
||||||
|
|
||||||
|
- Each resistor has a resistance value.
|
||||||
|
- Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read.
|
||||||
|
|
||||||
|
To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values.
|
||||||
|
Each band has a position and a numeric value.
|
||||||
|
|
||||||
|
The first 2 bands of a resistor have a simple encoding scheme: each color maps to a single number.
|
||||||
|
For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15.
|
||||||
|
|
||||||
|
In this exercise you are going to create a helpful program so that you don't have to remember the values of the bands.
|
||||||
|
The program will take color names as input and output a two digit number, even if the input is more than two colors!
|
||||||
|
|
||||||
|
The band colors are encoded as follows:
|
||||||
|
|
||||||
|
- Black: 0
|
||||||
|
- Brown: 1
|
||||||
|
- Red: 2
|
||||||
|
- Orange: 3
|
||||||
|
- Yellow: 4
|
||||||
|
- Green: 5
|
||||||
|
- Blue: 6
|
||||||
|
- Violet: 7
|
||||||
|
- Grey: 8
|
||||||
|
- White: 9
|
||||||
|
|
||||||
|
From the example above:
|
||||||
|
brown-green should return 15
|
||||||
|
brown-green-violet should return 15 too, ignoring the third color.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @glennj
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @bkhl
|
||||||
|
- @guygastineau
|
||||||
|
- @IsaacG
|
||||||
|
- @kotp
|
||||||
|
|
||||||
|
### Based on
|
||||||
|
|
||||||
|
Maud de Vries, Erik Schierboom - https://github.com/exercism/problem-specifications/issues/1464
|
||||||
637
bash/resistor-color-duo/bats-extra.bash
Normal file
637
bash/resistor-color-duo/bats-extra.bash
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
# This is the source code for bats-support and bats-assert, concatenated
|
||||||
|
# * https://github.com/bats-core/bats-support
|
||||||
|
# * https://github.com/bats-core/bats-assert
|
||||||
|
#
|
||||||
|
# Comments have been removed to save space. See the git repos for full source code.
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
#
|
||||||
|
# bats-support - Supporting library for Bats test helpers
|
||||||
|
#
|
||||||
|
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
|
||||||
|
#
|
||||||
|
# To the extent possible under law, the author(s) have dedicated all
|
||||||
|
# copyright and related and neighboring rights to this software to the
|
||||||
|
# public domain worldwide. This software is distributed without any
|
||||||
|
# warranty.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the CC0 Public Domain Dedication
|
||||||
|
# along with this software. If not, see
|
||||||
|
# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
(( $# == 0 )) && batslib_err || batslib_err "$@"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_caller() {
|
||||||
|
local -i is_mode_direct=1
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-i|--indirect) is_mode_direct=0; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r func="$1"
|
||||||
|
|
||||||
|
# Check call stack.
|
||||||
|
if (( is_mode_direct )); then
|
||||||
|
[[ $func == "${FUNCNAME[2]}" ]] && return 0
|
||||||
|
else
|
||||||
|
local -i depth
|
||||||
|
for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do
|
||||||
|
[[ $func == "${FUNCNAME[$depth]}" ]] && return 0
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_err() {
|
||||||
|
{ if (( $# > 0 )); then
|
||||||
|
echo "$@"
|
||||||
|
else
|
||||||
|
cat -
|
||||||
|
fi
|
||||||
|
} >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_count_lines() {
|
||||||
|
local -i n_lines=0
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
(( ++n_lines ))
|
||||||
|
done < <(printf '%s' "$1")
|
||||||
|
echo "$n_lines"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_single_line() {
|
||||||
|
for string in "$@"; do
|
||||||
|
(( $(batslib_count_lines "$string") > 1 )) && return 1
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_get_max_single_line_key_width() {
|
||||||
|
local -i max_len=-1
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
local -i key_len="${#1}"
|
||||||
|
batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
echo "$max_len"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single() {
|
||||||
|
local -ir col_width="$1"; shift
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%-*s : %s\n' "$col_width" "$1" "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_multi() {
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )"
|
||||||
|
printf '%s\n' "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single_or_multi() {
|
||||||
|
local -ir width="$1"; shift
|
||||||
|
local -a pairs=( "$@" )
|
||||||
|
|
||||||
|
local -a values=()
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
values+=( "${pairs[$i]}" )
|
||||||
|
done
|
||||||
|
|
||||||
|
if batslib_is_single_line "${values[@]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${pairs[@]}"
|
||||||
|
else
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )"
|
||||||
|
done
|
||||||
|
batslib_print_kv_multi "${pairs[@]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_prefix() {
|
||||||
|
local -r prefix="${1:- }"
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
printf '%s%s\n' "$prefix" "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_mark() {
|
||||||
|
local -r symbol="$1"; shift
|
||||||
|
# Sort line numbers.
|
||||||
|
set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" )
|
||||||
|
|
||||||
|
local line
|
||||||
|
local -i idx=0
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
if (( ${1:--1} == idx )); then
|
||||||
|
printf '%s\n' "${symbol}${line:${#symbol}}"
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
printf '%s\n' "$line"
|
||||||
|
fi
|
||||||
|
(( ++idx ))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_decorate() {
|
||||||
|
echo
|
||||||
|
echo "-- $1 --"
|
||||||
|
cat -
|
||||||
|
echo '--'
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
assert() {
|
||||||
|
if ! "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal() {
|
||||||
|
if [[ $1 != "$2" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$2" \
|
||||||
|
'actual' "$1" \
|
||||||
|
| batslib_decorate 'values do not equal' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_failure() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
(( $# > 0 )) && local -r expected="$1"
|
||||||
|
if (( status == 0 )); then
|
||||||
|
batslib_print_kv_single_or_multi 6 'output' "$output" \
|
||||||
|
| batslib_decorate 'command succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
elif (( $# > 0 )) && (( status != expected )); then
|
||||||
|
{ local -ir width=8
|
||||||
|
batslib_print_kv_single "$width" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" \
|
||||||
|
'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed as expected, but status differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r expected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if ! [[ ${lines[$idx]} =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression does not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single 8 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} =~ $expected ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'regexp' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line matches regular expression' \
|
||||||
|
| fail
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == *"$expected"* ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'substring' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line contains substring' \
|
||||||
|
| fail
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == "$expected" ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'line' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'output does not contain line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_nonempty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_nonempty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local expected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
expected="$(cat -)"
|
||||||
|
else
|
||||||
|
expected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_nonempty )); then
|
||||||
|
if [ -z "$output" ]; then
|
||||||
|
echo 'expected non-empty output, but output was empty' \
|
||||||
|
| batslib_decorate 'no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
elif ! [[ $output =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression does not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$output" \
|
||||||
|
| batslib_decorate 'output differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_success() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
if (( status != 0 )); then
|
||||||
|
{ local -ir width=6
|
||||||
|
batslib_print_kv_single "$width" 'status' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" 'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute() {
|
||||||
|
if "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r unexpected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression should not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single 5 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Line contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
{ local -ar single=( 'regexp' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should match the regular expression' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
{ local -ar single=( 'substring' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should contain substring' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
{ local -ar single=( 'line' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'line should not be in output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_empty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_empty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local unexpected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
unexpected="$(cat -)"
|
||||||
|
else
|
||||||
|
unexpected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_empty )); then
|
||||||
|
if [ -n "$output" ]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output non-empty, but expected no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ $output =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression should not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output equals, but it was expected to differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
82
bash/resistor-color-duo/resistor_color_duo.bats
Normal file
82
bash/resistor-color-duo/resistor_color_duo.bats
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
load bats-extra
|
||||||
|
|
||||||
|
# local version: 2.1.0.1
|
||||||
|
# add test for invalid color
|
||||||
|
|
||||||
|
@test "brown black" {
|
||||||
|
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_duo.sh brown black
|
||||||
|
assert_success
|
||||||
|
assert_output "10"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "blue grey" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_duo.sh blue grey
|
||||||
|
assert_success
|
||||||
|
assert_output "68"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "yellow violet" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_duo.sh yellow violet
|
||||||
|
assert_success
|
||||||
|
assert_output "47"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "white red" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_duo.sh white red
|
||||||
|
assert_success
|
||||||
|
assert_output "92"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "orange orange" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_duo.sh orange orange
|
||||||
|
assert_success
|
||||||
|
assert_output "33"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "invalid color" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_duo.sh foo
|
||||||
|
assert_failure
|
||||||
|
assert_output --partial "invalid color"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "one valid color and one invalid color" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_duo.sh blue foo
|
||||||
|
assert_failure
|
||||||
|
assert_output --partial "invalid color"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "one invalid color and one valid color" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_duo.sh foo blue
|
||||||
|
assert_failure
|
||||||
|
assert_output --partial "invalid color"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ignore too many colors" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_duo.sh green brown orange
|
||||||
|
assert_success
|
||||||
|
assert_output "51"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "black brown" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_duo.sh black brown
|
||||||
|
assert_success
|
||||||
|
assert_output "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "black grey" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_duo.sh black grey
|
||||||
|
assert_success
|
||||||
|
assert_output "8"
|
||||||
|
}
|
||||||
46
bash/resistor-color-duo/resistor_color_duo.sh
Normal file
46
bash/resistor-color-duo/resistor_color_duo.sh
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Initialize variables for the first and second bands
|
||||||
|
first_band=""
|
||||||
|
second_band=""
|
||||||
|
|
||||||
|
# Iterate over the color arguments
|
||||||
|
for ((i=1; i<=2; i++)); do
|
||||||
|
color="${!i}" # Get the value of the i-th argument
|
||||||
|
value="" # Initialize the value for the color
|
||||||
|
|
||||||
|
# Match the color to its numeric value using a case statement
|
||||||
|
case $color in
|
||||||
|
black) value="0" ;;
|
||||||
|
brown) value="1" ;;
|
||||||
|
red) value="2" ;;
|
||||||
|
orange) value="3" ;;
|
||||||
|
yellow) value="4" ;;
|
||||||
|
green) value="5" ;;
|
||||||
|
blue) value="6" ;;
|
||||||
|
violet) value="7" ;;
|
||||||
|
grey) value="8" ;;
|
||||||
|
white) value="9" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Check if the color value is empty (unrecognized color)
|
||||||
|
if [ -z "$value" ]; then
|
||||||
|
echo "invalid color"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Assign the value to the corresponding band
|
||||||
|
if [ -z "$first_band" ]; then
|
||||||
|
first_band="$value"
|
||||||
|
elif [ -z "$second_band" ]; then
|
||||||
|
second_band="$value"
|
||||||
|
break # Break the loop after finding the first two bands
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Print the resulting number
|
||||||
|
if [ "$first_band" = "0" ]; then
|
||||||
|
echo "$second_band"
|
||||||
|
else
|
||||||
|
echo "$first_band$second_band"
|
||||||
|
fi
|
||||||
24
bash/resistor-color-trio/.exercism/config.json
Normal file
24
bash/resistor-color-trio/.exercism/config.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"glennj"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"alirezaghey",
|
||||||
|
"guygastineau",
|
||||||
|
"IsaacG"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"resistor_color_trio.sh"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"resistor_color_trio.bats"
|
||||||
|
],
|
||||||
|
"example": [
|
||||||
|
".meta/example.sh"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"blurb": "Convert color codes, as used on resistors, to a human-readable label.",
|
||||||
|
"source": "Maud de Vries, Erik Schierboom",
|
||||||
|
"source_url": "https://github.com/exercism/problem-specifications/issues/1549"
|
||||||
|
}
|
||||||
1
bash/resistor-color-trio/.exercism/metadata.json
Normal file
1
bash/resistor-color-trio/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"bash","exercise":"resistor-color-trio","id":"a9b9f196cdd6470bb7513963ab18923c","url":"https://exercism.org/tracks/bash/exercises/resistor-color-trio","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||||
105
bash/resistor-color-trio/HELP.md
Normal file
105
bash/resistor-color-trio/HELP.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
Each exercise contains a test file.
|
||||||
|
Run the tests using the `bats` program.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats hello_world.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
`bats` will need to be installed.
|
||||||
|
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
|
||||||
|
|
||||||
|
[tests]: https://exercism.org/docs/tracks/bash/tests
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
[bats-assert]: https://github.com/bats-core/bats-assert
|
||||||
|
|
||||||
|
## 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 next annotation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
[[ $BATS_RUN_SKIPPED == true ]] || skip
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overriding skips
|
||||||
|
|
||||||
|
To run all tests, including the ones with `skip` annotations, you can run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BATS_RUN_SKIPPED=true bats exercise_name.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
It can be convenient to use a wrapper function to save on typing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats() {
|
||||||
|
BATS_RUN_SKIPPED=true command bats *.bats
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run tests with just:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit resistor_color_trio.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
|
||||||
|
- [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.
|
||||||
|
|
||||||
|
Check your code for syntax errors: paste your code into
|
||||||
|
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
|
||||||
|
|
||||||
|
Stack Overflow will be your first stop for bash questions.
|
||||||
|
|
||||||
|
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
|
||||||
|
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
|
||||||
|
* the "Books and Resources" section is particularly useful.
|
||||||
|
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
|
||||||
|
|
||||||
|
## External utilities
|
||||||
|
|
||||||
|
`bash` is a language to write "scripts" -- programs that can call
|
||||||
|
external tools, such as
|
||||||
|
[`sed`](https://www.gnu.org/software/sed/),
|
||||||
|
[`awk`](https://www.gnu.org/software/gawk/),
|
||||||
|
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
|
||||||
|
and even programs written in other programming languages,
|
||||||
|
like [`Python`](https://www.python.org/).
|
||||||
|
This track does not restrict the usage of these utilities, and as long
|
||||||
|
as your solution is portable between systems and does not require
|
||||||
|
installation of third party applications, feel free to use them to solve
|
||||||
|
the exercise.
|
||||||
|
|
||||||
|
For an extra challenge, if you would like to have a better understanding of
|
||||||
|
the language, try to re-implement the solution in pure bash, without using
|
||||||
|
any external tools. There are some types of problems that bash cannot solve,
|
||||||
|
such as floating point arithmetic and manipulating dates: for those, you
|
||||||
|
must call out to an external tool.
|
||||||
77
bash/resistor-color-trio/README.md
Normal file
77
bash/resistor-color-trio/README.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# Resistor Color Trio
|
||||||
|
|
||||||
|
Welcome to Resistor Color Trio on Exercism's Bash Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
If you want to build something using a Raspberry Pi, you'll probably use _resistors_.
|
||||||
|
For this exercise, you need to know only three things about them:
|
||||||
|
|
||||||
|
- Each resistor has a resistance value.
|
||||||
|
- Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read.
|
||||||
|
To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values.
|
||||||
|
- Each band acts as a digit of a number.
|
||||||
|
For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15.
|
||||||
|
In this exercise, you are going to create a helpful program so that you don't have to remember the values of the bands.
|
||||||
|
The program will take 3 colors as input, and outputs the correct value, in ohms.
|
||||||
|
The color bands are encoded as follows:
|
||||||
|
|
||||||
|
- Black: 0
|
||||||
|
- Brown: 1
|
||||||
|
- Red: 2
|
||||||
|
- Orange: 3
|
||||||
|
- Yellow: 4
|
||||||
|
- Green: 5
|
||||||
|
- Blue: 6
|
||||||
|
- Violet: 7
|
||||||
|
- Grey: 8
|
||||||
|
- White: 9
|
||||||
|
|
||||||
|
In `resistor-color duo` you decoded the first two colors.
|
||||||
|
For instance: orange-orange got the main value `33`.
|
||||||
|
The third color stands for how many zeros need to be added to the main value.
|
||||||
|
The main value plus the zeros gives us a value in ohms.
|
||||||
|
For the exercise it doesn't matter what ohms really are.
|
||||||
|
For example:
|
||||||
|
|
||||||
|
- orange-orange-black would be 33 and no zeros, which becomes 33 ohms.
|
||||||
|
- orange-orange-red would be 33 and 2 zeros, which becomes 3300 ohms.
|
||||||
|
- orange-orange-orange would be 33 and 3 zeros, which becomes 33000 ohms.
|
||||||
|
|
||||||
|
(If Math is your thing, you may want to think of the zeros as exponents of 10.
|
||||||
|
If Math is not your thing, go with the zeros.
|
||||||
|
It really is the same thing, just in plain English instead of Math lingo.)
|
||||||
|
|
||||||
|
This exercise is about translating the colors into a label:
|
||||||
|
|
||||||
|
> "... ohms"
|
||||||
|
|
||||||
|
So an input of `"orange", "orange", "black"` should return:
|
||||||
|
|
||||||
|
> "33 ohms"
|
||||||
|
|
||||||
|
When we get to larger resistors, a [metric prefix][metric-prefix] is used to indicate a larger magnitude of ohms, such as "kiloohms".
|
||||||
|
That is similar to saying "2 kilometers" instead of "2000 meters", or "2 kilograms" for "2000 grams".
|
||||||
|
|
||||||
|
For example, an input of `"orange", "orange", "orange"` should return:
|
||||||
|
|
||||||
|
> "33 kiloohms"
|
||||||
|
|
||||||
|
[metric-prefix]: https://en.wikipedia.org/wiki/Metric_prefix
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @glennj
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @alirezaghey
|
||||||
|
- @guygastineau
|
||||||
|
- @IsaacG
|
||||||
|
|
||||||
|
### Based on
|
||||||
|
|
||||||
|
Maud de Vries, Erik Schierboom - https://github.com/exercism/problem-specifications/issues/1549
|
||||||
637
bash/resistor-color-trio/bats-extra.bash
Normal file
637
bash/resistor-color-trio/bats-extra.bash
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
# This is the source code for bats-support and bats-assert, concatenated
|
||||||
|
# * https://github.com/bats-core/bats-support
|
||||||
|
# * https://github.com/bats-core/bats-assert
|
||||||
|
#
|
||||||
|
# Comments have been removed to save space. See the git repos for full source code.
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
#
|
||||||
|
# bats-support - Supporting library for Bats test helpers
|
||||||
|
#
|
||||||
|
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
|
||||||
|
#
|
||||||
|
# To the extent possible under law, the author(s) have dedicated all
|
||||||
|
# copyright and related and neighboring rights to this software to the
|
||||||
|
# public domain worldwide. This software is distributed without any
|
||||||
|
# warranty.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the CC0 Public Domain Dedication
|
||||||
|
# along with this software. If not, see
|
||||||
|
# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
(( $# == 0 )) && batslib_err || batslib_err "$@"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_caller() {
|
||||||
|
local -i is_mode_direct=1
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-i|--indirect) is_mode_direct=0; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r func="$1"
|
||||||
|
|
||||||
|
# Check call stack.
|
||||||
|
if (( is_mode_direct )); then
|
||||||
|
[[ $func == "${FUNCNAME[2]}" ]] && return 0
|
||||||
|
else
|
||||||
|
local -i depth
|
||||||
|
for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do
|
||||||
|
[[ $func == "${FUNCNAME[$depth]}" ]] && return 0
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_err() {
|
||||||
|
{ if (( $# > 0 )); then
|
||||||
|
echo "$@"
|
||||||
|
else
|
||||||
|
cat -
|
||||||
|
fi
|
||||||
|
} >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_count_lines() {
|
||||||
|
local -i n_lines=0
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
(( ++n_lines ))
|
||||||
|
done < <(printf '%s' "$1")
|
||||||
|
echo "$n_lines"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_single_line() {
|
||||||
|
for string in "$@"; do
|
||||||
|
(( $(batslib_count_lines "$string") > 1 )) && return 1
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_get_max_single_line_key_width() {
|
||||||
|
local -i max_len=-1
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
local -i key_len="${#1}"
|
||||||
|
batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
echo "$max_len"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single() {
|
||||||
|
local -ir col_width="$1"; shift
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%-*s : %s\n' "$col_width" "$1" "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_multi() {
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )"
|
||||||
|
printf '%s\n' "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single_or_multi() {
|
||||||
|
local -ir width="$1"; shift
|
||||||
|
local -a pairs=( "$@" )
|
||||||
|
|
||||||
|
local -a values=()
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
values+=( "${pairs[$i]}" )
|
||||||
|
done
|
||||||
|
|
||||||
|
if batslib_is_single_line "${values[@]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${pairs[@]}"
|
||||||
|
else
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )"
|
||||||
|
done
|
||||||
|
batslib_print_kv_multi "${pairs[@]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_prefix() {
|
||||||
|
local -r prefix="${1:- }"
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
printf '%s%s\n' "$prefix" "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_mark() {
|
||||||
|
local -r symbol="$1"; shift
|
||||||
|
# Sort line numbers.
|
||||||
|
set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" )
|
||||||
|
|
||||||
|
local line
|
||||||
|
local -i idx=0
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
if (( ${1:--1} == idx )); then
|
||||||
|
printf '%s\n' "${symbol}${line:${#symbol}}"
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
printf '%s\n' "$line"
|
||||||
|
fi
|
||||||
|
(( ++idx ))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_decorate() {
|
||||||
|
echo
|
||||||
|
echo "-- $1 --"
|
||||||
|
cat -
|
||||||
|
echo '--'
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
assert() {
|
||||||
|
if ! "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal() {
|
||||||
|
if [[ $1 != "$2" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$2" \
|
||||||
|
'actual' "$1" \
|
||||||
|
| batslib_decorate 'values do not equal' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_failure() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
(( $# > 0 )) && local -r expected="$1"
|
||||||
|
if (( status == 0 )); then
|
||||||
|
batslib_print_kv_single_or_multi 6 'output' "$output" \
|
||||||
|
| batslib_decorate 'command succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
elif (( $# > 0 )) && (( status != expected )); then
|
||||||
|
{ local -ir width=8
|
||||||
|
batslib_print_kv_single "$width" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" \
|
||||||
|
'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed as expected, but status differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r expected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if ! [[ ${lines[$idx]} =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression does not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single 8 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} =~ $expected ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'regexp' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line matches regular expression' \
|
||||||
|
| fail
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == *"$expected"* ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'substring' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line contains substring' \
|
||||||
|
| fail
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == "$expected" ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'line' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'output does not contain line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_nonempty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_nonempty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local expected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
expected="$(cat -)"
|
||||||
|
else
|
||||||
|
expected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_nonempty )); then
|
||||||
|
if [ -z "$output" ]; then
|
||||||
|
echo 'expected non-empty output, but output was empty' \
|
||||||
|
| batslib_decorate 'no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
elif ! [[ $output =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression does not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$output" \
|
||||||
|
| batslib_decorate 'output differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_success() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
if (( status != 0 )); then
|
||||||
|
{ local -ir width=6
|
||||||
|
batslib_print_kv_single "$width" 'status' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" 'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute() {
|
||||||
|
if "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r unexpected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression should not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single 5 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Line contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
{ local -ar single=( 'regexp' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should match the regular expression' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
{ local -ar single=( 'substring' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should contain substring' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
{ local -ar single=( 'line' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'line should not be in output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_empty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_empty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local unexpected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
unexpected="$(cat -)"
|
||||||
|
else
|
||||||
|
unexpected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_empty )); then
|
||||||
|
if [ -n "$output" ]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output non-empty, but expected no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ $output =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression should not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output equals, but it was expected to differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
104
bash/resistor-color-trio/resistor_color_trio.bats
Normal file
104
bash/resistor-color-trio/resistor_color_trio.bats
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
load bats-extra
|
||||||
|
|
||||||
|
# local version: 1.0.0.2
|
||||||
|
# additional tests for: invalid color, invalid octal number, too many colors
|
||||||
|
|
||||||
|
@test "Orange and orange and black" {
|
||||||
|
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_trio.sh "orange" "orange" "black"
|
||||||
|
assert_success
|
||||||
|
assert_output "33 ohms"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Blue and grey and brown" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_trio.sh "blue" "grey" "brown"
|
||||||
|
assert_success
|
||||||
|
assert_output "680 ohms"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Brown and red and red" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_trio.sh "brown" "red" "red"
|
||||||
|
assert_success
|
||||||
|
assert_output "1200 ohms"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Red and black and red" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_trio.sh "red" "black" "red"
|
||||||
|
assert_success
|
||||||
|
assert_output "2 kiloohms"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Green and brown and orange" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_trio.sh "green" "brown" "orange"
|
||||||
|
assert_success
|
||||||
|
assert_output "51 kiloohms"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Yellow and violet and yellow" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_trio.sh "yellow" "violet" "yellow"
|
||||||
|
assert_success
|
||||||
|
assert_output "470 kiloohms"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Blue and violet and grey" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_trio.sh "blue" "violet" "blue"
|
||||||
|
assert_success
|
||||||
|
assert_output "67 megaohms"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Minimum possible value" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_trio.sh "black" "black" "black"
|
||||||
|
assert_success
|
||||||
|
assert_output "0 ohms"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Maximum possible value" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_trio.sh "white" "white" "white"
|
||||||
|
assert_success
|
||||||
|
assert_output "99 gigaohms"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@test "Invalid first color" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_trio.sh "foo" "white" "white"
|
||||||
|
assert_failure
|
||||||
|
assert_output # there is _some_ output
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Invalid second color" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_trio.sh "white" "bar" "white"
|
||||||
|
assert_failure
|
||||||
|
assert_output # there is _some_ output
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Invalid third color" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_trio.sh "white" "white" "baz"
|
||||||
|
assert_failure
|
||||||
|
assert_output # there is _some_ output
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "First two colors make an invalid octal number" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_trio.sh "black" "grey" "black"
|
||||||
|
assert_success
|
||||||
|
assert_output "8 ohms"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Ignore extra colors" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash resistor_color_trio.sh "blue" "green" "yellow" "orange"
|
||||||
|
assert_success
|
||||||
|
assert_output "650 kiloohms"
|
||||||
|
}
|
||||||
56
bash/resistor-color-trio/resistor_color_trio.sh
Normal file
56
bash/resistor-color-trio/resistor_color_trio.sh
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
color1=$1
|
||||||
|
color2=$2
|
||||||
|
color3=$3
|
||||||
|
|
||||||
|
# Associative array to store color values
|
||||||
|
declare -A colors=(
|
||||||
|
["black"]=0
|
||||||
|
["brown"]=1
|
||||||
|
["red"]=2
|
||||||
|
["orange"]=3
|
||||||
|
["yellow"]=4
|
||||||
|
["green"]=5
|
||||||
|
["blue"]=6
|
||||||
|
["violet"]=7
|
||||||
|
["grey"]=8
|
||||||
|
["white"]=9
|
||||||
|
)
|
||||||
|
|
||||||
|
value="${colors[$color1]}${colors[$color2]}"
|
||||||
|
zeros="${colors[$color3]}"
|
||||||
|
|
||||||
|
# Calculate the value with zeros
|
||||||
|
value_with_zeros="$value"
|
||||||
|
for (( i = 0; i < zeros; i++ )); do
|
||||||
|
value_with_zeros+="0"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if the first digit is zero and omit it if needed
|
||||||
|
if [[ ${value_with_zeros:0:1} == "0" ]]; then
|
||||||
|
value_with_zeros=${value_with_zeros:1}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine the metric prefix
|
||||||
|
giga=1000000000
|
||||||
|
mega=1000000
|
||||||
|
kilo=1000
|
||||||
|
if (( value_with_zeros >= $giga )) && (( value_with_zeros % $giga == 0)); then
|
||||||
|
prefix="gigaohms"
|
||||||
|
value_with_zeros=$(( value_with_zeros / $giga ))
|
||||||
|
elif (( value_with_zeros >= $mega )) && (( value_with_zeros % $mega == 0)); then
|
||||||
|
prefix="megaohms"
|
||||||
|
value_with_zeros=$(( value_with_zeros / $mega ))
|
||||||
|
elif (( value_with_zeros >= $kilo )) && (( value_with_zeros % $kilo == 0)); then
|
||||||
|
prefix="kiloohms"
|
||||||
|
value_with_zeros=$(( value_with_zeros / $kilo ))
|
||||||
|
else
|
||||||
|
prefix="ohms"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${colors[$color1]}" || -z "${colors[$color2]}" || -z "${colors[$color3]}" ]]; then
|
||||||
|
return
|
||||||
|
else
|
||||||
|
echo "$value_with_zeros $prefix"
|
||||||
|
fi
|
||||||
25
bash/secret-handshake/.exercism/config.json
Normal file
25
bash/secret-handshake/.exercism/config.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"glennj"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"bkhl",
|
||||||
|
"guygastineau",
|
||||||
|
"IsaacG",
|
||||||
|
"kotp"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"secret_handshake.sh"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"secret_handshake.bats"
|
||||||
|
],
|
||||||
|
"example": [
|
||||||
|
".meta/example.sh"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"blurb": "Given a decimal number, convert it to the appropriate sequence of events for a secret handshake.",
|
||||||
|
"source": "Bert, in Mary Poppins",
|
||||||
|
"source_url": "https://www.imdb.com/title/tt0058331/quotes/qt0437047"
|
||||||
|
}
|
||||||
1
bash/secret-handshake/.exercism/metadata.json
Normal file
1
bash/secret-handshake/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"bash","exercise":"secret-handshake","id":"4b4be587ec1c4d4cbbaea6f7ad0a57f7","url":"https://exercism.org/tracks/bash/exercises/secret-handshake","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||||
105
bash/secret-handshake/HELP.md
Normal file
105
bash/secret-handshake/HELP.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
Each exercise contains a test file.
|
||||||
|
Run the tests using the `bats` program.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats hello_world.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
`bats` will need to be installed.
|
||||||
|
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
|
||||||
|
|
||||||
|
[tests]: https://exercism.org/docs/tracks/bash/tests
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
[bats-assert]: https://github.com/bats-core/bats-assert
|
||||||
|
|
||||||
|
## 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 next annotation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
[[ $BATS_RUN_SKIPPED == true ]] || skip
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overriding skips
|
||||||
|
|
||||||
|
To run all tests, including the ones with `skip` annotations, you can run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BATS_RUN_SKIPPED=true bats exercise_name.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
It can be convenient to use a wrapper function to save on typing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats() {
|
||||||
|
BATS_RUN_SKIPPED=true command bats *.bats
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run tests with just:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit secret_handshake.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
|
||||||
|
- [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.
|
||||||
|
|
||||||
|
Check your code for syntax errors: paste your code into
|
||||||
|
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
|
||||||
|
|
||||||
|
Stack Overflow will be your first stop for bash questions.
|
||||||
|
|
||||||
|
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
|
||||||
|
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
|
||||||
|
* the "Books and Resources" section is particularly useful.
|
||||||
|
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
|
||||||
|
|
||||||
|
## External utilities
|
||||||
|
|
||||||
|
`bash` is a language to write "scripts" -- programs that can call
|
||||||
|
external tools, such as
|
||||||
|
[`sed`](https://www.gnu.org/software/sed/),
|
||||||
|
[`awk`](https://www.gnu.org/software/gawk/),
|
||||||
|
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
|
||||||
|
and even programs written in other programming languages,
|
||||||
|
like [`Python`](https://www.python.org/).
|
||||||
|
This track does not restrict the usage of these utilities, and as long
|
||||||
|
as your solution is portable between systems and does not require
|
||||||
|
installation of third party applications, feel free to use them to solve
|
||||||
|
the exercise.
|
||||||
|
|
||||||
|
For an extra challenge, if you would like to have a better understanding of
|
||||||
|
the language, try to re-implement the solution in pure bash, without using
|
||||||
|
any external tools. There are some types of problems that bash cannot solve,
|
||||||
|
such as floating point arithmetic and manipulating dates: for those, you
|
||||||
|
must call out to an external tool.
|
||||||
78
bash/secret-handshake/README.md
Normal file
78
bash/secret-handshake/README.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# Secret Handshake
|
||||||
|
|
||||||
|
Welcome to Secret Handshake on Exercism's Bash Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
You are starting a secret coding club with some friends and friends-of-friends.
|
||||||
|
Not everyone knows each other, so you and your friends have decided to create a secret handshake that you can use to recognize that someone is a member.
|
||||||
|
You don't want anyone who isn't in the know to be able to crack the code.
|
||||||
|
|
||||||
|
You've designed the code so that one person says a number between 1 and 31, and the other person turns it into a series of actions.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Your task is to convert a number between 1 and 31 to a sequence of actions in the secret handshake.
|
||||||
|
|
||||||
|
The sequence of actions is chosen by looking at the rightmost five digits of the number once it's been converted to binary.
|
||||||
|
Start at the right-most digit and move left.
|
||||||
|
|
||||||
|
The actions for each number place are:
|
||||||
|
|
||||||
|
```plaintext
|
||||||
|
00001 = wink
|
||||||
|
00010 = double blink
|
||||||
|
00100 = close your eyes
|
||||||
|
01000 = jump
|
||||||
|
10000 = Reverse the order of the operations in the secret handshake.
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's use the number `9` as an example:
|
||||||
|
|
||||||
|
- 9 in binary is `1001`.
|
||||||
|
- The digit that is farthest to the right is 1, so the first action is `wink`.
|
||||||
|
- Going left, the next digit is 0, so there is no double-blink.
|
||||||
|
- Going left again, the next digit is 0, so you leave your eyes open.
|
||||||
|
- Going left again, the next digit is 1, so you jump.
|
||||||
|
|
||||||
|
That was the last digit, so the final code is:
|
||||||
|
|
||||||
|
```plaintext
|
||||||
|
wink, jump
|
||||||
|
```
|
||||||
|
|
||||||
|
Given the number 26, which is `11010` in binary, we get the following actions:
|
||||||
|
|
||||||
|
- double blink
|
||||||
|
- jump
|
||||||
|
- reverse actions
|
||||||
|
|
||||||
|
The secret handshake for 26 is therefore:
|
||||||
|
|
||||||
|
```plaintext
|
||||||
|
jump, double blink
|
||||||
|
```
|
||||||
|
|
||||||
|
~~~~exercism/note
|
||||||
|
If you aren't sure what binary is or how it works, check out [this binary tutorial][intro-to-binary].
|
||||||
|
|
||||||
|
[intro-to-binary]: https://medium.com/basecs/bits-bytes-building-with-binary-13cb4289aafa
|
||||||
|
~~~~
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @glennj
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @bkhl
|
||||||
|
- @guygastineau
|
||||||
|
- @IsaacG
|
||||||
|
- @kotp
|
||||||
|
|
||||||
|
### Based on
|
||||||
|
|
||||||
|
Bert, in Mary Poppins - https://www.imdb.com/title/tt0058331/quotes/qt0437047
|
||||||
637
bash/secret-handshake/bats-extra.bash
Normal file
637
bash/secret-handshake/bats-extra.bash
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
# This is the source code for bats-support and bats-assert, concatenated
|
||||||
|
# * https://github.com/bats-core/bats-support
|
||||||
|
# * https://github.com/bats-core/bats-assert
|
||||||
|
#
|
||||||
|
# Comments have been removed to save space. See the git repos for full source code.
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
#
|
||||||
|
# bats-support - Supporting library for Bats test helpers
|
||||||
|
#
|
||||||
|
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
|
||||||
|
#
|
||||||
|
# To the extent possible under law, the author(s) have dedicated all
|
||||||
|
# copyright and related and neighboring rights to this software to the
|
||||||
|
# public domain worldwide. This software is distributed without any
|
||||||
|
# warranty.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the CC0 Public Domain Dedication
|
||||||
|
# along with this software. If not, see
|
||||||
|
# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
(( $# == 0 )) && batslib_err || batslib_err "$@"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_caller() {
|
||||||
|
local -i is_mode_direct=1
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-i|--indirect) is_mode_direct=0; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r func="$1"
|
||||||
|
|
||||||
|
# Check call stack.
|
||||||
|
if (( is_mode_direct )); then
|
||||||
|
[[ $func == "${FUNCNAME[2]}" ]] && return 0
|
||||||
|
else
|
||||||
|
local -i depth
|
||||||
|
for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do
|
||||||
|
[[ $func == "${FUNCNAME[$depth]}" ]] && return 0
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_err() {
|
||||||
|
{ if (( $# > 0 )); then
|
||||||
|
echo "$@"
|
||||||
|
else
|
||||||
|
cat -
|
||||||
|
fi
|
||||||
|
} >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_count_lines() {
|
||||||
|
local -i n_lines=0
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
(( ++n_lines ))
|
||||||
|
done < <(printf '%s' "$1")
|
||||||
|
echo "$n_lines"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_single_line() {
|
||||||
|
for string in "$@"; do
|
||||||
|
(( $(batslib_count_lines "$string") > 1 )) && return 1
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_get_max_single_line_key_width() {
|
||||||
|
local -i max_len=-1
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
local -i key_len="${#1}"
|
||||||
|
batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
echo "$max_len"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single() {
|
||||||
|
local -ir col_width="$1"; shift
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%-*s : %s\n' "$col_width" "$1" "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_multi() {
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )"
|
||||||
|
printf '%s\n' "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single_or_multi() {
|
||||||
|
local -ir width="$1"; shift
|
||||||
|
local -a pairs=( "$@" )
|
||||||
|
|
||||||
|
local -a values=()
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
values+=( "${pairs[$i]}" )
|
||||||
|
done
|
||||||
|
|
||||||
|
if batslib_is_single_line "${values[@]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${pairs[@]}"
|
||||||
|
else
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )"
|
||||||
|
done
|
||||||
|
batslib_print_kv_multi "${pairs[@]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_prefix() {
|
||||||
|
local -r prefix="${1:- }"
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
printf '%s%s\n' "$prefix" "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_mark() {
|
||||||
|
local -r symbol="$1"; shift
|
||||||
|
# Sort line numbers.
|
||||||
|
set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" )
|
||||||
|
|
||||||
|
local line
|
||||||
|
local -i idx=0
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
if (( ${1:--1} == idx )); then
|
||||||
|
printf '%s\n' "${symbol}${line:${#symbol}}"
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
printf '%s\n' "$line"
|
||||||
|
fi
|
||||||
|
(( ++idx ))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_decorate() {
|
||||||
|
echo
|
||||||
|
echo "-- $1 --"
|
||||||
|
cat -
|
||||||
|
echo '--'
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
assert() {
|
||||||
|
if ! "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal() {
|
||||||
|
if [[ $1 != "$2" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$2" \
|
||||||
|
'actual' "$1" \
|
||||||
|
| batslib_decorate 'values do not equal' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_failure() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
(( $# > 0 )) && local -r expected="$1"
|
||||||
|
if (( status == 0 )); then
|
||||||
|
batslib_print_kv_single_or_multi 6 'output' "$output" \
|
||||||
|
| batslib_decorate 'command succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
elif (( $# > 0 )) && (( status != expected )); then
|
||||||
|
{ local -ir width=8
|
||||||
|
batslib_print_kv_single "$width" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" \
|
||||||
|
'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed as expected, but status differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r expected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if ! [[ ${lines[$idx]} =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression does not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single 8 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} =~ $expected ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'regexp' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line matches regular expression' \
|
||||||
|
| fail
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == *"$expected"* ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'substring' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line contains substring' \
|
||||||
|
| fail
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == "$expected" ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'line' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'output does not contain line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_nonempty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_nonempty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local expected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
expected="$(cat -)"
|
||||||
|
else
|
||||||
|
expected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_nonempty )); then
|
||||||
|
if [ -z "$output" ]; then
|
||||||
|
echo 'expected non-empty output, but output was empty' \
|
||||||
|
| batslib_decorate 'no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
elif ! [[ $output =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression does not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$output" \
|
||||||
|
| batslib_decorate 'output differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_success() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
if (( status != 0 )); then
|
||||||
|
{ local -ir width=6
|
||||||
|
batslib_print_kv_single "$width" 'status' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" 'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute() {
|
||||||
|
if "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r unexpected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression should not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single 5 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Line contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
{ local -ar single=( 'regexp' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should match the regular expression' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
{ local -ar single=( 'substring' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should contain substring' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
{ local -ar single=( 'line' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'line should not be in output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_empty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_empty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local unexpected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
unexpected="$(cat -)"
|
||||||
|
else
|
||||||
|
unexpected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_empty )); then
|
||||||
|
if [ -n "$output" ]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output non-empty, but expected no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ $output =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression should not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output equals, but it was expected to differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
81
bash/secret-handshake/secret_handshake.bats
Normal file
81
bash/secret-handshake/secret_handshake.bats
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
load bats-extra
|
||||||
|
|
||||||
|
# local version: 1.2.0.0
|
||||||
|
|
||||||
|
@test "wink for 1" {
|
||||||
|
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash secret_handshake.sh 1
|
||||||
|
assert_success
|
||||||
|
assert_output "wink"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "double blink for 10" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash secret_handshake.sh 2
|
||||||
|
assert_success
|
||||||
|
assert_output "double blink"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "close your eyes for 100" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash secret_handshake.sh 4
|
||||||
|
assert_success
|
||||||
|
assert_output "close your eyes"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "jump for 1000" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash secret_handshake.sh 8
|
||||||
|
assert_success
|
||||||
|
assert_output "jump"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "combine two actions" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash secret_handshake.sh 3
|
||||||
|
assert_success
|
||||||
|
assert_output "wink,double blink"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "all possible actions" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash secret_handshake.sh 15
|
||||||
|
assert_success
|
||||||
|
assert_output "wink,double blink,close your eyes,jump"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "do nothing for zero" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash secret_handshake.sh 0
|
||||||
|
assert_success
|
||||||
|
assert_output ""
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "reversing no actions still gives no actions" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash secret_handshake.sh 16
|
||||||
|
assert_success
|
||||||
|
assert_output ""
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "reversing one action gives the same action" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash secret_handshake.sh 24
|
||||||
|
assert_success
|
||||||
|
assert_output "jump"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "reverse two actions" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash secret_handshake.sh 19
|
||||||
|
assert_success
|
||||||
|
assert_output "double blink,wink"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "reverse all possible actions" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
run bash secret_handshake.sh 31
|
||||||
|
assert_success
|
||||||
|
assert_output "jump,close your eyes,double blink,wink"
|
||||||
|
}
|
||||||
40
bash/secret-handshake/secret_handshake.sh
Normal file
40
bash/secret-handshake/secret_handshake.sh
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Read the number from the user
|
||||||
|
number=$1
|
||||||
|
|
||||||
|
binary=""
|
||||||
|
while ((number > 0)); do
|
||||||
|
remainder=$((number % 2))
|
||||||
|
binary="$remainder$binary"
|
||||||
|
number=$((number / 2))
|
||||||
|
done
|
||||||
|
binary=$(printf "%05d" "$binary")
|
||||||
|
|
||||||
|
# Initialize an empty array to store the actions
|
||||||
|
actions=()
|
||||||
|
|
||||||
|
# Check the rightmost five digits of the binary number
|
||||||
|
# and add corresponding actions to the array
|
||||||
|
if [[ ${binary:4:1} == "1" ]]; then
|
||||||
|
actions+=("wink")
|
||||||
|
fi
|
||||||
|
if [[ ${binary:3:1} == "1" ]]; then
|
||||||
|
actions+=("double blink")
|
||||||
|
fi
|
||||||
|
if [[ ${binary:2:1} == "1" ]]; then
|
||||||
|
actions+=("close your eyes")
|
||||||
|
fi
|
||||||
|
if [[ ${binary:1:1} == "1" ]]; then
|
||||||
|
actions+=("jump")
|
||||||
|
fi
|
||||||
|
if [[ ${binary:0:1} == "1" ]]; then
|
||||||
|
# reverse actions
|
||||||
|
for ((i = ${#actions[@]} - 1; i >= 0; i--)); do
|
||||||
|
rev+=("${actions[i]}")
|
||||||
|
done
|
||||||
|
actions=("${rev[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add a comma between actions
|
||||||
|
echo "$(IFS=, ; echo "${actions[*]}")"
|
||||||
25
bash/sieve/.exercism/config.json
Normal file
25
bash/sieve/.exercism/config.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"glennj"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"bkhl",
|
||||||
|
"guygastineau",
|
||||||
|
"IsaacG",
|
||||||
|
"kotp"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"sieve.sh"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"sieve.bats"
|
||||||
|
],
|
||||||
|
"example": [
|
||||||
|
".meta/example.sh"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"blurb": "Use the Sieve of Eratosthenes to find all the primes from 2 up to a given number.",
|
||||||
|
"source": "Sieve of Eratosthenes at Wikipedia",
|
||||||
|
"source_url": "https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes"
|
||||||
|
}
|
||||||
1
bash/sieve/.exercism/metadata.json
Normal file
1
bash/sieve/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"bash","exercise":"sieve","id":"01268935f0604c5eb192a7502640bad6","url":"https://exercism.org/tracks/bash/exercises/sieve","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||||
105
bash/sieve/HELP.md
Normal file
105
bash/sieve/HELP.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
Each exercise contains a test file.
|
||||||
|
Run the tests using the `bats` program.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats hello_world.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
`bats` will need to be installed.
|
||||||
|
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
|
||||||
|
|
||||||
|
[tests]: https://exercism.org/docs/tracks/bash/tests
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
[bats-assert]: https://github.com/bats-core/bats-assert
|
||||||
|
|
||||||
|
## 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 next annotation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
[[ $BATS_RUN_SKIPPED == true ]] || skip
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overriding skips
|
||||||
|
|
||||||
|
To run all tests, including the ones with `skip` annotations, you can run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BATS_RUN_SKIPPED=true bats exercise_name.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
It can be convenient to use a wrapper function to save on typing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats() {
|
||||||
|
BATS_RUN_SKIPPED=true command bats *.bats
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run tests with just:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit sieve.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
|
||||||
|
- [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.
|
||||||
|
|
||||||
|
Check your code for syntax errors: paste your code into
|
||||||
|
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
|
||||||
|
|
||||||
|
Stack Overflow will be your first stop for bash questions.
|
||||||
|
|
||||||
|
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
|
||||||
|
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
|
||||||
|
* the "Books and Resources" section is particularly useful.
|
||||||
|
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
|
||||||
|
|
||||||
|
## External utilities
|
||||||
|
|
||||||
|
`bash` is a language to write "scripts" -- programs that can call
|
||||||
|
external tools, such as
|
||||||
|
[`sed`](https://www.gnu.org/software/sed/),
|
||||||
|
[`awk`](https://www.gnu.org/software/gawk/),
|
||||||
|
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
|
||||||
|
and even programs written in other programming languages,
|
||||||
|
like [`Python`](https://www.python.org/).
|
||||||
|
This track does not restrict the usage of these utilities, and as long
|
||||||
|
as your solution is portable between systems and does not require
|
||||||
|
installation of third party applications, feel free to use them to solve
|
||||||
|
the exercise.
|
||||||
|
|
||||||
|
For an extra challenge, if you would like to have a better understanding of
|
||||||
|
the language, try to re-implement the solution in pure bash, without using
|
||||||
|
any external tools. There are some types of problems that bash cannot solve,
|
||||||
|
such as floating point arithmetic and manipulating dates: for those, you
|
||||||
|
must call out to an external tool.
|
||||||
58
bash/sieve/README.md
Normal file
58
bash/sieve/README.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Sieve
|
||||||
|
|
||||||
|
Welcome to Sieve on Exercism's Bash Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
You bought a big box of random computer parts at a garage sale.
|
||||||
|
You've started putting the parts together to build custom computers.
|
||||||
|
|
||||||
|
You want to test the performance of different combinations of parts, and decide to create your own benchmarking program to see how your computers compare.
|
||||||
|
You choose the famous "Sieve of Eratosthenes" algorithm, an ancient algorithm, but one that should push your computers to the limits.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find prime numbers.
|
||||||
|
|
||||||
|
A prime number is a number that is only divisible by 1 and itself.
|
||||||
|
For example, 2, 3, 5, 7, 11, and 13 are prime numbers.
|
||||||
|
|
||||||
|
The Sieve of Eratosthenes is an ancient algorithm that works by taking a list of numbers and crossing out all the numbers that aren't prime.
|
||||||
|
|
||||||
|
A number that is **not** prime is called a "composite number".
|
||||||
|
|
||||||
|
To use the Sieve of Eratosthenes, you first create a list of all the numbers between 2 and your given number.
|
||||||
|
Then you repeat the following steps:
|
||||||
|
|
||||||
|
1. Find the next unmarked number in your list. This is a prime number.
|
||||||
|
2. Mark all the multiples of that prime number as composite (not prime).
|
||||||
|
|
||||||
|
You keep repeating these steps until you've gone through every number in your list.
|
||||||
|
At the end, all the unmarked numbers are prime.
|
||||||
|
|
||||||
|
~~~~exercism/note
|
||||||
|
[Wikipedia's Sieve of Eratosthenes article][eratosthenes] has a useful graphic that explains the algorithm.
|
||||||
|
|
||||||
|
The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes.
|
||||||
|
A good first test is to check that you do not use division or remainder operations.
|
||||||
|
|
||||||
|
[eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
|
||||||
|
~~~~
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @glennj
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @bkhl
|
||||||
|
- @guygastineau
|
||||||
|
- @IsaacG
|
||||||
|
- @kotp
|
||||||
|
|
||||||
|
### Based on
|
||||||
|
|
||||||
|
Sieve of Eratosthenes at Wikipedia - https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
|
||||||
637
bash/sieve/bats-extra.bash
Normal file
637
bash/sieve/bats-extra.bash
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
# This is the source code for bats-support and bats-assert, concatenated
|
||||||
|
# * https://github.com/bats-core/bats-support
|
||||||
|
# * https://github.com/bats-core/bats-assert
|
||||||
|
#
|
||||||
|
# Comments have been removed to save space. See the git repos for full source code.
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
#
|
||||||
|
# bats-support - Supporting library for Bats test helpers
|
||||||
|
#
|
||||||
|
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
|
||||||
|
#
|
||||||
|
# To the extent possible under law, the author(s) have dedicated all
|
||||||
|
# copyright and related and neighboring rights to this software to the
|
||||||
|
# public domain worldwide. This software is distributed without any
|
||||||
|
# warranty.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the CC0 Public Domain Dedication
|
||||||
|
# along with this software. If not, see
|
||||||
|
# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
(( $# == 0 )) && batslib_err || batslib_err "$@"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_caller() {
|
||||||
|
local -i is_mode_direct=1
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-i|--indirect) is_mode_direct=0; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r func="$1"
|
||||||
|
|
||||||
|
# Check call stack.
|
||||||
|
if (( is_mode_direct )); then
|
||||||
|
[[ $func == "${FUNCNAME[2]}" ]] && return 0
|
||||||
|
else
|
||||||
|
local -i depth
|
||||||
|
for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do
|
||||||
|
[[ $func == "${FUNCNAME[$depth]}" ]] && return 0
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_err() {
|
||||||
|
{ if (( $# > 0 )); then
|
||||||
|
echo "$@"
|
||||||
|
else
|
||||||
|
cat -
|
||||||
|
fi
|
||||||
|
} >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_count_lines() {
|
||||||
|
local -i n_lines=0
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
(( ++n_lines ))
|
||||||
|
done < <(printf '%s' "$1")
|
||||||
|
echo "$n_lines"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_single_line() {
|
||||||
|
for string in "$@"; do
|
||||||
|
(( $(batslib_count_lines "$string") > 1 )) && return 1
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_get_max_single_line_key_width() {
|
||||||
|
local -i max_len=-1
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
local -i key_len="${#1}"
|
||||||
|
batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
echo "$max_len"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single() {
|
||||||
|
local -ir col_width="$1"; shift
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%-*s : %s\n' "$col_width" "$1" "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_multi() {
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )"
|
||||||
|
printf '%s\n' "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single_or_multi() {
|
||||||
|
local -ir width="$1"; shift
|
||||||
|
local -a pairs=( "$@" )
|
||||||
|
|
||||||
|
local -a values=()
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
values+=( "${pairs[$i]}" )
|
||||||
|
done
|
||||||
|
|
||||||
|
if batslib_is_single_line "${values[@]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${pairs[@]}"
|
||||||
|
else
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )"
|
||||||
|
done
|
||||||
|
batslib_print_kv_multi "${pairs[@]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_prefix() {
|
||||||
|
local -r prefix="${1:- }"
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
printf '%s%s\n' "$prefix" "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_mark() {
|
||||||
|
local -r symbol="$1"; shift
|
||||||
|
# Sort line numbers.
|
||||||
|
set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" )
|
||||||
|
|
||||||
|
local line
|
||||||
|
local -i idx=0
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
if (( ${1:--1} == idx )); then
|
||||||
|
printf '%s\n' "${symbol}${line:${#symbol}}"
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
printf '%s\n' "$line"
|
||||||
|
fi
|
||||||
|
(( ++idx ))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_decorate() {
|
||||||
|
echo
|
||||||
|
echo "-- $1 --"
|
||||||
|
cat -
|
||||||
|
echo '--'
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
assert() {
|
||||||
|
if ! "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal() {
|
||||||
|
if [[ $1 != "$2" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$2" \
|
||||||
|
'actual' "$1" \
|
||||||
|
| batslib_decorate 'values do not equal' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_failure() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
(( $# > 0 )) && local -r expected="$1"
|
||||||
|
if (( status == 0 )); then
|
||||||
|
batslib_print_kv_single_or_multi 6 'output' "$output" \
|
||||||
|
| batslib_decorate 'command succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
elif (( $# > 0 )) && (( status != expected )); then
|
||||||
|
{ local -ir width=8
|
||||||
|
batslib_print_kv_single "$width" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" \
|
||||||
|
'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed as expected, but status differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r expected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if ! [[ ${lines[$idx]} =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression does not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single 8 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} =~ $expected ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'regexp' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line matches regular expression' \
|
||||||
|
| fail
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == *"$expected"* ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'substring' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line contains substring' \
|
||||||
|
| fail
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == "$expected" ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'line' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'output does not contain line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_nonempty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_nonempty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local expected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
expected="$(cat -)"
|
||||||
|
else
|
||||||
|
expected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_nonempty )); then
|
||||||
|
if [ -z "$output" ]; then
|
||||||
|
echo 'expected non-empty output, but output was empty' \
|
||||||
|
| batslib_decorate 'no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
elif ! [[ $output =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression does not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$output" \
|
||||||
|
| batslib_decorate 'output differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_success() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
if (( status != 0 )); then
|
||||||
|
{ local -ir width=6
|
||||||
|
batslib_print_kv_single "$width" 'status' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" 'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute() {
|
||||||
|
if "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r unexpected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression should not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single 5 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Line contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
{ local -ar single=( 'regexp' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should match the regular expression' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
{ local -ar single=( 'substring' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should contain substring' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
{ local -ar single=( 'line' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'line should not be in output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_empty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_empty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local unexpected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
unexpected="$(cat -)"
|
||||||
|
else
|
||||||
|
unexpected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_empty )); then
|
||||||
|
if [ -n "$output" ]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output non-empty, but expected no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ $output =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression should not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output equals, but it was expected to differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
44
bash/sieve/sieve.bats
Normal file
44
bash/sieve/sieve.bats
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
load bats-extra
|
||||||
|
|
||||||
|
# local version: 1.1.0.0
|
||||||
|
|
||||||
|
@test "no primes under two" {
|
||||||
|
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected=""
|
||||||
|
run bash sieve.sh 1
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "find first prime" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="2"
|
||||||
|
run bash sieve.sh 2
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "find primes up to 10" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="2 3 5 7"
|
||||||
|
run bash sieve.sh 10
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "limit is prime" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="2 3 5 7 11 13"
|
||||||
|
run bash sieve.sh 13
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "find primes up to 1000" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
expected="2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593 599 601 607 613 617 619 631 641 643 647 653 659 661 673 677 683 691 701 709 719 727 733 739 743 751 757 761 769 773 787 797 809 811 821 823 827 829 839 853 857 859 863 877 881 883 887 907 911 919 929 937 941 947 953 967 971 977 983 991 997"
|
||||||
|
run bash sieve.sh 1000
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
38
bash/sieve/sieve.sh
Normal file
38
bash/sieve/sieve.sh
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [ $# -ne 1 ]; then
|
||||||
|
echo "Usage: $0 <upper_limit>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
upper_limit=$1
|
||||||
|
|
||||||
|
# Create an array to store whether each number is prime or not
|
||||||
|
declare -a is_prime
|
||||||
|
|
||||||
|
# Initialize the array to assume all numbers are prime initially
|
||||||
|
for ((i = 2; i <= upper_limit; i++)); do
|
||||||
|
is_prime[$i]=true
|
||||||
|
done
|
||||||
|
|
||||||
|
# Apply the Sieve of Eratosthenes algorithm
|
||||||
|
for ((i = 2; i * i <= upper_limit; i++)); do
|
||||||
|
if [ "${is_prime[$i]}" = true ]; then
|
||||||
|
for ((j = i * i; j <= upper_limit; j += i)); do
|
||||||
|
is_prime[$j]=false
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Print the prime numbers in a single line with space separation
|
||||||
|
for ((i = 2; i <= upper_limit; i++)); do
|
||||||
|
if [ "${is_prime[$i]}" = true ]; then
|
||||||
|
# if number is 2, print it without space before
|
||||||
|
if [ $i -eq 2 ]; then
|
||||||
|
echo -n "$i"
|
||||||
|
# print number after a space
|
||||||
|
else
|
||||||
|
echo -n " $i"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
21
bash/tournament/.exercism/config.json
Normal file
21
bash/tournament/.exercism/config.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"glennj"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"guygastineau",
|
||||||
|
"IsaacG"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"tournament.sh"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"tournament.bats"
|
||||||
|
],
|
||||||
|
"example": [
|
||||||
|
".meta/example.sh"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"blurb": "Tally the results of a small football competition."
|
||||||
|
}
|
||||||
1
bash/tournament/.exercism/metadata.json
Normal file
1
bash/tournament/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"bash","exercise":"tournament","id":"d43f98a967f14693bc86e6eb2479c653","url":"https://exercism.org/tracks/bash/exercises/tournament","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||||
106
bash/tournament/HELP.md
Normal file
106
bash/tournament/HELP.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
Each exercise contains a test file.
|
||||||
|
Run the tests using the `bats` program.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats hello_world.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
`bats` will need to be installed.
|
||||||
|
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
|
||||||
|
|
||||||
|
[tests]: https://exercism.org/docs/tracks/bash/tests
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
[bats-assert]: https://github.com/bats-core/bats-assert
|
||||||
|
|
||||||
|
## 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 next annotation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
[[ $BATS_RUN_SKIPPED == true ]] || skip
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overriding skips
|
||||||
|
|
||||||
|
To run all tests, including the ones with `skip` annotations, you can run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BATS_RUN_SKIPPED=true bats exercise_name.bats
|
||||||
|
```
|
||||||
|
|
||||||
|
It can be convenient to use a wrapper function to save on typing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats() {
|
||||||
|
BATS_RUN_SKIPPED=true command bats *.bats
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run tests with just:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bats
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit tournament.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
|
||||||
|
- The [Bash track's programming category on the forum](https://forum.exercism.org/c/programming/bash)
|
||||||
|
- [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.
|
||||||
|
|
||||||
|
Check your code for syntax errors: paste your code into
|
||||||
|
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
|
||||||
|
|
||||||
|
Stack Overflow will be your first stop for bash questions.
|
||||||
|
|
||||||
|
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
|
||||||
|
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
|
||||||
|
* the "Books and Resources" section is particularly useful.
|
||||||
|
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
|
||||||
|
|
||||||
|
## External utilities
|
||||||
|
|
||||||
|
`bash` is a language to write "scripts" -- programs that can call
|
||||||
|
external tools, such as
|
||||||
|
[`sed`](https://www.gnu.org/software/sed/),
|
||||||
|
[`awk`](https://www.gnu.org/software/gawk/),
|
||||||
|
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
|
||||||
|
and even programs written in other programming languages,
|
||||||
|
like [`Python`](https://www.python.org/).
|
||||||
|
This track does not restrict the usage of these utilities, and as long
|
||||||
|
as your solution is portable between systems and does not require
|
||||||
|
installation of third party applications, feel free to use them to solve
|
||||||
|
the exercise.
|
||||||
|
|
||||||
|
For an extra challenge, if you would like to have a better understanding of
|
||||||
|
the language, try to re-implement the solution in pure bash, without using
|
||||||
|
any external tools. There are some types of problems that bash cannot solve,
|
||||||
|
such as floating point arithmetic and manipulating dates: for those, you
|
||||||
|
must call out to an external tool.
|
||||||
82
bash/tournament/README.md
Normal file
82
bash/tournament/README.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Tournament
|
||||||
|
|
||||||
|
Welcome to Tournament on Exercism's Bash Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Tally the results of a small football competition.
|
||||||
|
|
||||||
|
Based on an input file containing which team played against which and what the outcome was, create a file with a table like this:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Team | MP | W | D | L | P
|
||||||
|
Devastating Donkeys | 3 | 2 | 1 | 0 | 7
|
||||||
|
Allegoric Alaskans | 3 | 2 | 0 | 1 | 6
|
||||||
|
Blithering Badgers | 3 | 1 | 0 | 2 | 3
|
||||||
|
Courageous Californians | 3 | 0 | 1 | 2 | 1
|
||||||
|
```
|
||||||
|
|
||||||
|
What do those abbreviations mean?
|
||||||
|
|
||||||
|
- MP: Matches Played
|
||||||
|
- W: Matches Won
|
||||||
|
- D: Matches Drawn (Tied)
|
||||||
|
- L: Matches Lost
|
||||||
|
- P: Points
|
||||||
|
|
||||||
|
A win earns a team 3 points.
|
||||||
|
A draw earns 1.
|
||||||
|
A loss earns 0.
|
||||||
|
|
||||||
|
The outcome is ordered by points, descending.
|
||||||
|
In case of a tie, teams are ordered alphabetically.
|
||||||
|
|
||||||
|
## Input
|
||||||
|
|
||||||
|
Your tallying program will receive input that looks like:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Allegoric Alaskans;Blithering Badgers;win
|
||||||
|
Devastating Donkeys;Courageous Californians;draw
|
||||||
|
Devastating Donkeys;Allegoric Alaskans;win
|
||||||
|
Courageous Californians;Blithering Badgers;loss
|
||||||
|
Blithering Badgers;Devastating Donkeys;loss
|
||||||
|
Allegoric Alaskans;Courageous Californians;win
|
||||||
|
```
|
||||||
|
|
||||||
|
The result of the match refers to the first team listed.
|
||||||
|
So this line:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Allegoric Alaskans;Blithering Badgers;win
|
||||||
|
```
|
||||||
|
|
||||||
|
means that the Allegoric Alaskans beat the Blithering Badgers.
|
||||||
|
|
||||||
|
This line:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Courageous Californians;Blithering Badgers;loss
|
||||||
|
```
|
||||||
|
|
||||||
|
means that the Blithering Badgers beat the Courageous Californians.
|
||||||
|
|
||||||
|
And this line:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Devastating Donkeys;Courageous Californians;draw
|
||||||
|
```
|
||||||
|
|
||||||
|
means that the Devastating Donkeys and Courageous Californians tied.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @glennj
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @guygastineau
|
||||||
|
- @IsaacG
|
||||||
637
bash/tournament/bats-extra.bash
Normal file
637
bash/tournament/bats-extra.bash
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
# This is the source code for bats-support and bats-assert, concatenated
|
||||||
|
# * https://github.com/bats-core/bats-support
|
||||||
|
# * https://github.com/bats-core/bats-assert
|
||||||
|
#
|
||||||
|
# Comments have been removed to save space. See the git repos for full source code.
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
#
|
||||||
|
# bats-support - Supporting library for Bats test helpers
|
||||||
|
#
|
||||||
|
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
|
||||||
|
#
|
||||||
|
# To the extent possible under law, the author(s) have dedicated all
|
||||||
|
# copyright and related and neighboring rights to this software to the
|
||||||
|
# public domain worldwide. This software is distributed without any
|
||||||
|
# warranty.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the CC0 Public Domain Dedication
|
||||||
|
# along with this software. If not, see
|
||||||
|
# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
(( $# == 0 )) && batslib_err || batslib_err "$@"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_caller() {
|
||||||
|
local -i is_mode_direct=1
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-i|--indirect) is_mode_direct=0; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r func="$1"
|
||||||
|
|
||||||
|
# Check call stack.
|
||||||
|
if (( is_mode_direct )); then
|
||||||
|
[[ $func == "${FUNCNAME[2]}" ]] && return 0
|
||||||
|
else
|
||||||
|
local -i depth
|
||||||
|
for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do
|
||||||
|
[[ $func == "${FUNCNAME[$depth]}" ]] && return 0
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_err() {
|
||||||
|
{ if (( $# > 0 )); then
|
||||||
|
echo "$@"
|
||||||
|
else
|
||||||
|
cat -
|
||||||
|
fi
|
||||||
|
} >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_count_lines() {
|
||||||
|
local -i n_lines=0
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
(( ++n_lines ))
|
||||||
|
done < <(printf '%s' "$1")
|
||||||
|
echo "$n_lines"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_is_single_line() {
|
||||||
|
for string in "$@"; do
|
||||||
|
(( $(batslib_count_lines "$string") > 1 )) && return 1
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_get_max_single_line_key_width() {
|
||||||
|
local -i max_len=-1
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
local -i key_len="${#1}"
|
||||||
|
batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
echo "$max_len"
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single() {
|
||||||
|
local -ir col_width="$1"; shift
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%-*s : %s\n' "$col_width" "$1" "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_multi() {
|
||||||
|
while (( $# != 0 )); do
|
||||||
|
printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )"
|
||||||
|
printf '%s\n' "$2"
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_print_kv_single_or_multi() {
|
||||||
|
local -ir width="$1"; shift
|
||||||
|
local -a pairs=( "$@" )
|
||||||
|
|
||||||
|
local -a values=()
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
values+=( "${pairs[$i]}" )
|
||||||
|
done
|
||||||
|
|
||||||
|
if batslib_is_single_line "${values[@]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${pairs[@]}"
|
||||||
|
else
|
||||||
|
local -i i
|
||||||
|
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
|
||||||
|
pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )"
|
||||||
|
done
|
||||||
|
batslib_print_kv_multi "${pairs[@]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_prefix() {
|
||||||
|
local -r prefix="${1:- }"
|
||||||
|
local line
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
printf '%s%s\n' "$prefix" "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_mark() {
|
||||||
|
local -r symbol="$1"; shift
|
||||||
|
# Sort line numbers.
|
||||||
|
set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" )
|
||||||
|
|
||||||
|
local line
|
||||||
|
local -i idx=0
|
||||||
|
while IFS='' read -r line || [[ -n $line ]]; do
|
||||||
|
if (( ${1:--1} == idx )); then
|
||||||
|
printf '%s\n' "${symbol}${line:${#symbol}}"
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
printf '%s\n' "$line"
|
||||||
|
fi
|
||||||
|
(( ++idx ))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
batslib_decorate() {
|
||||||
|
echo
|
||||||
|
echo "-- $1 --"
|
||||||
|
cat -
|
||||||
|
echo '--'
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
assert() {
|
||||||
|
if ! "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal() {
|
||||||
|
if [[ $1 != "$2" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$2" \
|
||||||
|
'actual' "$1" \
|
||||||
|
| batslib_decorate 'values do not equal' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_failure() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
(( $# > 0 )) && local -r expected="$1"
|
||||||
|
if (( status == 0 )); then
|
||||||
|
batslib_print_kv_single_or_multi 6 'output' "$output" \
|
||||||
|
| batslib_decorate 'command succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
elif (( $# > 0 )) && (( status != expected )); then
|
||||||
|
{ local -ir width=8
|
||||||
|
batslib_print_kv_single "$width" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" \
|
||||||
|
'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed as expected, but status differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r expected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if ! [[ ${lines[$idx]} =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression does not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single 8 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} =~ $expected ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'regexp' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line matches regular expression' \
|
||||||
|
| fail
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == *"$expected"* ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'substring' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no output line contains substring' \
|
||||||
|
| fail
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
[[ ${lines[$idx]} == "$expected" ]] && return 0
|
||||||
|
done
|
||||||
|
{ local -ar single=( 'line' "$expected" )
|
||||||
|
local -ar may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'output does not contain line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_nonempty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_nonempty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local expected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
expected="$(cat -)"
|
||||||
|
else
|
||||||
|
expected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_nonempty )); then
|
||||||
|
if [ -z "$output" ]; then
|
||||||
|
echo 'expected non-empty output, but output was empty' \
|
||||||
|
| batslib_decorate 'no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ '' =~ $expected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$expected'" \
|
||||||
|
| batslib_decorate 'ERROR: assert_output' \
|
||||||
|
| fail
|
||||||
|
elif ! [[ $output =~ $expected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression does not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output != *"$expected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$expected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output does not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output != "$expected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 8 \
|
||||||
|
'expected' "$expected" \
|
||||||
|
'actual' "$output" \
|
||||||
|
| batslib_decorate 'output differs' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_success() {
|
||||||
|
: "${output?}"
|
||||||
|
: "${status?}"
|
||||||
|
|
||||||
|
if (( status != 0 )); then
|
||||||
|
{ local -ir width=6
|
||||||
|
batslib_print_kv_single "$width" 'status' "$status"
|
||||||
|
batslib_print_kv_single_or_multi "$width" 'output' "$output"
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'command failed' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute() {
|
||||||
|
if "$@"; then
|
||||||
|
batslib_print_kv_single 10 'expression' "$*" \
|
||||||
|
| batslib_decorate 'assertion succeeded, but it was expected to fail' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_line() {
|
||||||
|
local -i is_match_line=0
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
: "${lines?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-n|--index)
|
||||||
|
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
|
||||||
|
echo "\`--index' requires an integer argument: \`$2'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
is_match_line=1
|
||||||
|
local -ri idx="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local -r unexpected="$1"
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_line' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_match_line )); then
|
||||||
|
# Specific line.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single 6 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'regular expression should not match line' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single 9 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single 5 \
|
||||||
|
'index' "$idx" \
|
||||||
|
'line' "${lines[$idx]}" \
|
||||||
|
| batslib_decorate 'line should differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Line contained in output.
|
||||||
|
if (( is_mode_regexp )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} =~ $unexpected ]]; then
|
||||||
|
{ local -ar single=( 'regexp' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should match the regular expression' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
|
||||||
|
{ local -ar single=( 'substring' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'no line should contain substring' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
local -i idx
|
||||||
|
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
|
||||||
|
if [[ ${lines[$idx]} == "$unexpected" ]]; then
|
||||||
|
{ local -ar single=( 'line' "$unexpected" 'index' "$idx" )
|
||||||
|
local -a may_be_multi=( 'output' "$output" )
|
||||||
|
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
|
||||||
|
batslib_print_kv_single "$width" "${single[@]}"
|
||||||
|
if batslib_is_single_line "${may_be_multi[1]}"; then
|
||||||
|
batslib_print_kv_single "$width" "${may_be_multi[@]}"
|
||||||
|
else
|
||||||
|
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
|
||||||
|
batslib_print_kv_multi "${may_be_multi[@]}"
|
||||||
|
fi
|
||||||
|
} \
|
||||||
|
| batslib_decorate 'line should not be in output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
refute_output() {
|
||||||
|
local -i is_mode_partial=0
|
||||||
|
local -i is_mode_regexp=0
|
||||||
|
local -i is_mode_empty=0
|
||||||
|
local -i use_stdin=0
|
||||||
|
: "${output?}"
|
||||||
|
|
||||||
|
# Handle options.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
is_mode_empty=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while (( $# > 0 )); do
|
||||||
|
case "$1" in
|
||||||
|
-p|--partial) is_mode_partial=1; shift ;;
|
||||||
|
-e|--regexp) is_mode_regexp=1; shift ;;
|
||||||
|
-|--stdin) use_stdin=1; shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( is_mode_partial )) && (( is_mode_regexp )); then
|
||||||
|
echo "\`--partial' and \`--regexp' are mutually exclusive" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Arguments.
|
||||||
|
local unexpected
|
||||||
|
if (( use_stdin )); then
|
||||||
|
unexpected="$(cat -)"
|
||||||
|
else
|
||||||
|
unexpected="${1-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
|
||||||
|
echo "Invalid extended regular expression: \`$unexpected'" \
|
||||||
|
| batslib_decorate 'ERROR: refute_output' \
|
||||||
|
| fail
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Matching.
|
||||||
|
if (( is_mode_empty )); then
|
||||||
|
if [ -n "$output" ]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output non-empty, but expected no output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_regexp )); then
|
||||||
|
if [[ $output =~ $unexpected ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'regexp' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'regular expression should not match output' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
elif (( is_mode_partial )); then
|
||||||
|
if [[ $output == *"$unexpected"* ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 9 \
|
||||||
|
'substring' "$unexpected" \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output should not contain substring' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $output == "$unexpected" ]]; then
|
||||||
|
batslib_print_kv_single_or_multi 6 \
|
||||||
|
'output' "$output" \
|
||||||
|
| batslib_decorate 'output equals, but it was expected to differ' \
|
||||||
|
| fail
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
284
bash/tournament/tournament.bats
Normal file
284
bash/tournament/tournament.bats
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
load bats-extra
|
||||||
|
|
||||||
|
# local version: 1.4.0.0
|
||||||
|
|
||||||
|
# Your bash program should be able to accept input
|
||||||
|
# 1. via standard input, OR
|
||||||
|
# 2. as a filename given on the cmd line.
|
||||||
|
|
||||||
|
# uses external tool: mktemp
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
export INPUT_FILE HAS_TTY
|
||||||
|
INPUT_FILE=$( mktemp )
|
||||||
|
[[ -t 0 ]] && HAS_TTY=1 || HAS_TTY=0
|
||||||
|
}
|
||||||
|
teardown() { rm -f "$INPUT_FILE"; }
|
||||||
|
|
||||||
|
@test "just the header if no input" {
|
||||||
|
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
|
||||||
|
input=$( cat <<INPUT
|
||||||
|
INPUT
|
||||||
|
)
|
||||||
|
|
||||||
|
expected=$( cat <<EXPECTED
|
||||||
|
Team | MP | W | D | L | P
|
||||||
|
EXPECTED
|
||||||
|
)
|
||||||
|
|
||||||
|
run bash tournament.sh <<< "$input"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "a win is three points, a loss is zero points" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
# ignore this test in CI
|
||||||
|
(( HAS_TTY )) || skip
|
||||||
|
|
||||||
|
cat <<INPUT >"$INPUT_FILE"
|
||||||
|
Allegoric Alaskans;Blithering Badgers;win
|
||||||
|
INPUT
|
||||||
|
|
||||||
|
expected=$( cat <<EXPECTED
|
||||||
|
Team | MP | W | D | L | P
|
||||||
|
Allegoric Alaskans | 1 | 1 | 0 | 0 | 3
|
||||||
|
Blithering Badgers | 1 | 0 | 0 | 1 | 0
|
||||||
|
EXPECTED
|
||||||
|
)
|
||||||
|
|
||||||
|
run bash tournament.sh "$INPUT_FILE"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "a win can also be expressed as a loss" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
|
||||||
|
input=$( cat <<INPUT
|
||||||
|
Blithering Badgers;Allegoric Alaskans;loss
|
||||||
|
INPUT
|
||||||
|
)
|
||||||
|
|
||||||
|
expected=$( cat <<EXPECTED
|
||||||
|
Team | MP | W | D | L | P
|
||||||
|
Allegoric Alaskans | 1 | 1 | 0 | 0 | 3
|
||||||
|
Blithering Badgers | 1 | 0 | 0 | 1 | 0
|
||||||
|
EXPECTED
|
||||||
|
)
|
||||||
|
|
||||||
|
run bash tournament.sh <<< "$input"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "a different team can win" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
|
||||||
|
input=$( cat <<INPUT
|
||||||
|
Blithering Badgers;Allegoric Alaskans;win
|
||||||
|
INPUT
|
||||||
|
)
|
||||||
|
|
||||||
|
expected=$( cat <<EXPECTED
|
||||||
|
Team | MP | W | D | L | P
|
||||||
|
Blithering Badgers | 1 | 1 | 0 | 0 | 3
|
||||||
|
Allegoric Alaskans | 1 | 0 | 0 | 1 | 0
|
||||||
|
EXPECTED
|
||||||
|
)
|
||||||
|
|
||||||
|
run bash tournament.sh <<< "$input"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "a draw is one point each" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
|
||||||
|
input=$( cat <<INPUT
|
||||||
|
Allegoric Alaskans;Blithering Badgers;draw
|
||||||
|
INPUT
|
||||||
|
)
|
||||||
|
|
||||||
|
expected=$( cat <<EXPECTED
|
||||||
|
Team | MP | W | D | L | P
|
||||||
|
Allegoric Alaskans | 1 | 0 | 1 | 0 | 1
|
||||||
|
Blithering Badgers | 1 | 0 | 1 | 0 | 1
|
||||||
|
EXPECTED
|
||||||
|
)
|
||||||
|
|
||||||
|
run bash tournament.sh <<< "$input"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "There can be more than one match" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
|
||||||
|
input=$( cat <<INPUT
|
||||||
|
Allegoric Alaskans;Blithering Badgers;win
|
||||||
|
Allegoric Alaskans;Blithering Badgers;win
|
||||||
|
INPUT
|
||||||
|
)
|
||||||
|
|
||||||
|
expected=$( cat <<EXPECTED
|
||||||
|
Team | MP | W | D | L | P
|
||||||
|
Allegoric Alaskans | 2 | 2 | 0 | 0 | 6
|
||||||
|
Blithering Badgers | 2 | 0 | 0 | 2 | 0
|
||||||
|
EXPECTED
|
||||||
|
)
|
||||||
|
|
||||||
|
run bash tournament.sh <<< "$input"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "There can be more than one winner" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
|
||||||
|
input=$( cat <<INPUT
|
||||||
|
Allegoric Alaskans;Blithering Badgers;loss
|
||||||
|
Allegoric Alaskans;Blithering Badgers;win
|
||||||
|
INPUT
|
||||||
|
)
|
||||||
|
|
||||||
|
expected=$( cat <<EXPECTED
|
||||||
|
Team | MP | W | D | L | P
|
||||||
|
Allegoric Alaskans | 2 | 1 | 0 | 1 | 3
|
||||||
|
Blithering Badgers | 2 | 1 | 0 | 1 | 3
|
||||||
|
EXPECTED
|
||||||
|
)
|
||||||
|
|
||||||
|
run bash tournament.sh <<< "$input"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "There can be more than two teams" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
|
||||||
|
input=$( cat <<INPUT
|
||||||
|
Allegoric Alaskans;Blithering Badgers;win
|
||||||
|
Blithering Badgers;Courageous Californians;win
|
||||||
|
Courageous Californians;Allegoric Alaskans;loss
|
||||||
|
INPUT
|
||||||
|
)
|
||||||
|
|
||||||
|
expected=$( cat <<EXPECTED
|
||||||
|
Team | MP | W | D | L | P
|
||||||
|
Allegoric Alaskans | 2 | 2 | 0 | 0 | 6
|
||||||
|
Blithering Badgers | 2 | 1 | 0 | 1 | 3
|
||||||
|
Courageous Californians | 2 | 0 | 0 | 2 | 0
|
||||||
|
EXPECTED
|
||||||
|
)
|
||||||
|
|
||||||
|
run bash tournament.sh <<< "$input"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "typical input" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
|
||||||
|
input=$( cat <<INPUT
|
||||||
|
Allegoric Alaskans;Blithering Badgers;win
|
||||||
|
Devastating Donkeys;Courageous Californians;draw
|
||||||
|
Devastating Donkeys;Allegoric Alaskans;win
|
||||||
|
Courageous Californians;Blithering Badgers;loss
|
||||||
|
Blithering Badgers;Devastating Donkeys;loss
|
||||||
|
Allegoric Alaskans;Courageous Californians;win
|
||||||
|
INPUT
|
||||||
|
)
|
||||||
|
|
||||||
|
expected=$( cat <<EXPECTED
|
||||||
|
Team | MP | W | D | L | P
|
||||||
|
Devastating Donkeys | 3 | 2 | 1 | 0 | 7
|
||||||
|
Allegoric Alaskans | 3 | 2 | 0 | 1 | 6
|
||||||
|
Blithering Badgers | 3 | 1 | 0 | 2 | 3
|
||||||
|
Courageous Californians | 3 | 0 | 1 | 2 | 1
|
||||||
|
EXPECTED
|
||||||
|
)
|
||||||
|
|
||||||
|
run bash tournament.sh <<< "$input"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "incomplete competition (not all pairs have played)" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
# ignore this test in CI
|
||||||
|
(( HAS_TTY )) || skip
|
||||||
|
|
||||||
|
cat <<INPUT > "$INPUT_FILE"
|
||||||
|
Allegoric Alaskans;Blithering Badgers;loss
|
||||||
|
Devastating Donkeys;Allegoric Alaskans;loss
|
||||||
|
Courageous Californians;Blithering Badgers;draw
|
||||||
|
Allegoric Alaskans;Courageous Californians;win
|
||||||
|
INPUT
|
||||||
|
|
||||||
|
expected=$( cat <<EXPECTED
|
||||||
|
Team | MP | W | D | L | P
|
||||||
|
Allegoric Alaskans | 3 | 2 | 0 | 1 | 6
|
||||||
|
Blithering Badgers | 2 | 1 | 1 | 0 | 4
|
||||||
|
Courageous Californians | 2 | 0 | 1 | 1 | 1
|
||||||
|
Devastating Donkeys | 1 | 0 | 0 | 1 | 0
|
||||||
|
EXPECTED
|
||||||
|
)
|
||||||
|
|
||||||
|
run bash tournament.sh "$INPUT_FILE"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ties broken alphabetically" {
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
|
||||||
|
input=$( cat <<INPUT
|
||||||
|
Courageous Californians;Devastating Donkeys;win
|
||||||
|
Allegoric Alaskans;Blithering Badgers;win
|
||||||
|
Devastating Donkeys;Allegoric Alaskans;loss
|
||||||
|
Courageous Californians;Blithering Badgers;win
|
||||||
|
Blithering Badgers;Devastating Donkeys;draw
|
||||||
|
Allegoric Alaskans;Courageous Californians;draw
|
||||||
|
INPUT
|
||||||
|
)
|
||||||
|
|
||||||
|
expected=$( cat <<EXPECTED
|
||||||
|
Team | MP | W | D | L | P
|
||||||
|
Allegoric Alaskans | 3 | 2 | 1 | 0 | 7
|
||||||
|
Courageous Californians | 3 | 2 | 1 | 0 | 7
|
||||||
|
Blithering Badgers | 3 | 0 | 1 | 2 | 1
|
||||||
|
Devastating Donkeys | 3 | 0 | 1 | 2 | 1
|
||||||
|
EXPECTED
|
||||||
|
)
|
||||||
|
|
||||||
|
run bash tournament.sh <<< "$input"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ensure points sorted numerically" {
|
||||||
|
|
||||||
|
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
|
||||||
|
|
||||||
|
input=$( cat <<INPUT
|
||||||
|
Devastating Donkeys;Blithering Badgers;win
|
||||||
|
Devastating Donkeys;Blithering Badgers;win
|
||||||
|
Devastating Donkeys;Blithering Badgers;win
|
||||||
|
Devastating Donkeys;Blithering Badgers;win
|
||||||
|
Blithering Badgers;Devastating Donkeys;win
|
||||||
|
INPUT
|
||||||
|
)
|
||||||
|
expected=$( cat <<EXPECTED
|
||||||
|
Team | MP | W | D | L | P
|
||||||
|
Devastating Donkeys | 5 | 4 | 0 | 1 | 12
|
||||||
|
Blithering Badgers | 5 | 1 | 0 | 4 | 3
|
||||||
|
EXPECTED
|
||||||
|
)
|
||||||
|
|
||||||
|
run bash tournament.sh <<< "$input"
|
||||||
|
assert_success
|
||||||
|
assert_output "$expected"
|
||||||
|
}
|
||||||
59
bash/tournament/tournament.sh
Normal file
59
bash/tournament/tournament.sh
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
declare -A matches_played wins draws losses points
|
||||||
|
|
||||||
|
print_table_row() {
|
||||||
|
printf '%-30s | %2s | %2s | %2s | %2s | %2s\n' "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_table_row "Team" "MP" "W" "D" "L" "P"
|
||||||
|
|
||||||
|
initialize_teams_stats() {
|
||||||
|
local team
|
||||||
|
for team in "$@"; do
|
||||||
|
: "${matches_played[$team]:=0}"
|
||||||
|
: "${wins[$team]:=0}"
|
||||||
|
: "${draws[$team]:=0}"
|
||||||
|
: "${losses[$team]:=0}"
|
||||||
|
: "${points[$team]:=0}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize_teams_stats "$team1" "$team2"
|
||||||
|
|
||||||
|
while IFS= read -r -t1 line; do
|
||||||
|
# Break loop if an empty line is encountered
|
||||||
|
[ -z "$line" ] && break
|
||||||
|
|
||||||
|
# Parse the input line
|
||||||
|
IFS=';' read -r team1 team2 outcome <<<"$line"
|
||||||
|
|
||||||
|
((matches_played[$team1]++))
|
||||||
|
((matches_played[$team2]++))
|
||||||
|
|
||||||
|
case "$outcome" in
|
||||||
|
"win")
|
||||||
|
((wins[$team1]++))
|
||||||
|
((losses[$team2]++))
|
||||||
|
((points[$team1] += 3))
|
||||||
|
;;
|
||||||
|
"draw")
|
||||||
|
((draws[$team1]++))
|
||||||
|
((draws[$team2]++))
|
||||||
|
((points[$team1]++))
|
||||||
|
((points[$team2]++))
|
||||||
|
;;
|
||||||
|
"loss")
|
||||||
|
((losses[$team1]++))
|
||||||
|
((wins[$team2]++))
|
||||||
|
((points[$team2] += 3))
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
# Read input from a file or standard input (stdin)
|
||||||
|
done <"${1:-/dev/stdin}"
|
||||||
|
|
||||||
|
# Iterate over teams and print their statistics, sorted by points
|
||||||
|
for team in "${!matches_played[@]}"; do
|
||||||
|
print_table_row "$team" "${matches_played[$team]}" "${wins[$team]}" "${draws[$team]}" "${losses[$team]}" "${points[$team]}"
|
||||||
|
# Sort the table by points in descending order, then alphabetically by team name
|
||||||
|
done | sort -t'|' -k6nr,6 -k1,1
|
||||||
26
julia/annalyns-infiltration/.exercism/config.json
Normal file
26
julia/annalyns-infiltration/.exercism/config.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"SaschaMann",
|
||||||
|
"colinleach"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"cmcaine",
|
||||||
|
"BNAndras"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"annalyns-infiltration.jl"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"runtests.jl"
|
||||||
|
],
|
||||||
|
"exemplar": [
|
||||||
|
".meta/exemplar.jl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"language_versions": "≥1.0",
|
||||||
|
"forked_from": [
|
||||||
|
"javascript/annalyns-infiltration"
|
||||||
|
],
|
||||||
|
"blurb": "Learn about boolean expressions by implementing the quest logic for a new RPG."
|
||||||
|
}
|
||||||
1
julia/annalyns-infiltration/.exercism/metadata.json
Normal file
1
julia/annalyns-infiltration/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"julia","exercise":"annalyns-infiltration","id":"30cf911b357741e8a7c62eb77cf57308","url":"https://exercism.org/tracks/julia/exercises/annalyns-infiltration","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||||
34
julia/annalyns-infiltration/HELP.md
Normal file
34
julia/annalyns-infiltration/HELP.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
To run the tests, run this command from within the exercise directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ julia runtests.jl
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit annalyns-infiltration.jl` 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 [Julia track's documentation](https://exercism.org/docs/tracks/julia)
|
||||||
|
- The [Julia track's programming category on the forum](https://forum.exercism.org/c/programming/julia)
|
||||||
|
- [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.
|
||||||
|
|
||||||
|
To get help if you're having trouble, we recommend that you submit your code and request mentoring :)
|
||||||
|
|
||||||
|
If you don't want to do that for whatever reason, then you can find the wider Julia community channels [here](https://julialang.org/community/).
|
||||||
23
julia/annalyns-infiltration/HINTS.md
Normal file
23
julia/annalyns-infiltration/HINTS.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Hints
|
||||||
|
|
||||||
|
## General
|
||||||
|
|
||||||
|
- There are three [boolean operators][boolean-operators] to work with boolean values.
|
||||||
|
- Multiple operators can be combined in a single expression.
|
||||||
|
|
||||||
|
## 1. Check if a fast attack can be made
|
||||||
|
|
||||||
|
- The logical NOT operator (`!`) can be placed before an expression to negate its value.
|
||||||
|
|
||||||
|
## 2. Check if the group can be spied upon
|
||||||
|
|
||||||
|
- Boolean operators are typically used to evaluate whether two or more expressions are true or not true.
|
||||||
|
|
||||||
|
## 3. Check if the prisoner can be signaled
|
||||||
|
|
||||||
|
- Boolean operators execute in the order of their precedence (from highest to lowest): `!`, `&&`, `||`.
|
||||||
|
- In general, use of parentheses is encouraged to make your intention clearer.
|
||||||
|
- For more details check out the Operator Precedence section on the [official Julia documentation][operator-precedence].
|
||||||
|
|
||||||
|
[boolean-operators]: https://docs.julialang.org/en/v1/manual/mathematical-operations/#Boolean-Operators
|
||||||
|
[operator-precedence]: https://docs.julialang.org/en/v1/manual/mathematical-operations/#Operator-Precedence-and-Associativity
|
||||||
162
julia/annalyns-infiltration/README.md
Normal file
162
julia/annalyns-infiltration/README.md
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# Annalyns Infiltration
|
||||||
|
|
||||||
|
Welcome to Annalyns Infiltration on Exercism's Julia Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
## Booleans in Julia
|
||||||
|
|
||||||
|
True or false values are represented by the `Bool` type.
|
||||||
|
It contains only two values: `true` and `false`.
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> true
|
||||||
|
true
|
||||||
|
|
||||||
|
julia> false
|
||||||
|
false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Boolean Operators
|
||||||
|
|
||||||
|
There are three Boolean operators in Julia.
|
||||||
|
|
||||||
|
`&&` is Boolean "and".
|
||||||
|
It evaluates to `true` if the expressions on *both* sides of `&&` are `true`.
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> true && true
|
||||||
|
true
|
||||||
|
|
||||||
|
julia> true && false
|
||||||
|
false
|
||||||
|
```
|
||||||
|
|
||||||
|
`||` is Boolean "or".
|
||||||
|
It evaluates to `true` if an expression on *either* side of `||` is `true`.
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> true || true
|
||||||
|
true
|
||||||
|
|
||||||
|
julia> false || true
|
||||||
|
true
|
||||||
|
```
|
||||||
|
|
||||||
|
`!` is Boolean "not".
|
||||||
|
It exchanges `true` and `false` values.
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> !true
|
||||||
|
false
|
||||||
|
|
||||||
|
julia> !false
|
||||||
|
true
|
||||||
|
```
|
||||||
|
|
||||||
|
For longer and more complicated expressions, it is best to use parentheses to make your intention clear.
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> (true || false) && (false && true)
|
||||||
|
false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
In this exercise, you'll be writing some logic for a video game a friend is developing.
|
||||||
|
The game's main character is Annalyn, a brave girl with a fierce and loyal pet dog.
|
||||||
|
Unfortunately, disaster strikes, as her best friend is kidnapped while searching for berries in the forest.
|
||||||
|
Annalyn will try to find and free her friend, optionally taking her dog with her on this quest.
|
||||||
|
|
||||||
|
Annalyn eventually finds the camp in which her friend is imprisoned and it turns out there are two kidnappers: a mighty knight and a cunning archer.
|
||||||
|
|
||||||
|
The player is presented with some options for what to do next.
|
||||||
|
For each of the four possible options you need to write a function that tells the game whether it should show that option or not.
|
||||||
|
|
||||||
|
## 1. Check if the 'Fast Attack' option should be shown
|
||||||
|
|
||||||
|
If the knight is sleeping, then Annalyn will be able to make a quick attack into the camp before he can wake up properly and get his armour on.
|
||||||
|
|
||||||
|
Implement a function named `can_do_fast_attack` that takes a boolean value which indicates if the knight is awake.
|
||||||
|
This function returns `true` if the 'Fast Attack' action is available based on the state of the character. Otherwise, returns `false`:
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> knight_awake = true
|
||||||
|
|
||||||
|
julia> can_do_fast_attack(knight_awake)
|
||||||
|
false
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Check if the 'Spy' option should be shown
|
||||||
|
|
||||||
|
The group can be spied upon if at least one of them is awake. Otherwise, spying is a waste of time.
|
||||||
|
|
||||||
|
Implement a function named `can_spy` that takes three boolean values, indicating if the knight, archer and prisoner, respectively, are awake.
|
||||||
|
The function returns `true` if the 'Spy' action is available based on the state of the characters.
|
||||||
|
Otherwise, returns `false`:
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
# Output suppressed from the next 3 lines
|
||||||
|
julia> knight_awake = false
|
||||||
|
julia> archer_awake = true
|
||||||
|
julia> prisoner_awake = false
|
||||||
|
|
||||||
|
julia> can_spy(knight_awake, archer_awake, prisoner_awake)
|
||||||
|
true
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Check if the 'Signal Prisoner' option should be shown
|
||||||
|
|
||||||
|
The prisoner can be signalled using bird sounds if she is awake and the archer is sleeping.
|
||||||
|
If the archer is awake then she can't be safely signaled because the archer is also trained in bird signalling.
|
||||||
|
|
||||||
|
Implement a function named `can_signal_prisoner` that takes two boolean values, indicating if the archer and prisoner, respectively, are awake.
|
||||||
|
The function returns `true` if the 'Signal Prisoner' action is available based on the state of the characters.
|
||||||
|
Otherwise, returns `false`:
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> archer_awake = false
|
||||||
|
julia> prisoner_awake = true
|
||||||
|
|
||||||
|
julia> can_signal_prisoner(archer_awake, prisoner_awake)
|
||||||
|
true
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Check if the 'Free Prisoner' option should be shown
|
||||||
|
|
||||||
|
Annalyn can try sneaking into the camp to free her friend. This is a risky thing to do and can only succeed in one of two ways:
|
||||||
|
|
||||||
|
- If Annalyn has her pet dog with her, she can rescue the prisoner if the archer is asleep.
|
||||||
|
The knight is scared of the dog and the archer will not have time to get ready before Annalyn and her friend can escape.
|
||||||
|
|
||||||
|
- If Annalyn does not have her dog then she and the prisoner must be very sneaky!
|
||||||
|
Annalyn can free the prisoner if she is awake and the knight and archer are both sleeping, but if the prisoner is sleeping, she can't be rescued: she would be startled by Annalyn's sudden appearance and wake up the knight and archer.
|
||||||
|
|
||||||
|
Implement a function named `can_free_prisoner` that takes four boolean values.
|
||||||
|
The first three parameters indicate if the knight, archer and prisoner, respectively, are awake.
|
||||||
|
The last parameter indicates if Annalyn's pet dog is present.
|
||||||
|
The function returns `true` if the 'Free Prisoner' action is available based on the state of the characters. Otherwise, it returns `false`:
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> knight_awake = false
|
||||||
|
julia> archer_awake = true
|
||||||
|
julia> prisoner_awake = false
|
||||||
|
julia> dog_present = false
|
||||||
|
|
||||||
|
julia> can_free_prisoner(knight_awake, archer_awake, prisoner_awake, dog_present)
|
||||||
|
false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @SaschaMann
|
||||||
|
- @colinleach
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @cmcaine
|
||||||
|
- @BNAndras
|
||||||
15
julia/annalyns-infiltration/annalyns-infiltration.jl
Normal file
15
julia/annalyns-infiltration/annalyns-infiltration.jl
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
function can_do_fast_attack(knight_awake)
|
||||||
|
return !knight_awake
|
||||||
|
end
|
||||||
|
|
||||||
|
function can_spy(knight_awake, archer_awake, prisoner_awake)
|
||||||
|
return (knight_awake || archer_awake || prisoner_awake)
|
||||||
|
end
|
||||||
|
|
||||||
|
function can_signal_prisoner(archer_awake, prisoner_awake)
|
||||||
|
return !archer_awake && prisoner_awake
|
||||||
|
end
|
||||||
|
|
||||||
|
function can_free_prisoner(knight_awake, archer_awake, prisoner_awake, dog_present)
|
||||||
|
return (dog_present && !archer_awake) || (!dog_present && prisoner_awake && !knight_awake && !archer_awake)
|
||||||
|
end
|
||||||
71
julia/annalyns-infiltration/runtests.jl
Normal file
71
julia/annalyns-infiltration/runtests.jl
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using Test
|
||||||
|
|
||||||
|
include("annalyns-infiltration.jl")
|
||||||
|
|
||||||
|
# Julia 1.0 compat
|
||||||
|
# The function definition of eachrow is taken from Julia Base,
|
||||||
|
# released under the MIT license: https://julialang.org/license
|
||||||
|
if VERSION < v"1.1"
|
||||||
|
@eval eachrow(A) = (view(A, i, :) for i in axes(A, 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset verbose = true "tests" begin
|
||||||
|
@testset "fast attack" begin
|
||||||
|
@test !can_do_fast_attack(true)
|
||||||
|
@test can_do_fast_attack(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "spying" begin
|
||||||
|
character_state_combinations = Bool[
|
||||||
|
0 0 0 0;
|
||||||
|
0 0 1 1;
|
||||||
|
0 1 0 1;
|
||||||
|
0 1 1 1;
|
||||||
|
1 0 0 1;
|
||||||
|
1 0 1 1;
|
||||||
|
1 1 1 1;
|
||||||
|
]
|
||||||
|
|
||||||
|
for state in eachrow(character_state_combinations)
|
||||||
|
@test can_spy(state[1:3]...) == state[4]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "signaling prisoner" begin
|
||||||
|
character_state_combinations = Bool[
|
||||||
|
0 0 0;
|
||||||
|
0 1 1;
|
||||||
|
1 0 0;
|
||||||
|
1 1 0;
|
||||||
|
]
|
||||||
|
|
||||||
|
for state in eachrow(character_state_combinations)
|
||||||
|
@test can_signal_prisoner(state[1:2]...) == state[3]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "freeing prisoner" begin
|
||||||
|
character_state_combinations = Bool[
|
||||||
|
0 0 0 0 0;
|
||||||
|
0 0 0 1 1;
|
||||||
|
0 0 1 0 1;
|
||||||
|
0 0 1 1 1;
|
||||||
|
0 1 0 0 0;
|
||||||
|
0 1 0 1 0;
|
||||||
|
0 1 1 0 0;
|
||||||
|
0 1 1 1 0;
|
||||||
|
1 0 0 0 0;
|
||||||
|
1 0 0 1 1;
|
||||||
|
1 0 1 0 0;
|
||||||
|
1 0 1 1 1;
|
||||||
|
1 1 0 0 0;
|
||||||
|
1 1 0 1 0;
|
||||||
|
1 1 1 0 0;
|
||||||
|
1 1 1 1 0;
|
||||||
|
]
|
||||||
|
|
||||||
|
for state in eachrow(character_state_combinations)
|
||||||
|
@test can_free_prisoner(state[1:4]...) == state[5]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
20
julia/cars-assemble/.exercism/config.json
Normal file
20
julia/cars-assemble/.exercism/config.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"colinleach"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"cars-assemble.jl"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"runtests.jl"
|
||||||
|
],
|
||||||
|
"exemplar": [
|
||||||
|
".meta/exemplar.jl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"forked_from": [
|
||||||
|
"csharp/cars-assemble"
|
||||||
|
],
|
||||||
|
"blurb": "Learn about conditionals by analyzing the production of an assembly line."
|
||||||
|
}
|
||||||
1
julia/cars-assemble/.exercism/metadata.json
Normal file
1
julia/cars-assemble/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"julia","exercise":"cars-assemble","id":"45c3c88693d84b2d88daec48b8f45391","url":"https://exercism.org/tracks/julia/exercises/cars-assemble","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||||
34
julia/cars-assemble/HELP.md
Normal file
34
julia/cars-assemble/HELP.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
To run the tests, run this command from within the exercise directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ julia runtests.jl
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit cars-assemble.jl` 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 [Julia track's documentation](https://exercism.org/docs/tracks/julia)
|
||||||
|
- The [Julia track's programming category on the forum](https://forum.exercism.org/c/programming/julia)
|
||||||
|
- [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.
|
||||||
|
|
||||||
|
To get help if you're having trouble, we recommend that you submit your code and request mentoring :)
|
||||||
|
|
||||||
|
If you don't want to do that for whatever reason, then you can find the wider Julia community channels [here](https://julialang.org/community/).
|
||||||
22
julia/cars-assemble/HINTS.md
Normal file
22
julia/cars-assemble/HINTS.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Hints
|
||||||
|
|
||||||
|
## 1. Calculate the success rate
|
||||||
|
|
||||||
|
- You need to translate the speed into a success rate, using the rules given in the instructions.
|
||||||
|
- The returned value is a floating-point number.
|
||||||
|
- Julia does not have the `select`/`case` syntax present in some other languages, but the Introduction discussed `if-else` syntax.
|
||||||
|
|
||||||
|
## 2. Calculate the production rate per hour
|
||||||
|
|
||||||
|
- The total theoretical production rate depends on the base production rate (a constant) and the speed.
|
||||||
|
- Return the actual production rate, which also depends on the success rate.
|
||||||
|
|
||||||
|
## 3. Calculate the number of working items produced per minute
|
||||||
|
|
||||||
|
- The hourly production rate was calculated in task 2.
|
||||||
|
- This task requires the rate per minute.
|
||||||
|
- Only complete, working cars are counted in this exercise, so you will need to remove part-complete cars from the count.
|
||||||
|
- The return value must be an integer (for example `7`, not `7.0`).
|
||||||
|
- The [Numbers][numbers] Concept already discussed ways to round values.
|
||||||
|
|
||||||
|
[numbers]: https://exercism.org/tracks/julia/concepts/numbers
|
||||||
150
julia/cars-assemble/README.md
Normal file
150
julia/cars-assemble/README.md
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
# Cars Assemble
|
||||||
|
|
||||||
|
Welcome to Cars Assemble on Exercism's Julia Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
## Comparison operators
|
||||||
|
|
||||||
|
Comparison operators in Julia are similar to many other languages, though with some extra options for math-lovers.
|
||||||
|
|
||||||
|
For equality, the operators are `==` (equal) and `!=` or `≠` (not equal).
|
||||||
|
|
||||||
|
```julia
|
||||||
|
txt = "abc"
|
||||||
|
txt == "abc" # true
|
||||||
|
txt != "abc" # false
|
||||||
|
txt ≠ "abc" # false (synonym for !=)
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition, we have the various greater/less than operators.
|
||||||
|
|
||||||
|
```julia
|
||||||
|
1 < 3 # true
|
||||||
|
3 > 3 # false
|
||||||
|
3 <= 3 # true
|
||||||
|
3 ≤ 3 # true (synonym for <=)
|
||||||
|
4 >= 3 # true
|
||||||
|
4 ≥ 3 # true (synonym for >=)
|
||||||
|
```
|
||||||
|
|
||||||
|
As often with Julia, an appropriate editor makes use of the mathematical symbol easy.
|
||||||
|
Type `\ne`, `\le` or `\ge` then `TAB` to get `≠`, `≤` or `≥`.
|
||||||
|
|
||||||
|
The previous example uses only numbers, but we will see in other parts of the syllabus that various additional types have a sense of ordering and can be tested for greater/less than.
|
||||||
|
|
||||||
|
Comparison operators can be chained, which allows a clear and concise syntax:
|
||||||
|
|
||||||
|
```julia
|
||||||
|
n = 3
|
||||||
|
1 ≤ n ≤ 5 # true (n "between" two limits)
|
||||||
|
```
|
||||||
|
|
||||||
|
The previous example is a synonym for `1 ≤ n && n ≤ 5`.
|
||||||
|
|
||||||
|
## Branching with `if`
|
||||||
|
|
||||||
|
This is the full form of an `if` statement:
|
||||||
|
|
||||||
|
```julia
|
||||||
|
if conditional1
|
||||||
|
statements...
|
||||||
|
elseif conditional2
|
||||||
|
statements...
|
||||||
|
else
|
||||||
|
statements...
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
There is no need for parentheses `()` or braces `{}`, and indentation is "only" to improve readability _(but readability is very important!)_.
|
||||||
|
|
||||||
|
Both `elseif` and `else` are optional, and there can be multiple `elseif` blocks.
|
||||||
|
However, the `end` is required.
|
||||||
|
|
||||||
|
It is possible to nest `if` statements, though you might want to help readability with the thoughtful use of parentheses, indents and comments.
|
||||||
|
|
||||||
|
The shortest form of an `if` statement would be something like this:
|
||||||
|
|
||||||
|
```julia
|
||||||
|
if n < 0
|
||||||
|
n = 0
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
As a reminder: only expressions that evaluate to `true` or `false` can be used as conditionals.
|
||||||
|
Julia deliberately avoids any concept of "truthiness", so zero values, empty strings and empty arrays are _not_ equivalent to `false`.
|
||||||
|
|
||||||
|
## Ternary operator
|
||||||
|
|
||||||
|
A simple and common situation is picking one of two values based on a conditional.
|
||||||
|
|
||||||
|
Julia, like many languages, has a ternary operator to make this more concise.
|
||||||
|
|
||||||
|
The syntax is `conditional ? value_if_true : value_if_false`.
|
||||||
|
|
||||||
|
So the previous example could be rewritten:
|
||||||
|
|
||||||
|
```julia
|
||||||
|
n = n < 0 ? 0 : n
|
||||||
|
```
|
||||||
|
|
||||||
|
Parentheses are not required by the compiler, but may improve readability.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
In this exercise you will be writing code to analyze the production of an assembly line in a car factory.
|
||||||
|
The assembly line's speed can range from `0` (off) to `10` (maximum).
|
||||||
|
|
||||||
|
At its lowest speed (`1`), `221` cars are produced each hour.
|
||||||
|
The production increases linearly with the speed.
|
||||||
|
So with the speed set to `4`, it should produce `4 * 221 = 884` cars per hour.
|
||||||
|
However, higher speeds increase the likelihood that faulty cars are produced, which then have to be discarded.
|
||||||
|
|
||||||
|
You have three tasks.
|
||||||
|
Each of the required functions takes a single integer parameter, the speed of the assembly line.
|
||||||
|
|
||||||
|
## 1. Calculate the success rate
|
||||||
|
|
||||||
|
Implement the `success_rate()` method to calculate the probability of an item being created without error for a given speed.
|
||||||
|
The following table shows how speed influences the success rate:
|
||||||
|
|
||||||
|
- `0`: 0% success rate.
|
||||||
|
- `1` to `4`: 100% success rate.
|
||||||
|
- `5` to `8`: 90% success rate.
|
||||||
|
- `9`: 80% success rate.
|
||||||
|
- `10`: 77% success rate.
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> success_rate(10)
|
||||||
|
0.77
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Calculate the production rate per hour
|
||||||
|
|
||||||
|
Implement the `production_rate_per_hour()` method to calculate the assembly line's production rate per hour, taking into account its success rate.
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> production_rate_per_hour(6)
|
||||||
|
1193.4
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the value returned is floating-point.
|
||||||
|
|
||||||
|
## 3. Calculate the number of working items produced per minute
|
||||||
|
|
||||||
|
Implement the `working_items_per_minute()` method to calculate how many working cars are produced per minute:
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> working_items_per_minute(6)
|
||||||
|
19
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the value returned is an integer: incomplete items are not included.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @colinleach
|
||||||
24
julia/cars-assemble/cars-assemble.jl
Normal file
24
julia/cars-assemble/cars-assemble.jl
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
CARS_PER_HOUR = 221
|
||||||
|
|
||||||
|
function success_rate(speed)
|
||||||
|
if speed == 0
|
||||||
|
success = 0
|
||||||
|
elseif 1 ≤ speed ≤ 4
|
||||||
|
success = 100
|
||||||
|
elseif 5 ≤ speed ≤ 8
|
||||||
|
success = 90
|
||||||
|
elseif speed == 9
|
||||||
|
success = 80
|
||||||
|
elseif speed == 10
|
||||||
|
success = 77
|
||||||
|
end
|
||||||
|
return success / 100
|
||||||
|
end
|
||||||
|
|
||||||
|
function production_rate_per_hour(speed)
|
||||||
|
return CARS_PER_HOUR * speed * success_rate(speed)
|
||||||
|
end
|
||||||
|
|
||||||
|
function working_items_per_minute(speed)
|
||||||
|
return Int(production_rate_per_hour(speed) ÷ 60)
|
||||||
|
end
|
||||||
108
julia/cars-assemble/runtests.jl
Normal file
108
julia/cars-assemble/runtests.jl
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
using Test
|
||||||
|
|
||||||
|
include("cars-assemble.jl")
|
||||||
|
|
||||||
|
@testset verbose = true "tests" begin
|
||||||
|
|
||||||
|
@testset "success_rate" begin
|
||||||
|
@testset "Success rate for speed zero" begin
|
||||||
|
speed = 0
|
||||||
|
@test isapprox(success_rate(speed), 0.0, atol=1e-3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Success rate for speed one" begin
|
||||||
|
speed = 1
|
||||||
|
@test isapprox(success_rate(speed), 1.0, atol=1e-3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Success rate for speed four" begin
|
||||||
|
speed = 4
|
||||||
|
@test isapprox(success_rate(speed), 1.0, atol=1e-3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Success rate for speed five" begin
|
||||||
|
speed = 5
|
||||||
|
@test isapprox(success_rate(speed), 0.9, atol=1e-3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Success rate for speed nine" begin
|
||||||
|
speed = 9
|
||||||
|
@test isapprox(success_rate(speed), 0.8, atol=1e-3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Success rate for speed ten" begin
|
||||||
|
speed = 10
|
||||||
|
@test isapprox(success_rate(speed), 0.77, atol=1e-3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "production_rate_per_hour" begin
|
||||||
|
@testset "Production rate per hour for speed zero" begin
|
||||||
|
speed = 0
|
||||||
|
@test isapprox(production_rate_per_hour(speed), 0.0, atol=1e-3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Production rate per hour for speed one" begin
|
||||||
|
speed = 1
|
||||||
|
@test isapprox(production_rate_per_hour(speed), 221.0, atol=1e-3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Production rate per hour for speed four" begin
|
||||||
|
speed = 4
|
||||||
|
@test isapprox(production_rate_per_hour(speed), 884.0, atol=1e-3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Production rate per hour for speed seven" begin
|
||||||
|
speed = 7
|
||||||
|
@test isapprox(production_rate_per_hour(speed), 1392.3, atol=1e-3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Production rate per hour for speed nine" begin
|
||||||
|
speed = 9
|
||||||
|
@test isapprox(production_rate_per_hour(speed), 1591.2, atol=1e-3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Production rate per hour for speed ten" begin
|
||||||
|
speed = 10
|
||||||
|
@test isapprox(production_rate_per_hour(speed), 1701.7, atol=1e-3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "working_items_per_minute" begin
|
||||||
|
@testset "Working items per minute for speed zero" begin
|
||||||
|
speed = 0
|
||||||
|
@test typeof(working_items_per_minute(speed)) == Int
|
||||||
|
@test working_items_per_minute(speed) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Working items per minute for speed one" begin
|
||||||
|
speed = 1
|
||||||
|
@test typeof(working_items_per_minute(speed)) == Int
|
||||||
|
@test working_items_per_minute(speed) == 3
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Working items per minute for speed five" begin
|
||||||
|
speed = 5
|
||||||
|
@test typeof(working_items_per_minute(speed)) == Int
|
||||||
|
@test working_items_per_minute(speed) == 16
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Working items per minute for speed eight" begin
|
||||||
|
speed = 8
|
||||||
|
@test typeof(working_items_per_minute(speed)) == Int
|
||||||
|
@test working_items_per_minute(speed) == 26
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Working items per minute for speed nine" begin
|
||||||
|
speed = 9
|
||||||
|
@test typeof(working_items_per_minute(speed)) == Int
|
||||||
|
@test working_items_per_minute(speed) == 26
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Working items per minute for speed ten" begin
|
||||||
|
speed = 10
|
||||||
|
@test typeof(working_items_per_minute(speed)) == Int
|
||||||
|
@test working_items_per_minute(speed) == 28
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
20
julia/chessboard/.exercism/config.json
Normal file
20
julia/chessboard/.exercism/config.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"colinleach"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"chessboard.jl"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"runtests.jl"
|
||||||
|
],
|
||||||
|
"exemplar": [
|
||||||
|
".meta/exemplar.jl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"forked_from": [
|
||||||
|
"elixir/chessboard"
|
||||||
|
],
|
||||||
|
"blurb": "Learn about ranges by building a chessboard."
|
||||||
|
}
|
||||||
1
julia/chessboard/.exercism/metadata.json
Normal file
1
julia/chessboard/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"julia","exercise":"chessboard","id":"dc930c6af16b45df91a8634905880861","url":"https://exercism.org/tracks/julia/exercises/chessboard","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||||
34
julia/chessboard/HELP.md
Normal file
34
julia/chessboard/HELP.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
To run the tests, run this command from within the exercise directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ julia runtests.jl
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit chessboard.jl` 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 [Julia track's documentation](https://exercism.org/docs/tracks/julia)
|
||||||
|
- The [Julia track's programming category on the forum](https://forum.exercism.org/c/programming/julia)
|
||||||
|
- [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.
|
||||||
|
|
||||||
|
To get help if you're having trouble, we recommend that you submit your code and request mentoring :)
|
||||||
|
|
||||||
|
If you don't want to do that for whatever reason, then you can find the wider Julia community channels [here](https://julialang.org/community/).
|
||||||
22
julia/chessboard/HINTS.md
Normal file
22
julia/chessboard/HINTS.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Hints
|
||||||
|
|
||||||
|
This is a short and simple exercise, so it is best to avoid complicating it.
|
||||||
|
|
||||||
|
## 1. Define the rank range
|
||||||
|
|
||||||
|
- You need to return a range of integers
|
||||||
|
|
||||||
|
## 2. Define the file range
|
||||||
|
|
||||||
|
- You need to return a range of uppercase characters.
|
||||||
|
- Characters use single-quotes, such as `'W'`,
|
||||||
|
|
||||||
|
## 3. Transform the rank range into a list of ranks
|
||||||
|
|
||||||
|
- The [`collect()`][collect] function is your friend here.
|
||||||
|
|
||||||
|
## 4. Transform the file range into a list of files
|
||||||
|
|
||||||
|
- See task 3.
|
||||||
|
|
||||||
|
[collect]: https://docs.julialang.org/en/v1/base/collections/#Base.collect-Tuple{Any}
|
||||||
149
julia/chessboard/README.md
Normal file
149
julia/chessboard/README.md
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# Chessboard
|
||||||
|
|
||||||
|
Welcome to Chessboard on Exercism's Julia Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Suppose you want all the non-negative integers up to 1000.
|
||||||
|
It would be ridiculous if you had to type these into an array.
|
||||||
|
|
||||||
|
For this we have the `range` type:
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> 0:1000
|
||||||
|
0:1000
|
||||||
|
|
||||||
|
julia> typeof(0:1000)
|
||||||
|
UnitRange{Int64}
|
||||||
|
```
|
||||||
|
|
||||||
|
Ranges are very common: not just to save you typing, but also as return types from functions.
|
||||||
|
|
||||||
|
Note that ranges are _not_ vectors.
|
||||||
|
They are just a set of instructions to generate a sequence ("lazy" evaluation, or an "iterator").
|
||||||
|
|
||||||
|
If you need a range as a vector, use the `collect()` function for conversion:
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> collect(0:5)
|
||||||
|
6-element Vector{Int64}:
|
||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
```
|
||||||
|
|
||||||
|
The step size can be specified, in this case 0.3:
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> collect(1.0:0.3:2.0)
|
||||||
|
4-element Vector{Float64}:
|
||||||
|
1.0
|
||||||
|
1.3
|
||||||
|
1.6
|
||||||
|
1.9
|
||||||
|
```
|
||||||
|
|
||||||
|
So the syntax is `start:stepsize:stop`.
|
||||||
|
Both end limits are _inclusive_, as seen in the integer example.
|
||||||
|
If the step size does not divide exactly into `stop - start`, the last element will avoid exceeding `stop`.
|
||||||
|
|
||||||
|
## Letter ranges
|
||||||
|
|
||||||
|
Non-numeric sequences can also be used in ranges.
|
||||||
|
The simplest example is ASCII letters:
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> 'a':'d'
|
||||||
|
'a':1:'d'
|
||||||
|
|
||||||
|
julia> typeof('a':'d')
|
||||||
|
StepRange{Char, Int64}
|
||||||
|
|
||||||
|
julia> collect('a':'d')
|
||||||
|
4-element Vector{Char}:
|
||||||
|
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
|
||||||
|
'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
|
||||||
|
'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
|
||||||
|
'd': ASCII/Unicode U+0064 (category Ll: Letter, lowercase)
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Char` type will be covered in more detail in another Concept.
|
||||||
|
For now, just treat these as single characters in single-quotes.
|
||||||
|
|
||||||
|
## Functions and operators for ranges
|
||||||
|
|
||||||
|
Check the limits of a range with `first()` and `last()`.
|
||||||
|
|
||||||
|
```julia
|
||||||
|
r = 1:10 # => 1:10
|
||||||
|
first(r) # => 1
|
||||||
|
last(r) # => 10
|
||||||
|
```
|
||||||
|
|
||||||
|
## More on vector indexing
|
||||||
|
|
||||||
|
Integer ranges and vectors can be used in vector indexing:
|
||||||
|
|
||||||
|
```julia
|
||||||
|
nums = collect(10.0:50.0)
|
||||||
|
nums[3:2:7] # gives [12.0, 14.0, 16.0]
|
||||||
|
nums[ [3, 5, 7] ] # also gives [12.0, 14.0, 16.0]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
As a chess enthusiast, you would like to write your own version of the game. Yes, there maybe plenty of implementations of chess available online already, but yours will be unique!
|
||||||
|
|
||||||
|
But before you can let your imagination run wild, you need to take care of the basics. Let's start by generating the board.
|
||||||
|
|
||||||
|
Each square of the chessboard is identified by a letter-number pair. The vertical columns of squares, called files, are labeled A through H. The horizontal rows of squares, called ranks, are numbered 1 to 8.
|
||||||
|
|
||||||
|
## 1. Define the rank range
|
||||||
|
|
||||||
|
Implement the `rank_range()` function. It should return a range of integers, from 1 to 8.
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> rank_range()
|
||||||
|
# output omitted
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Define the file range
|
||||||
|
|
||||||
|
Implement the `file_range()` function.
|
||||||
|
It should return a range of integers, from the uppercase letter A, to the uppercase letter H.
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> file_range()
|
||||||
|
# output omitted
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Transform the rank range into a vector of ranks
|
||||||
|
|
||||||
|
Implement the `ranks()` function. It should return a vector of integers, from 1 to 8.
|
||||||
|
Do not write the vector by hand, generate it from the range returned by the `rank_range()` function.
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> ranks()
|
||||||
|
[1, 2, 3, 4, 5, 6, 7, 8]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Transform the file range into a vector of files
|
||||||
|
|
||||||
|
Implement the `files` function. It should return a vector of characters, from 'A' to 'H'.
|
||||||
|
Do not write the vector by hand, generate it from the range returned by the `file_range()` function.
|
||||||
|
|
||||||
|
```julia-repl
|
||||||
|
julia> files()
|
||||||
|
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
|
||||||
|
```
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @colinleach
|
||||||
15
julia/chessboard/chessboard.jl
Normal file
15
julia/chessboard/chessboard.jl
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
function rank_range()
|
||||||
|
return 1:8
|
||||||
|
end
|
||||||
|
|
||||||
|
function file_range()
|
||||||
|
return 'A':'H'
|
||||||
|
end
|
||||||
|
|
||||||
|
function ranks()
|
||||||
|
return collect(rank_range())
|
||||||
|
end
|
||||||
|
|
||||||
|
function files()
|
||||||
|
return collect(file_range())
|
||||||
|
end
|
||||||
31
julia/chessboard/runtests.jl
Normal file
31
julia/chessboard/runtests.jl
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using Test
|
||||||
|
|
||||||
|
include("chessboard.jl")
|
||||||
|
|
||||||
|
@testset verbose = true "tests" begin
|
||||||
|
|
||||||
|
@testset "rank_range is a range from 1 to 8" begin
|
||||||
|
result = rank_range()
|
||||||
|
@test first(result) == 1
|
||||||
|
@test last(result) == 8
|
||||||
|
@test length(result) == 8
|
||||||
|
@test typeof(result) == UnitRange{Int}
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "file_range is a range from 'A' to 'H'" begin
|
||||||
|
result = file_range()
|
||||||
|
@test first(result) == 'A'
|
||||||
|
@test last(result) == 'H'
|
||||||
|
@test length(result) == 8
|
||||||
|
@test typeof(result) == StepRange{Char, Int}
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "ranks is a vector of integers from 1 to 8" begin
|
||||||
|
@test ranks() == [1, 2, 3, 4, 5, 6, 7, 8]
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "files is a vector of characters from 'A' to 'H'" begin
|
||||||
|
@test ranks() == [1, 2, 3, 4, 5, 6, 7, 8]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
22
julia/collatz-conjecture/.exercism/config.json
Normal file
22
julia/collatz-conjecture/.exercism/config.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"SaschaMann"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"guilhermebodin"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"collatz-conjecture.jl"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"runtests.jl"
|
||||||
|
],
|
||||||
|
"example": [
|
||||||
|
".meta/example.jl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"blurb": "Calculate the number of steps to reach 1 using the Collatz conjecture.",
|
||||||
|
"source": "Wikipedia",
|
||||||
|
"source_url": "https://en.wikipedia.org/wiki/Collatz_conjecture"
|
||||||
|
}
|
||||||
1
julia/collatz-conjecture/.exercism/metadata.json
Normal file
1
julia/collatz-conjecture/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"julia","exercise":"collatz-conjecture","id":"3beda5faa27740749db53e30351afca0","url":"https://exercism.org/tracks/julia/exercises/collatz-conjecture","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||||
34
julia/collatz-conjecture/HELP.md
Normal file
34
julia/collatz-conjecture/HELP.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
To run the tests, run this command from within the exercise directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ julia runtests.jl
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit collatz-conjecture.jl` 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 [Julia track's documentation](https://exercism.org/docs/tracks/julia)
|
||||||
|
- The [Julia track's programming category on the forum](https://forum.exercism.org/c/programming/julia)
|
||||||
|
- [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.
|
||||||
|
|
||||||
|
To get help if you're having trouble, we recommend that you submit your code and request mentoring :)
|
||||||
|
|
||||||
|
If you don't want to do that for whatever reason, then you can find the wider Julia community channels [here](https://julialang.org/community/).
|
||||||
51
julia/collatz-conjecture/README.md
Normal file
51
julia/collatz-conjecture/README.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Collatz Conjecture
|
||||||
|
|
||||||
|
Welcome to Collatz Conjecture on Exercism's Julia Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
One evening, you stumbled upon an old notebook filled with cryptic scribbles, as though someone had been obsessively chasing an idea.
|
||||||
|
On one page, a single question stood out: **Can every number find its way to 1?**
|
||||||
|
It was tied to something called the **Collatz Conjecture**, a puzzle that has baffled thinkers for decades.
|
||||||
|
|
||||||
|
The rules were deceptively simple.
|
||||||
|
Pick any positive integer.
|
||||||
|
|
||||||
|
- If it's even, divide it by 2.
|
||||||
|
- If it's odd, multiply it by 3 and add 1.
|
||||||
|
|
||||||
|
Then, repeat these steps with the result, continuing indefinitely.
|
||||||
|
|
||||||
|
Curious, you picked number 12 to test and began the journey:
|
||||||
|
|
||||||
|
12 ➜ 6 ➜ 3 ➜ 10 ➜ 5 ➜ 16 ➜ 8 ➜ 4 ➜ 2 ➜ 1
|
||||||
|
|
||||||
|
Counting from the second number (6), it took 9 steps to reach 1, and each time the rules repeated, the number kept changing.
|
||||||
|
At first, the sequence seemed unpredictable — jumping up, down, and all over.
|
||||||
|
Yet, the conjecture claims that no matter the starting number, we'll always end at 1.
|
||||||
|
|
||||||
|
It was fascinating, but also puzzling.
|
||||||
|
Why does this always seem to work?
|
||||||
|
Could there be a number where the process breaks down, looping forever or escaping into infinity?
|
||||||
|
The notebook suggested solving this could reveal something profound — and with it, fame, [fortune][collatz-prize], and a place in history awaits whoever could unlock its secrets.
|
||||||
|
|
||||||
|
[collatz-prize]: https://mathprize.net/posts/collatz-conjecture/
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Given a positive integer, return the number of steps it takes to reach 1 according to the rules of the Collatz Conjecture.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @SaschaMann
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @guilhermebodin
|
||||||
|
|
||||||
|
### Based on
|
||||||
|
|
||||||
|
Wikipedia - https://en.wikipedia.org/wiki/Collatz_conjecture
|
||||||
23
julia/collatz-conjecture/collatz-conjecture.jl
Normal file
23
julia/collatz-conjecture/collatz-conjecture.jl
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
function is_even(n::Int)
|
||||||
|
return n % 2 == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function is_odd(n::Int)
|
||||||
|
return n % 2 != 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function collatz_steps(n::Int)
|
||||||
|
if n <= 0
|
||||||
|
throw(DomainError("Input must be a positive integer."))
|
||||||
|
end
|
||||||
|
steps = 0
|
||||||
|
while n != 1
|
||||||
|
if is_even(n)
|
||||||
|
n ÷= 2
|
||||||
|
else
|
||||||
|
n = 3 * n + 1
|
||||||
|
end
|
||||||
|
steps += 1
|
||||||
|
end
|
||||||
|
return steps
|
||||||
|
end
|
||||||
15
julia/collatz-conjecture/runtests.jl
Normal file
15
julia/collatz-conjecture/runtests.jl
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Test
|
||||||
|
|
||||||
|
include("collatz-conjecture.jl")
|
||||||
|
|
||||||
|
@testset verbose = true "tests" begin
|
||||||
|
# canonical data
|
||||||
|
@testset "Canonical data" begin
|
||||||
|
@test collatz_steps(1) == 0
|
||||||
|
@test collatz_steps(16) == 4
|
||||||
|
@test collatz_steps(12) == 9
|
||||||
|
@test collatz_steps(1000000) == 152
|
||||||
|
@test_throws DomainError collatz_steps(0)
|
||||||
|
@test_throws DomainError collatz_steps(-15)
|
||||||
|
end
|
||||||
|
end
|
||||||
21
julia/currency-exchange/.exercism/config.json
Normal file
21
julia/currency-exchange/.exercism/config.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"colinleach"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"currency-exchange.jl"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"runtests.jl"
|
||||||
|
],
|
||||||
|
"exemplar": [
|
||||||
|
".meta/exemplar.jl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"forked_from": [
|
||||||
|
"python/currency-exchange"
|
||||||
|
],
|
||||||
|
"icon": "hyperia-forex",
|
||||||
|
"blurb": "Learn about numbers by solving Chandler's currency exchange conundrums."
|
||||||
|
}
|
||||||
1
julia/currency-exchange/.exercism/metadata.json
Normal file
1
julia/currency-exchange/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"julia","exercise":"currency-exchange","id":"57c3aae34f93402a953b2840e9fbe904","url":"https://exercism.org/tracks/julia/exercises/currency-exchange","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user