Compare commits
12 Commits
8db600e59a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8296c79f68 | |||
| 5c52e8e34d | |||
| 83538317ee | |||
| b44d1e941f | |||
| 33b8f2e204 | |||
| 888b84bef9 | |||
| 6db87ea8f4 | |||
| 6d6148d66d | |||
| 9dea7a1fcc | |||
| 09bf8bc515 | |||
| f26ebd97f2 | |||
| 0b66c3cdf7 |
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
|
||||
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/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}
|
||||
34
julia/currency-exchange/HELP.md
Normal file
34
julia/currency-exchange/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 currency-exchange.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/).
|
||||
41
julia/currency-exchange/HINTS.md
Normal file
41
julia/currency-exchange/HINTS.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Hints
|
||||
|
||||
## General
|
||||
|
||||
## 1. Estimate value after exchange
|
||||
|
||||
- You can use the [division operator][division-operator] to get the value of exchanged currency.
|
||||
|
||||
## 2. Calculate currency left after an exchange
|
||||
|
||||
- You can use the [subtraction operator][subtraction-operator] to get the amount of change.
|
||||
|
||||
## 3. Calculate value of bills
|
||||
|
||||
- You can use the [multiplication operator][multiplication-operator] to get the value of bills.
|
||||
|
||||
## 4. Calculate number of bills
|
||||
|
||||
- You need to divide `amount` into `denomination`.
|
||||
- To remove decimal places from a floating point value, you can use the [`floor()`][floor] function.
|
||||
- You need an integer to get the exact number of bills.
|
||||
The `floor` function can take the desired output type as a parameter.
|
||||
|
||||
**Note:** The [`÷` operator][div], or `div()` function, also does floor division. However, if an operand is floating point the result is still floating point.
|
||||
|
||||
## 5. Calculate leftover after exchanging into bills
|
||||
|
||||
- You need to find the remainder of `amount` that does not equal a whole `denomination`.
|
||||
- The [Modulo operator][div] `%` can help find the remainder.
|
||||
|
||||
## 6. Calculate value after exchange
|
||||
|
||||
- You need to calculate `spread` percent of `exchange_rate` using multiplication operator and add it to `exchange_rate` to get the exchanged currency.
|
||||
- The actual rate needs to be computed. Remember to add exchange _rate_ and exchange _fee_.
|
||||
- You can get exchanged money affected by commission by using a divide operation, plus type casting to integer if necessary.
|
||||
|
||||
[division-operator]: https://docs.julialang.org/en/v1/manual/mathematical-operations/#Arithmetic-Operators
|
||||
[multiplication-operator]: https://docs.julialang.org/en/v1/manual/mathematical-operations/#Arithmetic-Operators#Arithmetic-Operators
|
||||
[subtraction-operator]: https://docs.julialang.org/en/v1/manual/mathematical-operations/#Arithmetic-Operators
|
||||
[floor]: https://docs.julialang.org/en/v1/base/math/#Base.floor
|
||||
[div]: https://benlauwens.github.io/ThinkJulia.jl/latest/book.html#_floor_division_and_modulus
|
||||
313
julia/currency-exchange/README.md
Normal file
313
julia/currency-exchange/README.md
Normal file
@@ -0,0 +1,313 @@
|
||||
# Currency Exchange
|
||||
|
||||
Welcome to Currency Exchange 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
|
||||
|
||||
Julia is a general-purpose language that can be used for most programming tasks.
|
||||
In practice, however, the main use cases tend to be in engineering and science.
|
||||
Fast, versatile, sophisticated numerical calculations are central to the design.
|
||||
|
||||
## Integers
|
||||
|
||||
An integer is a "round" number with no decimal point.
|
||||
|
||||
In the [Basics][basics] concept, we saw that an integer value can be assigned to a variable without specifying a type.
|
||||
|
||||
For readability, underscores can be used as a digit separator.
|
||||
They are ignored by the compiler.
|
||||
|
||||
```julia-repl
|
||||
julia> x = 3
|
||||
3
|
||||
|
||||
julia> typeof(x)
|
||||
Int64
|
||||
|
||||
julia> large_number = 1_234_567_890
|
||||
1234567890
|
||||
```
|
||||
|
||||
|
||||
## Floating-point
|
||||
|
||||
It will be no surprise that floating-point numbers have a decimal point, and a fractional part after the point.
|
||||
|
||||
```julia-repl
|
||||
julia> f = 3.45
|
||||
3.45
|
||||
|
||||
julia> typeof(f)
|
||||
Float64
|
||||
```
|
||||
|
||||
Of course, scientific notation is supported.
|
||||
|
||||
```julia-repl
|
||||
julia> avogadro = 6.02e23
|
||||
6.02e23
|
||||
```
|
||||
|
||||
The maximum and minimum values may come as a surprise:
|
||||
|
||||
```julia-repl
|
||||
julia> typemax(Float64)
|
||||
Inf
|
||||
|
||||
julia> typemin(Float64)
|
||||
-Inf
|
||||
```
|
||||
|
||||
Infinity is a valid value!
|
||||
|
||||
## Arithmetic operators
|
||||
|
||||
As discussed in the Basics concept, arithmetic operators mostly work the same as standard arithmetic, as taught to children.
|
||||
Note that exponentiation uses `^`, _not_ `**` (both are common in other languages).
|
||||
|
||||
```julia
|
||||
2 + 3 # 5 (addition)
|
||||
2 - 3 # -1 (subtraction)
|
||||
2 * 3 # 6 (multiplication)
|
||||
8 / 2 # 4.0 (division)
|
||||
8 % 3 # 2 (remainder)
|
||||
2 ^ 3 # 8 (exponentiation)
|
||||
```
|
||||
|
||||
However, a few Julia-specific details are worth discussing.
|
||||
|
||||
### Multiplication
|
||||
|
||||
```julia-repl
|
||||
julia> x = 4.2
|
||||
4.2
|
||||
|
||||
julia> 2 * x
|
||||
8.4
|
||||
|
||||
julia> 2x
|
||||
8.4
|
||||
|
||||
julia> 2.4x
|
||||
10.08
|
||||
```
|
||||
|
||||
That may be surprising.
|
||||
|
||||
It is always possible to use `*` as an infix operator, as in most other computer languages.
|
||||
|
||||
However, Julia is designed by people who believe that code should look as much as possible like mathematical equations.
|
||||
|
||||
Because variable names must start with a letter, prefacing the name with a number (integer or floating-point) is treated as implicit multiplication.
|
||||
|
||||
For example, if we want the surface area of a sphere, instead of `4 * pi * r * r` we could do this :
|
||||
|
||||
```julia-repl
|
||||
julia> surface(r) = 4π * r^2
|
||||
surface (generic function with 1 method)
|
||||
|
||||
julia> surface(3)
|
||||
113.09733552923255
|
||||
```
|
||||
|
||||
Although π is a built-in constant, it is also a (Greek) letter.
|
||||
The parser therefore still needs one explicit `*` to separate `π` from `r`.
|
||||
|
||||
### Division
|
||||
|
||||
Using `/` as the infix operator will always give a floating-point result, even for integer inputs.
|
||||
|
||||
For integer division, there are more options.
|
||||
|
||||
```julia-repl
|
||||
julia> 10 / 3 # floating-point division
|
||||
3.3333333333333335
|
||||
|
||||
julia> div(10, 3) # integer division
|
||||
3
|
||||
|
||||
julia> 10 ÷ 3 # synonym for div()
|
||||
3
|
||||
|
||||
julia> 10 // 3 # rational number (fraction)
|
||||
10//3
|
||||
```
|
||||
|
||||
The `div()` function is for integer division, with the result truncated towards zero: downwards for positive numbers, upwards for negative numbers.
|
||||
|
||||
As a synonym, we can use the infix operator `÷`, again aiming to make it look more mathematical.
|
||||
If you are using a Julia-aware editor, enter this as `\div` then hit the `<Tab>` key.
|
||||
|
||||
The `//` operator is beyond the scope of this Concept.
|
||||
For now, we can just say that the result of `//` is a "rational" number, which most people call a _fraction_.
|
||||
|
||||
## Conversion of numeric types
|
||||
|
||||
This can often happen automatically:
|
||||
|
||||
```julia-repl
|
||||
julia> x = 2 + 3.5
|
||||
5.5
|
||||
|
||||
julia> typeof(x)
|
||||
Float64
|
||||
```
|
||||
|
||||
We added an `Int64` to a `Float64`, and got a `Float64` result.
|
||||
|
||||
In fact, the integer was silently converted to a `Float64` before doing the addition.
|
||||
|
||||
**Float-to-integer** conversions are inevitably more complicated.
|
||||
What do you want to do with anything after the decimal point?
|
||||
|
||||
- The `round()` function converts to the nearest whole number, with ties such as 4.5 rounding to the nearest _even_ whole number.
|
||||
- `floor()` rounds down, `ceil()` rounds up, `trunc()` rounds towards zero.
|
||||
- Attempting to cast directly, for example with `Int32()`, will fail with an `InexactError`.
|
||||
|
||||
However, by default these functions do not return the integer type you might have wanted.
|
||||
The desired output type can be specified.
|
||||
|
||||
```julia-repl
|
||||
julia> round(4.5)
|
||||
4.0
|
||||
|
||||
julia> ceil(Int, 4.3)
|
||||
5
|
||||
```
|
||||
|
||||
Rounding to a specified number of digits after the decimal point is also possible with the `digits` keyword.
|
||||
|
||||
```julia-repl
|
||||
julia> round(π, digits=10)
|
||||
3.1415926536
|
||||
```
|
||||
|
||||
## Divide-by-zero
|
||||
|
||||
Surely this just throws an error?
|
||||
In fact, the situation is not that simple.
|
||||
|
||||
Integer division with `÷` or `//` will result in an error, as you might expect.
|
||||
|
||||
Floating-point division with `/` takes what might be considered an engineering approach, rather than a standard computer science approach:
|
||||
|
||||
```julia-repl
|
||||
julia> 2 / 0
|
||||
Inf
|
||||
|
||||
julia> 0 / 0
|
||||
NaN
|
||||
```
|
||||
|
||||
As discussed in a previous section, infinity is a valid floating-point number in Julia, represented by `Inf`.
|
||||
|
||||
When the numerator is also zero, the result is mathematically undefined.
|
||||
Julia then treats it as "not a number", represented by `NaN`.
|
||||
|
||||
[basics]: https://exercism.org/tracks/julia/concepts/basics
|
||||
|
||||
## Instructions
|
||||
|
||||
Your friend Chandler plans to visit exotic countries all around the world. Sadly, Chandler's math skills aren't good. He's pretty worried about being scammed by currency exchanges during his trip - and he wants you to make a currency calculator for him. Here are his specifications for the app:
|
||||
|
||||
## 1. Estimate value after exchange
|
||||
|
||||
Create the `exchange_money()` function, taking 2 parameters:
|
||||
|
||||
1. `budget` : The amount of money you are planning to exchange.
|
||||
2. `exchange_rate` : The amount of domestic currency equal to one unit of foreign currency.
|
||||
|
||||
This function should return the value of the exchanged currency.
|
||||
|
||||
**Note:** If your currency is USD and you want to exchange USD for EUR with an exchange rate of `1.20`, then `1.20 USD == 1 EUR`.
|
||||
|
||||
```julia
|
||||
julia> exchange_money(127.5, 1.2)
|
||||
106.25
|
||||
```
|
||||
|
||||
## 2. Calculate currency left after an exchange
|
||||
|
||||
Create the `get_change()` function, taking 2 parameters:
|
||||
|
||||
1. `budget` : Amount of money before exchange.
|
||||
2. `exchanging_value` : Amount of money that is *taken* from the budget to be exchanged.
|
||||
|
||||
This function should return the amount of money that *is left* from the budget.
|
||||
|
||||
```julia
|
||||
julia> get_change(127.5, 120)
|
||||
7.5
|
||||
```
|
||||
|
||||
## 3. Calculate value of bills
|
||||
|
||||
Create the `get_value_of_bills()` function, taking 2 parameters:
|
||||
|
||||
1. `denomination` : The value of a single bill.
|
||||
2. `number_of_bills` : The total number of bills.
|
||||
|
||||
This exchanging booth only deals in cash of certain increments.
|
||||
The total you receive must be divisible by the value of one "bill" or unit, which can leave behind a fraction or remainder.
|
||||
Your function should return only the total value of the bills (_excluding fractional amounts_) the booth would give back.
|
||||
Unfortunately, the booth gets to keep the remainder/change as an added bonus.
|
||||
|
||||
```julia
|
||||
julia> get_value_of_bills(5, 128)
|
||||
640
|
||||
```
|
||||
|
||||
## 4. Calculate number of bills
|
||||
|
||||
Create the `get_number_of_bills()` function, taking `amount` and `denomination`.
|
||||
|
||||
This function should return the _number of currency bills_ that you can receive within the given _amount_.
|
||||
In other words: How many _whole bills_ of currency fit into the starting amount?
|
||||
Remember -- you can only receive _whole bills_, not fractions of bills, so remember to divide accordingly.
|
||||
Effectively, you are rounding _down_ to the nearest whole bill/denomination.
|
||||
|
||||
```julia
|
||||
julia> get_number_of_bills(127.5, 5)
|
||||
25
|
||||
```
|
||||
|
||||
## 5. Calculate leftover after exchanging into bills
|
||||
|
||||
Create the `get_leftover_of_bills()` function, taking `amount` and `denomination`.
|
||||
|
||||
This function should return the _leftover amount_ that cannot be returned from your starting _amount_ given the denomination of bills.
|
||||
It is very important to know exactly how much the booth gets to keep.
|
||||
|
||||
```julia
|
||||
julia> get_leftover_of_bills(127.5, 20)
|
||||
7.5
|
||||
```
|
||||
|
||||
## 6. Calculate value after exchange
|
||||
|
||||
Create the `exchangeable_value()` function, taking `budget`, `exchange_rate`, `spread`, and `denomination`.
|
||||
|
||||
Parameter `spread` is the *percentage taken* as an exchange fee, written as an integer.
|
||||
It needs to be converted to decimal by dividing it by 100.
|
||||
If `1.00 EUR == 1.20 USD` and the *spread* is `10`, the actual exchange rate will be: `1.00 EUR == 1.32 USD` because 10% of 1.20 is 0.12, and this additional fee is added to the exchange.
|
||||
|
||||
This function should return the maximum value of the new currency after calculating the *exchange rate* plus the *spread*.
|
||||
Remember that the currency *denomination* is a whole number, and cannot be sub-divided.
|
||||
|
||||
**Note:** Returned value should be an integer type.
|
||||
|
||||
```julia
|
||||
julia> exchangeable_value(127.25, 1.20, 10, 20)
|
||||
80
|
||||
|
||||
julia> exchangeable_value(127.25, 1.20, 10, 5)
|
||||
95
|
||||
```
|
||||
|
||||
## Source
|
||||
|
||||
### Created by
|
||||
|
||||
- @colinleach
|
||||
24
julia/currency-exchange/currency-exchange.jl
Normal file
24
julia/currency-exchange/currency-exchange.jl
Normal file
@@ -0,0 +1,24 @@
|
||||
function exchange_money(budget, exchange_rate)
|
||||
return budget / exchange_rate
|
||||
end
|
||||
|
||||
function get_change(budget, exchanging_value)
|
||||
return budget - exchanging_value
|
||||
end
|
||||
|
||||
function get_value_of_bills(denomination, number_of_bills)
|
||||
return denomination * number_of_bills
|
||||
end
|
||||
|
||||
function get_number_of_bills(amount, denomination)
|
||||
return amount ÷ denomination
|
||||
end
|
||||
|
||||
function get_leftover_of_bills(amount, denomination)
|
||||
return amount % denomination
|
||||
end
|
||||
|
||||
function exchangeable_value(budget, exchange_rate, spread, denomination)
|
||||
actual_exchange_rate = exchange_rate * (1 + spread / 100)
|
||||
return floor(Int, budget / actual_exchange_rate / denomination) * denomination
|
||||
end
|
||||
41
julia/currency-exchange/runtests.jl
Normal file
41
julia/currency-exchange/runtests.jl
Normal file
@@ -0,0 +1,41 @@
|
||||
using Test
|
||||
|
||||
include("currency-exchange.jl")
|
||||
|
||||
@testset verbose = true "tests" begin
|
||||
@testset "1. exchange_money" begin
|
||||
@test isapprox(exchange_money(100000, 0.8), 125000; atol=0.1)
|
||||
@test isapprox(exchange_money(700000, 10.0), 70000; atol=0.1)
|
||||
end
|
||||
|
||||
@testset "2. get_change" begin
|
||||
@test isapprox(get_change(463000, 5000), 458000; atol=0.1)
|
||||
@test isapprox(get_change(1250, 120), 1130; atol=0.1)
|
||||
@test isapprox(get_change(15000, 1380), 13620; atol=0.1)
|
||||
end
|
||||
|
||||
@testset "3. get_value_of_bills" begin
|
||||
@test get_value_of_bills(10000, 128) == 1280000
|
||||
@test get_value_of_bills(50, 360) == 18000
|
||||
@test get_value_of_bills(200, 200) == 40000
|
||||
end
|
||||
|
||||
@testset "4. get_number_of_bills" begin
|
||||
@test get_number_of_bills(163270, 50000) == 3
|
||||
@test get_number_of_bills(54361, 1000) == 54
|
||||
end
|
||||
|
||||
@testset "5. get_leftover_of_bills" begin
|
||||
@test isapprox(get_leftover_of_bills(10.1, 10), 0.1; atol=1e-8)
|
||||
@test isapprox(get_leftover_of_bills(654321.0, 5), 1.0; atol=1e-8)
|
||||
@test isapprox(get_leftover_of_bills(3.14, 2) , 1.14; atol=1e-8)
|
||||
end
|
||||
|
||||
@testset "6. exchangeable_value" begin
|
||||
@test exchangeable_value(100000, 10.61, 10, 1) == 8568
|
||||
@test exchangeable_value(1500, 0.84, 25, 40) == 1400
|
||||
@test exchangeable_value(470000, 1050, 30, 10000000000) == 0
|
||||
@test exchangeable_value(470000, 0.00000009, 30, 700) == 4017094016600
|
||||
@test exchangeable_value(425.33, 0.0009, 30, 700) == 363300
|
||||
end
|
||||
end
|
||||
21
julia/darts/.exercism/config.json
Normal file
21
julia/darts/.exercism/config.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"authors": [
|
||||
"vyu"
|
||||
],
|
||||
"contributors": [
|
||||
"cmcaine"
|
||||
],
|
||||
"files": {
|
||||
"solution": [
|
||||
"darts.jl"
|
||||
],
|
||||
"test": [
|
||||
"runtests.jl"
|
||||
],
|
||||
"example": [
|
||||
".meta/example.jl"
|
||||
]
|
||||
},
|
||||
"blurb": "Calculate the points scored in a single toss of a Darts game.",
|
||||
"source": "Inspired by an exercise created by a professor Della Paolera in Argentina"
|
||||
}
|
||||
1
julia/darts/.exercism/metadata.json
Normal file
1
julia/darts/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
||||
{"track":"julia","exercise":"darts","id":"f274fafe88954ab6bace971d3f6adf19","url":"https://exercism.org/tracks/julia/exercises/darts","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||
34
julia/darts/HELP.md
Normal file
34
julia/darts/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 darts.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/).
|
||||
50
julia/darts/README.md
Normal file
50
julia/darts/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Darts
|
||||
|
||||
Welcome to Darts on Exercism's Julia Track.
|
||||
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||
|
||||
## Instructions
|
||||
|
||||
Calculate the points scored 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).
|
||||
|
||||
Given a point in the target (defined by its [Cartesian coordinates][cartesian-coordinates] `x` and `y`, where `x` and `y` are [real][real-numbers]), calculate the correct score earned by a dart landing at that point.
|
||||
|
||||
## Credit
|
||||
|
||||
The scoreboard image was created by [habere-et-dispertire][habere-et-dispertire] using [Inkscape][inkscape].
|
||||
|
||||
[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
|
||||
[habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire
|
||||
[inkscape]: https://en.wikipedia.org/wiki/Inkscape
|
||||
|
||||
## Source
|
||||
|
||||
### Created by
|
||||
|
||||
- @vyu
|
||||
|
||||
### Contributed to by
|
||||
|
||||
- @cmcaine
|
||||
|
||||
### Based on
|
||||
|
||||
Inspired by an exercise created by a professor Della Paolera in Argentina
|
||||
12
julia/darts/darts.jl
Normal file
12
julia/darts/darts.jl
Normal file
@@ -0,0 +1,12 @@
|
||||
function score(x, y)
|
||||
dist = sqrt(x^2 + y^2)
|
||||
if dist <= 1
|
||||
return 10
|
||||
elseif dist <= 5
|
||||
return 5
|
||||
elseif dist <= 10
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
57
julia/darts/runtests.jl
Normal file
57
julia/darts/runtests.jl
Normal file
@@ -0,0 +1,57 @@
|
||||
using Test
|
||||
|
||||
include("darts.jl")
|
||||
|
||||
@testset verbose = true "tests" begin
|
||||
@testset "Missed target" begin
|
||||
@test score(-9, 9) == 0
|
||||
end
|
||||
|
||||
@testset "On the outer circle" begin
|
||||
@test score(0, 10) == 1
|
||||
end
|
||||
|
||||
@testset "On the middle circle" begin
|
||||
@test score(-5, 0) == 5
|
||||
end
|
||||
|
||||
@testset "On the inner circle" begin
|
||||
@test score(0, -1) == 10
|
||||
end
|
||||
|
||||
@testset "Exactly on centre" begin
|
||||
@test score(0, 0) == 10
|
||||
end
|
||||
|
||||
@testset "Near the centre" begin
|
||||
@test score(-0.1, -0.1) == 10
|
||||
end
|
||||
|
||||
@testset "Just within the inner circle" begin
|
||||
@test score(0.7, 0.7) == 10
|
||||
end
|
||||
|
||||
@testset "Just outside the inner circle" begin
|
||||
@test score(0.8, -0.8) == 5
|
||||
end
|
||||
|
||||
@testset "Just within the middle circle" begin
|
||||
@test score(-3.5, 3.5) == 5
|
||||
end
|
||||
|
||||
@testset "Just outside the middle circle" begin
|
||||
@test score(-3.6, -3.6) == 1
|
||||
end
|
||||
|
||||
@testset "Just within the outer circle" begin
|
||||
@test score(-7.0, 7.0) == 1
|
||||
end
|
||||
|
||||
@testset "Just outside the outer circle" begin
|
||||
@test score(7.1, -7.1) == 0
|
||||
end
|
||||
|
||||
@testset "Asymmetric position between the inner and middle circles" begin
|
||||
@test score(0.5, -4) == 5
|
||||
end
|
||||
end
|
||||
23
julia/difference-of-squares/.exercism/config.json
Normal file
23
julia/difference-of-squares/.exercism/config.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"authors": [
|
||||
"SaschaMann"
|
||||
],
|
||||
"contributors": [
|
||||
"bovine3dom",
|
||||
"cmcaine"
|
||||
],
|
||||
"files": {
|
||||
"solution": [
|
||||
"difference-of-squares.jl"
|
||||
],
|
||||
"test": [
|
||||
"runtests.jl"
|
||||
],
|
||||
"example": [
|
||||
".meta/example.jl"
|
||||
]
|
||||
},
|
||||
"blurb": "Find the difference between the square of the sum and the sum of the squares of the first N natural numbers.",
|
||||
"source": "Problem 6 at Project Euler",
|
||||
"source_url": "https://projecteuler.net/problem=6"
|
||||
}
|
||||
1
julia/difference-of-squares/.exercism/metadata.json
Normal file
1
julia/difference-of-squares/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
||||
{"track":"julia","exercise":"difference-of-squares","id":"43726f141cb245cbb9d9c8b9cbdc4dc7","url":"https://exercism.org/tracks/julia/exercises/difference-of-squares","handle":"Kimawari","is_requester":true,"auto_approve":false}
|
||||
34
julia/difference-of-squares/HELP.md
Normal file
34
julia/difference-of-squares/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 difference-of-squares.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/).
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user