├── src
├── tools
│ ├── testdata
│ │ ├── blint_she_bang
│ │ ├── blint_tabs
│ │ ├── blint_brief_doc
│ │ ├── blint_readonly_tests
│ │ └── blint_signature
│ ├── p.sh
│ ├── bdoc_test.sh
│ ├── blint_test.sh
│ └── bdoc.sh
├── database
│ ├── p.sh
│ └── sql.sh
├── testing
│ ├── p.sh
│ └── testdata
│ │ └── test_bunit_junitxml.xml
├── ui
│ ├── p.sh
│ ├── ui.sh
│ ├── textui_test.sh
│ └── whiptail_test.sh
├── container
│ └── p.sh
├── net
│ ├── p.sh
│ ├── handler.sh
│ ├── response
│ ├── request.sh
│ └── response.sh
├── sync
│ ├── p.sh
│ ├── wait_group_test.sh
│ ├── mutex_test.sh
│ ├── mutex.sh
│ ├── wait_group.sh
│ ├── atomic_int_test.sh
│ ├── chan.sh
│ └── chan_test.sh
├── util
│ ├── char.sh
│ ├── p.sh
│ ├── binary.sh
│ ├── binary_test.sh
│ ├── set_test.sh
│ ├── os_test.sh
│ ├── filepath.sh
│ ├── rand_test.sh
│ ├── set.sh
│ ├── filepath_test.sh
│ ├── complex_test.sh
│ ├── rand.sh
│ ├── fileinfo.sh
│ ├── os.sh
│ ├── map.sh
│ └── regexp_test.sh
├── p.sh
├── lang
│ ├── p.sh
│ ├── pipe_test.sh
│ ├── runtime_test.sh
│ ├── os_test.sh
│ ├── result_test.sh
│ ├── runtime.sh
│ ├── bash_test.sh
│ ├── result.sh
│ ├── log_test.sh
│ ├── pipe.sh
│ ├── bash.sh
│ ├── core_test.sh
│ ├── int_test.sh
│ ├── jqmem.sh
│ ├── log.sh
│ ├── sys_test.sh
│ ├── os.sh
│ ├── x.sh
│ └── int.sh
├── external
│ └── git
│ │ └── testdata
│ │ ├── tags.json
│ │ └── branches.json
└── internal
│ └── ci
├── doc
├── gobash.gif
├── api.rst
├── design.rst
├── next.rst
├── Makefile
├── process-communication.rst
├── style.md
├── make.bat
├── conf.py
├── collections.rst
├── command-line-flags.rst
├── interactive-mode.rst
├── motivation.rst
└── index.rst
├── .gitignore
├── examples
├── tour
│ ├── welcome_ex
│ ├── rand_ex
│ ├── func_ex
│ ├── infinite_ex
│ ├── case_details_ex
│ ├── if_ex
│ ├── variables_details_ex
│ ├── fact_ex
│ ├── until_ex
│ ├── while_ex
│ ├── variables_ex
│ ├── select_details_ex
│ ├── select_ex
│ ├── test_cmd_ex
│ ├── loops_funcs_ex
│ ├── case_ex
│ ├── for_ex
│ ├── if_else_ex
│ └── tour_test.sh
├── playground
│ ├── hellow_world_ex
│ ├── ring_len_ex
│ ├── clear_screen_ex
│ ├── sleep_ex
│ ├── ring_prev_ex
│ ├── ring_next_ex
│ ├── ring_move_ex
│ ├── ring_unlink_ex
│ ├── ring_do_ex
│ ├── list_ex
│ ├── ring_link_ex
│ ├── http_server_ex
│ ├── concurrent_pi_ex
│ ├── test_function_ex
│ └── playground_test.sh
├── user_ex
├── file_ex
├── chan_ex
├── list_ex
├── map_ex
├── hello_world_ex
├── text_spinner_ex
├── log_ex
├── to_json_ex
├── text_menu_ex
├── strings_ex
├── flags_ex
├── anonymous_struct_ex
├── text_progress_ex
├── to_string_ex
├── template_ex
├── regexp_ex
├── methods_ex
├── error_ex
├── linked_list_ex
├── structs_ex
├── mutex_counter_ex
├── wait_group_ex
├── whiptail_ex
├── web_server_ex
├── result_ex
├── shapes_ex
├── flags_details_ex
├── binary_trees_ex
└── visitor_ex
├── .github
└── workflows
│ ├── lint.yml
│ ├── test-mac.yml
│ ├── test-bash5.yml
│ ├── test-bash3.yml
│ ├── test-bash4.yml
│ └── documentation.yml
├── hsabog
├── LICENSE
└── gobash_test.sh
/src/tools/testdata/blint_she_bang:
--------------------------------------------------------------------------------
1 |
2 | x=33
3 |
--------------------------------------------------------------------------------
/src/tools/testdata/blint_tabs:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # why does anyone use tabs
4 |
--------------------------------------------------------------------------------
/doc/gobash.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EngineeringSoftware/gobash/HEAD/doc/gobash.gif
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | *~
3 | .objects/
4 | doc/_build
5 |
6 | # Auto generated in CI.
7 | doc/apis/
8 |
--------------------------------------------------------------------------------
/src/tools/testdata/blint_brief_doc:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 |
4 | function nothing() {
5 | :
6 | }
7 |
--------------------------------------------------------------------------------
/src/tools/testdata/blint_readonly_tests:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function test_x() {
4 | :
5 | }
6 |
--------------------------------------------------------------------------------
/src/tools/testdata/blint_signature:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function fun()
4 | {
5 | :
6 | }
7 |
8 | function fun2() {
9 | :
10 | }
11 |
--------------------------------------------------------------------------------
/examples/tour/welcome_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Illustrate printing to stdout and the date command.
4 |
5 | echo "Welcome to bash"
6 | echo "The time is $(date)"
7 |
--------------------------------------------------------------------------------
/examples/tour/rand_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
4 | . ${DIR}/../../gobash
5 |
6 |
7 | echo "My favorite number is $(rand_intn 10)"
8 |
--------------------------------------------------------------------------------
/examples/tour/func_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function add() {
4 | local -r x="${1}"
5 | local -r y="${2}"
6 | shift 2
7 |
8 | bc <<< "${x} + ${y}"
9 | }
10 |
11 | add 42 14
12 |
--------------------------------------------------------------------------------
/examples/tour/infinite_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function main() {
4 | while :; do
5 | :
6 | done
7 | }
8 |
9 | # Uncomment if you want to be in an infinite loop.
10 | # main
11 |
--------------------------------------------------------------------------------
/doc/api.rst:
--------------------------------------------------------------------------------
1 | API
2 | ===
3 |
4 | .. This file will be auto generated in CI (and the content here might
5 | not reflect the actual status).
6 |
7 | .. toctree::
8 | :maxdepth: 2
9 | :caption: Packages:
10 |
--------------------------------------------------------------------------------
/examples/tour/case_details_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # More details for the case statement.
4 |
5 | case $(date +%b) in
6 | "Aug"|"Sep") echo "School time";;
7 | "Dec") echo "Food time";;
8 | *) echo "Something else";;
9 | esac
10 |
--------------------------------------------------------------------------------
/examples/playground/hellow_world_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example that corresponds to "Hello, World!" on https://go.dev/play/.
4 | # License for the corresponding code https://go.dev/LICENSE?m=text.
5 |
6 | echo "Hello, 世界"
7 |
8 | # OR
9 |
10 | printf "Hello, 世界\n"
11 |
--------------------------------------------------------------------------------
/examples/user_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # User API examples.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | # Obtain info (User) for the current user.
10 | u=$(user_current)
11 | # Print values.
12 | $u username
13 | $u uid
14 | $u gid
15 | $u home
16 |
--------------------------------------------------------------------------------
/src/database/p.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Database package.
6 |
7 | if [ -n "${DATABASE_PACKAGE:-}" ]; then return 0; fi
8 | readonly DATABASE_PACKAGE=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${DATABASE_PACKAGE}/sql.sh
11 |
--------------------------------------------------------------------------------
/src/tools/p.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Tools package.
6 |
7 | if [ -n "${TOOLS_PACKAGE:-}" ]; then return 0; fi
8 | readonly TOOLS_PACKAGE=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${TOOLS_PACKAGE}/blint.sh
11 | . ${TOOLS_PACKAGE}/bdoc.sh
12 |
--------------------------------------------------------------------------------
/src/testing/p.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Testing package.
6 |
7 | if [ -n "${TESTING_PACKAGE:-}" ]; then return 0; fi
8 | readonly TESTING_PACKAGE=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${TESTING_PACKAGE}/bunit.sh
11 | . ${TESTING_PACKAGE}/testt.sh
12 |
--------------------------------------------------------------------------------
/src/ui/p.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # UI package.
6 |
7 | if [ -n "${UI_PACKAGE:-}" ]; then return 0; fi
8 | readonly UI_PACKAGE=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${UI_PACKAGE}/ui.sh
11 | . ${UI_PACKAGE}/whiptail.sh
12 | . ${UI_PACKAGE}/textui.sh
13 |
--------------------------------------------------------------------------------
/src/container/p.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Container package.
6 |
7 | if [ -n "${CONTAINER_PACKAGE:-}" ]; then return 0; fi
8 | readonly CONTAINER_PACKAGE=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${CONTAINER_PACKAGE}/ring.sh
11 | . ${CONTAINER_PACKAGE}/list.sh
12 |
--------------------------------------------------------------------------------
/src/net/p.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Net package.
6 |
7 | if [ -n "${NET_PACKAGE:-}" ]; then return 0; fi
8 | readonly NET_PACKAGE=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${NET_PACKAGE}/http.sh
11 | . ${NET_PACKAGE}/request.sh
12 | . ${NET_PACKAGE}/response.sh
13 |
--------------------------------------------------------------------------------
/examples/tour/if_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function sqrt() {
4 | local x="${1}"
5 | shift 1
6 |
7 | if [ ${x} -lt 0 ]; then
8 | local v=$(sqrt $(( -${x} )))
9 | echo "${v}i"
10 | return 0
11 | fi
12 |
13 | bc <<< "scale=4; sqrt(${x})"
14 | }
15 |
16 | sqrt 2
17 | sqrt -4
18 |
--------------------------------------------------------------------------------
/examples/tour/variables_details_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function split() {
4 | local sum="${1}"
5 | shift 1
6 |
7 | x=$(bc <<< "${sum} * 4 / 9")
8 | y=$(bc <<< "${sum} - ${x}")
9 | }
10 |
11 | function main() {
12 | local x
13 | local y
14 | split 17
15 |
16 | echo "${x} ${y}"
17 | }
18 |
19 | main
20 |
--------------------------------------------------------------------------------
/examples/tour/fact_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Factorial example using recursion.
4 |
5 | function fact() {
6 | local -r n="${1}"
7 | shift 1
8 |
9 | [ ${n} -eq 0 ] && echo 1 && return 0
10 |
11 | # Recursion on the next line.
12 | bc <<< "${n} * $(fact $(( ${n} - 1 )))"
13 | }
14 |
15 | for i in $(seq 1 10); do
16 | fact "${i}"
17 | done
18 |
--------------------------------------------------------------------------------
/examples/file_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example with file API.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | f=$(os_mktemp_file)
10 | cat << END > "${f}"
11 | some
12 | random
13 | text
14 | END
15 |
16 | file_insert_at "${f}" 1 "insert first"
17 | file_remove_at "${f}" 2
18 | cat "${f}"
19 |
20 | fi=$(os_stat "${f}")
21 | $fi size
22 |
--------------------------------------------------------------------------------
/src/sync/p.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Sync package.
6 |
7 | if [ -n "${SYNC_PACKAGE:-}" ]; then return 0; fi
8 | readonly SYNC_PACKAGE=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${SYNC_PACKAGE}/mutex.sh
11 | . ${SYNC_PACKAGE}/atomic_int.sh
12 | . ${SYNC_PACKAGE}/chan.sh
13 | . ${SYNC_PACKAGE}/wait_group.sh
14 |
--------------------------------------------------------------------------------
/examples/playground/ring_len_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example that corresponds to https://go.dev/play/p/MczNZHltM8W from
4 | # the go documentation (https://pkg.go.dev/container/ring).
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../../gobash
8 |
9 |
10 | function main() {
11 | local r=$(container_Ring 4)
12 |
13 | $r len
14 | }
15 |
16 | main
17 |
--------------------------------------------------------------------------------
/examples/tour/until_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # util loop example.
4 |
5 | # util loop is very much like the while loop, but the loop is executed
6 | # as long as the condition is false (non-zero value).
7 |
8 | function main() {
9 | local sum=1
10 | until [ ${sum} -eq 1000 ]; do
11 | sum=$(( ${sum} + 1 ))
12 | done
13 | printf "Sum is ${sum}.\n"
14 | }
15 |
16 | main
17 |
--------------------------------------------------------------------------------
/examples/tour/while_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # while loop example.
4 |
5 | # while loop executes the body of the loop as long as the condition is
6 | # true; condition is a test (which return 0 or non-zero).
7 |
8 | function main() {
9 | local sum=1
10 | while [ ${sum} -lt 1000 ]; do
11 | sum=$(( ${sum} + 1 ))
12 | done
13 | printf "Sum is ${sum}.\n"
14 | }
15 |
16 | main
17 |
--------------------------------------------------------------------------------
/examples/chan_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # This example shows a way to communicate among processes via channels.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | # Create a channel for communication.
10 | ch=$(Chan)
11 |
12 | # Spaw some processes that will be sending messages.
13 | ( $ch send 55 ) &
14 | ( $ch send 57 ) &
15 |
16 | # Recive messages (and print).
17 | $ch recv
18 | $ch recv
19 |
--------------------------------------------------------------------------------
/examples/list_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Introduces the `List` struct and several methods on it.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | # Create a list.
10 | lst=$(List)
11 |
12 | # Add several elements.
13 | $lst add 4
14 | $lst add 10
15 | $lst add 11
16 |
17 | # Print length.
18 | $lst len
19 |
20 | # There are a number of methods implemented for the `List`
21 | # struct. Check the API for details.
22 |
--------------------------------------------------------------------------------
/examples/map_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Introduces the `Map` struct and several methods on it.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | # Create a map.
10 | map=$(Map)
11 |
12 | # Add some mappings.
13 | $map put "a" 40
14 | $map put "b" 45
15 | $map put "c" 70
16 |
17 | # Print value for a given key.
18 | $map get "b"
19 | # 45
20 |
21 | $map get "d"
22 | # null
23 |
24 | # Print the length.
25 | $map len
26 |
--------------------------------------------------------------------------------
/examples/tour/variables_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | global="variable"
4 | echo "This is a global ${global}"
5 |
6 | readonly roglobal="readonly variable"
7 | echo "This is a global ${roglobal}"
8 |
9 | # Setting a readonly variable will print an error.
10 | # roglobal="new value"
11 |
12 | function func() {
13 | local x="${1}"
14 | local z="${2}"
15 | shift 2
16 |
17 | echo "Local variables x=${x} and z=${z}."
18 | }
19 |
20 | func 5 8
21 | # x and z are not available outside func.
22 |
--------------------------------------------------------------------------------
/examples/tour/select_details_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # More info about select loop.
4 |
5 | function main() {
6 | # Select loop needs a list of values just like a `for` loop,
7 | # thus you can provide those values by taking info from a file
8 | # (e.g., with `cat`) or providing all elements of an array.
9 | local a=( "value" "two" )
10 | select val in ${a[@]}; do
11 | echo ${val}
12 | [ "${val}" = "two" ] && break
13 | done
14 | }
15 |
16 | # main
17 |
--------------------------------------------------------------------------------
/doc/design.rst:
--------------------------------------------------------------------------------
1 |
2 | Design
3 | ======
4 |
5 | Key design goal: Implement everything using functions and files (i.e.,
6 | there is no need to change existing shells or existing user code).
7 |
8 | As the result, a user can adopt gobash as needed without being forced
9 | to rewrite any of their code. Rather, a user can benefit by using
10 | some/all of gobash features, which can be introduced gradually.
11 | Additionally, gobash should work with any `bash` version and OS that
12 | runs `bash`.
13 |
14 | .. toctree::
15 | :maxdepth: 2
16 |
--------------------------------------------------------------------------------
/examples/hello_world_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Intro.
4 |
5 | # The following lines "import" the entire `gobash` (functions,
6 | # structs, methods, globals).
7 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
8 | . ${DIR}/../gobash
9 |
10 |
11 | # You do not need to use anything from `gobash` if you do not need.
12 | echo "Hello gobash"
13 | # Output:
14 | # Hello gobash
15 |
16 | # `gobash` does NOT change the interpreter (or anything else on your
17 | # system). It is a pure shell implementation of a number of functions.
18 |
--------------------------------------------------------------------------------
/examples/tour/select_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Introduces select loop.
4 |
5 | # Select loop is a handy for creating simple command menus.
6 |
7 | function main() {
8 | local val
9 | # `val` will be set to the value selected by the user. If a
10 | # user selects number outside the range, `val` will be empty.
11 | select val in "apple" "pear" "watermellon"; do
12 | [ "${val}" = "pear" ] && break
13 | done
14 | }
15 |
16 | # Here, you will be in an infinite loop until you select "pear".
17 | #main
18 |
--------------------------------------------------------------------------------
/src/testing/testdata/test_bunit_junitxml.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
14 |
15 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/examples/tour/test_cmd_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # `test` command example.
4 | #
5 | # Not compatible with -e.
6 |
7 | # `test` is a shell builtin (at least in more recent version) used to
8 | # check files types and compare values.
9 |
10 | test 3 -lt 5
11 | echo "test: 0 == $?"
12 | # `man test` for details.
13 |
14 | # `[` command is very similar to `test`; the last argument is `]`.
15 | [ 3 -lt 5 ]
16 | echo "[: 0 == $?"
17 |
18 | # `[[` is an improved version available in bash, which supports
19 | # regular expressions, etc.
20 | [[ 3 < 5 ]]
21 | echo "[[: 0 == $?"
22 |
--------------------------------------------------------------------------------
/examples/text_spinner_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example of a text-based spinner.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | function main() {
10 | local -r spinner=$(TextSpinner)
11 |
12 | # Start the spinner (runs in background, but keep in mind it
13 | # goes to your stdout).
14 | $spinner start
15 |
16 | # Do here any work you wish until things are spinning.
17 | sleep 3
18 |
19 | # Stop the spinner.
20 | $spinner stop
21 | }
22 |
23 | main
24 |
--------------------------------------------------------------------------------
/examples/playground/clear_screen_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example that corresponds to "Clear Screen" on https://go.dev/play/.
4 | # License for the corresponding code https://go.dev/LICENSE?m=text.
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../../gobash
8 |
9 |
10 | function main() {
11 | local -r col=30
12 |
13 | local -i i
14 | for i in $(seq 0 "${col}"); do
15 | printf "\033c[$(strings_repeat = ${i})>]"
16 | sleep 0.1
17 | done
18 | printf " Done!\n"
19 | }
20 |
21 | main
22 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: lint
2 | run-name: ${{ github.ref_name }} lint
3 | on: [push, pull_request]
4 | concurrency:
5 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
6 | cancel-in-progress: true
7 | jobs:
8 | Test:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout the repo
12 | uses: actions/checkout@v4
13 | # We change the depth to find changed files.
14 | with:
15 | fetch-depth: 2
16 | - run: bash --version
17 | - name: Run lint
18 | run: ./src/internal/ci ci_lint
19 |
--------------------------------------------------------------------------------
/examples/tour/loops_funcs_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
4 | . ${DIR}/../../gobash
5 |
6 |
7 | function sqrt() {
8 | local -r x="${1}"
9 | shift 1
10 |
11 | local z=1
12 | local i
13 | while :; do
14 | local n=$(math_calc "${z} - (${z} * ${z} - ${x}) / (2 * ${z})")
15 | echo "${n}" "${z}"
16 | local abs=$(math_calc "${z} - ${n}" | tr -d -)
17 | math_lt "${abs}" "0.001" && break
18 | z=${n}
19 | done
20 | }
21 |
22 | sqrt 2
23 |
--------------------------------------------------------------------------------
/examples/playground/sleep_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example that corresponds to "Sleep" on https://go.dev/play/.
4 | # License for the corresponding code https://go.dev/LICENSE?m=text.
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../../gobash
8 |
9 |
10 | function main() {
11 | local -i i
12 | for i in $(seq 0 9); do
13 | local dur=$(bc <<< "scale=2; $(rand_intn 1000) / 1000" )
14 | printf "Sleeping for %g \n" "${dur}"
15 | sleep "${dur}"
16 | done
17 | printf "Done!\n"
18 | }
19 |
20 | main
21 |
--------------------------------------------------------------------------------
/src/util/char.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Util char functions.
6 |
7 | if [ -n "${CHAR_MOD:-}" ]; then return 0; fi
8 | readonly CHAR_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${CHAR_MOD}/../lang/p.sh
11 |
12 |
13 | # ----------
14 | # Functions.
15 |
16 | function char_alphabet() {
17 | # Print alphabet on a single line.
18 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
19 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
20 | shift 0 || { ctx_wn $ctx; return $EC; }
21 |
22 | echo {a..z}
23 | }
24 |
--------------------------------------------------------------------------------
/examples/tour/case_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Case statement example.
4 |
5 | # Case statement enables you to select one of the options based on the
6 | # given value (or expression that evaluates to a value). This
7 | # statement is similar to switch statement in other programming
8 | # languages. In bash, only one case is selected (first one that
9 | # matches) and execution continuous after the case statement (similar
10 | # to Go). The default case (which is optional) is "*".
11 |
12 | case $(date +%b) in
13 | "Aug") echo "School is starting";;
14 | "Sep") echo "Labor day";;
15 | *) echo "Not one of the supported months";;
16 | esac
17 |
--------------------------------------------------------------------------------
/src/p.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Root package.
6 |
7 | if [ -n "${SRC_PACKAGE:-}" ]; then return 0; fi
8 | readonly SRC_PACKAGE=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | # Language.
11 | . ${SRC_PACKAGE}/lang/p.sh
12 |
13 | # API.
14 | . ${SRC_PACKAGE}/util/p.sh
15 | . ${SRC_PACKAGE}/ui/p.sh
16 | . ${SRC_PACKAGE}/net/p.sh
17 | . ${SRC_PACKAGE}/sync/p.sh
18 | . ${SRC_PACKAGE}/container/p.sh
19 |
20 | # Testing.
21 | . ${SRC_PACKAGE}/testing/p.sh
22 |
23 | # Tools.
24 | . ${SRC_PACKAGE}/tools/p.sh
25 |
26 | # Database.
27 | . ${SRC_PACKAGE}/database/p.sh
28 |
--------------------------------------------------------------------------------
/examples/tour/for_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function main() {
4 | local sum=0
5 | local i
6 |
7 | for (( i=0; i<10; i++ )); do
8 | sum=$(bc <<< "${sum} + ${i}")
9 | done
10 | printf "%d\n" "${sum}"
11 |
12 | # Alternative.
13 | sum=0
14 | for i in $(seq 0 9); do
15 | sum=$(bc <<< "${sum} + ${i}")
16 | done
17 | printf "%d\n" "${sum}"
18 |
19 | # Empty init and post.
20 | sum=0
21 | for (( ; ${sum}<1000; )); do
22 | sum=$(( ${sum} + 1 ))
23 | done
24 | printf "%d\n" "${sum}"
25 | }
26 |
27 | main
28 |
--------------------------------------------------------------------------------
/examples/tour/if_else_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
4 | . ${DIR}/../../gobash
5 |
6 |
7 | function pow() {
8 | local x="${1}"
9 | local n="${2}"
10 | local lim="${3}"
11 | shift 3
12 |
13 | local v=$(math_pow "${x}" "${n}")
14 | if math_lt "${v}" "${lim}"; then
15 | echo "${v}"
16 | return 0
17 | else
18 | printf "%g >= %g\n" "${v}" "${lim}"
19 | fi
20 |
21 | echo "${lim}"
22 | }
23 |
24 | function main() {
25 | pow 3 2 10
26 | pow 3 3 20
27 | }
28 |
29 | main
30 |
--------------------------------------------------------------------------------
/examples/log_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Log API usage examples.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | # Print the location for the log output.
10 | log_output
11 |
12 | # Log info (will appear with "INFO" prefix in the output).
13 | log_i
14 | # Log info with more details.
15 | log_i "Provide more details in a message"
16 |
17 | # Log warn (will appear with "WARN" prefix in the output).
18 | log_w "Provide more details"
19 |
20 | # Log error (will appear with "ERROR" prefix in the output).
21 | log_e "Provide more details"
22 |
23 | log_set_output $LOG_STDOUT
24 | log_i "This will be on stdout"
25 |
--------------------------------------------------------------------------------
/src/util/p.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Util package.
6 |
7 | if [ -n "${UTIL_PACKAGE:-}" ]; then return 0; fi
8 | readonly UTIL_PACKAGE=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${UTIL_PACKAGE}/file.sh
11 | . ${UTIL_PACKAGE}/filepath.sh
12 | . ${UTIL_PACKAGE}/os.sh
13 | . ${UTIL_PACKAGE}/time.sh
14 | . ${UTIL_PACKAGE}/math.sh
15 | . ${UTIL_PACKAGE}/rand.sh
16 | . ${UTIL_PACKAGE}/strings.sh
17 | . ${UTIL_PACKAGE}/char.sh
18 | . ${UTIL_PACKAGE}/regexp.sh
19 | . ${UTIL_PACKAGE}/flags.sh
20 | . ${UTIL_PACKAGE}/complex.sh
21 | . ${UTIL_PACKAGE}/user.sh
22 | . ${UTIL_PACKAGE}/binary.sh
23 |
--------------------------------------------------------------------------------
/doc/next.rst:
--------------------------------------------------------------------------------
1 |
2 | Next
3 | ====
4 |
5 | If you are looking for further reading, the best next place is the
6 | `examples page
7 | `_.
8 |
9 | Regarding the features in gobash, there is a lot of potential future
10 | work: improving flag parsing, API extensions, etc. Regardless what
11 | path this repo takes next, it should always keep programming
12 | abstractions simple (e.g., no hiding anything behind annotations).
13 |
14 | Performance is the current biggest issue. We have several ideas on
15 | improving the performance, but we need some available time.
16 |
17 | .. toctree::
18 | :maxdepth: 2
19 |
--------------------------------------------------------------------------------
/src/lang/p.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Lang package.
6 |
7 | if [ -n "${LANG_PACKAGE:-}" ]; then return 0; fi
8 | readonly LANG_PACKAGE=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${LANG_PACKAGE}/core.sh
11 | . ${LANG_PACKAGE}/unsafe.sh
12 | . ${LANG_PACKAGE}/os.sh
13 | . ${LANG_PACKAGE}/runtime.sh
14 | . ${LANG_PACKAGE}/sys.sh
15 | . ${LANG_PACKAGE}/make.sh
16 | . ${LANG_PACKAGE}/log.sh
17 | . ${LANG_PACKAGE}/result.sh
18 | . ${LANG_PACKAGE}/pipe.sh
19 | . ${LANG_PACKAGE}/bash.sh
20 |
21 | . ${LANG_PACKAGE}/bool.sh
22 | . ${LANG_PACKAGE}/int.sh
23 |
24 | . ${LANG_PACKAGE}/assert.sh
25 |
--------------------------------------------------------------------------------
/examples/to_json_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # This example illustrates default `to_json` method.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | function Person() {
10 | make_ $FUNCNAME \
11 | "name" "$1" \
12 | "age" "$2"
13 | }
14 | # Each `struct` has a default `to_json` method.
15 |
16 | # Create an instance and print the instance into `json` format.
17 | p=$(Person "Jessy" 10)
18 | $p to_json
19 |
20 | # Although one can override the implementation of `to_json`, doing
21 | # so is not recommended.
22 |
23 | # Output of this script:
24 | # {
25 | # "name": "Jessy",
26 | # "age": "10"
27 | # }
28 |
--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/examples/playground/ring_prev_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example that corresponds to https://go.dev/play/p/Ow6XFg9kWjG from
4 | # the go documentation (https://pkg.go.dev/container/ring).
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../../gobash
8 |
9 |
10 | function main() {
11 | local r=$(container_Ring 5)
12 |
13 | local n=$($r len)
14 |
15 | local i
16 | for (( i=0; i<${n}; i++ )); do
17 | $r value "${i}"
18 | r=$($r next)
19 | done
20 |
21 | local j
22 | for (( j=0; j<${n}; j++ )); do
23 | r=$($r prev)
24 | $r value
25 | done
26 | }
27 |
28 | main
29 |
--------------------------------------------------------------------------------
/examples/text_menu_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example that illustrates elements of text-based UI (e.g., menu).
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | function main() {
10 | # List of options to offer in a menu.
11 | local lst=$(List "red" "green" "blue")
12 | # Create a text menu, prompt, and provide list of options.
13 | local menu=$(TextMenu "Pick your favorite color." "$lst")
14 |
15 | local res=$(UIResult)
16 | # Ask a user to select an option.
17 | $menu show "${res}"
18 |
19 | # Result keeps the selected value.
20 | $res val
21 | }
22 |
23 | # Uncomment to run this example.
24 | # main
25 |
--------------------------------------------------------------------------------
/examples/playground/ring_next_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example that corresponds to https://go.dev/play/p/osN5glyORyq from
4 | # the go documentation (https://pkg.go.dev/container/ring).
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../../gobash
8 |
9 |
10 | function main() {
11 | local r
12 | r=$(container_Ring 5)
13 |
14 | local n=$($r len)
15 |
16 | local i
17 | for (( i=0; i<${n}; i++ )); do
18 | $r value "${i}"
19 | r=$($r next)
20 | done
21 |
22 | local j
23 | for (( j=0; j<${n}; j++ )); do
24 | $r value
25 | r=$($r next)
26 | done
27 | }
28 |
29 | main
30 |
--------------------------------------------------------------------------------
/.github/workflows/test-mac.yml:
--------------------------------------------------------------------------------
1 | name: test (macOS)
2 | run-name: ${{ github.ref_name }} on macOS
3 | on: [push, pull_request]
4 | env:
5 | bversion: 3
6 | concurrency:
7 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
8 | cancel-in-progress: true
9 | jobs:
10 | Test:
11 | runs-on: macos-latest
12 | steps:
13 | - name: Checkout the repo
14 | uses: actions/checkout@v4
15 | # We change the depth to find changed files.
16 | with:
17 | fetch-depth: 2
18 | - run: bash --version
19 | - name: Print configuration
20 | run: ./src/internal/ci ci_config $bversion
21 | - name: Run tests
22 | run: ./src/internal/ci ci_test $bversion
23 |
--------------------------------------------------------------------------------
/.github/workflows/test-bash5.yml:
--------------------------------------------------------------------------------
1 | name: test (bash 5)
2 | run-name: ${{ github.ref_name }} on bash 5
3 | on: [push, pull_request]
4 | env:
5 | bversion: 5
6 | concurrency:
7 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
8 | cancel-in-progress: true
9 | jobs:
10 | Test:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout the repo
14 | uses: actions/checkout@v4
15 | # We change the depth to find changed files.
16 | with:
17 | fetch-depth: 2
18 | - run: bash --version
19 | - name: Print configuration
20 | run: ./src/internal/ci ci_config $bversion
21 | - name: Run tests
22 | run: ./src/internal/ci ci_test $bversion
23 |
--------------------------------------------------------------------------------
/examples/playground/ring_move_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example that corresponds to https://go.dev/play/p/Q5yslb_uojR from
4 | # the go documentation (https://pkg.go.dev/container/ring).
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../../gobash
8 |
9 |
10 | function main() {
11 | local r=$(container_Ring 5)
12 |
13 | local n=$($r len)
14 |
15 | local i
16 | for (( i=0; i<${n}; i++ )); do
17 | $r value "${i}"
18 | r=$($r next)
19 | done
20 |
21 | r=$($r move 3)
22 |
23 | function lambda() {
24 | local p="${1}"
25 | echo "${p}"
26 | }
27 | $r do "lambda"
28 | }
29 |
30 | main
31 |
--------------------------------------------------------------------------------
/examples/playground/ring_unlink_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example that corresponds to https://go.dev/play/p/Akl3lMwkDkh from
4 | # the go documentation (https://pkg.go.dev/container/ring).
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../../gobash
8 |
9 |
10 | function main() {
11 | local r=$(container_Ring 6)
12 |
13 | local n=$($r len)
14 |
15 | local i
16 | for (( i=0; i<${n}; i++ )); do
17 | $r value "${i}"
18 | r=$($r next)
19 | done
20 |
21 | $r unlink 3
22 |
23 | function lambda() {
24 | local p="${1}"
25 | echo "${p}"
26 | }
27 | $r do "lambda"
28 | }
29 |
30 | main
31 |
--------------------------------------------------------------------------------
/examples/strings_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example with string API.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | function main() {
10 | strings_to_lower "Something"
11 | # Output: something
12 |
13 | strings_to_upper "something"
14 | # Output: SOMETHING
15 |
16 | strings_len "something"
17 | # Output: 9
18 |
19 | strings_repeat 'c' 10
20 | # Output: cccccccccc
21 |
22 | strings_has_prefix "this is string" "this" && echo "true"
23 | # Output: true
24 |
25 | strings_rev "this"
26 | # Output: siht
27 |
28 | strings_lstrip " what"
29 | # Output: what
30 | }
31 |
32 | main
33 |
--------------------------------------------------------------------------------
/examples/flags_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Illustrates command line flag parsing.
4 | # Try invoking this script with, e.g., --x 55.
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../gobash
8 |
9 |
10 | data=$(Args)
11 |
12 | flags=$(Flags "Tool for interacting with outer space.")
13 | $flags add "$(Flag x int 'Max value')"
14 | $flags add "$(Flag z int 'Min value')"
15 |
16 | # Print a help message (uncomment).
17 | # $flags help
18 |
19 | ctx=$(ctx_make)
20 |
21 | # Parse arguments.
22 | $flags $ctx parse "$data" "$@" || \
23 | { ctx_show $ctx; exit 1; }
24 |
25 | # Print parsed values.
26 | $data to_string
27 |
28 | # Use data.
29 | echo "The values that you provided are x=$($data x) and z=$($data z)"
30 |
--------------------------------------------------------------------------------
/examples/playground/ring_do_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example that corresponds to https://go.dev/play/p/_G-I78xsmoi from
4 | # the go documentation (https://pkg.go.dev/container/ring).
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../../gobash
8 |
9 |
10 | function main() {
11 | local r=$(container_Ring 5)
12 |
13 | local n
14 | n=$($r len) || return $EC
15 |
16 | local i
17 | for (( i=0; i<${n}; i++ )); do
18 | $r value "${i}" || return $EC
19 | r=$($r next) || return $EC
20 | done
21 |
22 | function lambda() {
23 | local p="${1}"
24 | echo "${p}"
25 | }
26 | $r do "lambda"
27 | }
28 |
29 | main
30 |
--------------------------------------------------------------------------------
/src/tools/bdoc_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the bdoc module.
6 |
7 | if [ -n "${BDOC_TEST_MOD:-}" ]; then return 0; fi
8 | readonly BDOC_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${BDOC_TEST_MOD}/bdoc.sh
11 | . ${BDOC_TEST_MOD}/../testing/bunit.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function test_bdoc_file() {
18 | local -r tmpf=$(os_mktemp_file)
19 | _bdoc_file "${BDOC_TEST_MOD}/bdoc.sh" > "${tmpf}"
20 |
21 | grep 'Document generation tool.' "${tmpf}" || \
22 | assert_fail
23 |
24 | grep 'bdoc_main' "${tmpf}" || \
25 | assert_fail
26 | }
27 | readonly -f test_bdoc_file
28 |
--------------------------------------------------------------------------------
/doc/process-communication.rst:
--------------------------------------------------------------------------------
1 |
2 | Inter-process Communication
3 | ===========================
4 |
5 | Starting a new process or a sub process is trivial in shell. The
6 | design of gobash enables easy sharing of objects and process
7 | communication. An object can be used in a sub shell or it can be
8 | passed to a different process. We illustrate the former case below.
9 |
10 | .. code-block:: bash
11 |
12 | #!/bin/bash
13 | . gobash/gobash
14 |
15 | ai=$(AtomicInt 0)
16 | ( $ai inc ) &
17 | ( $ai inc ) &
18 | ( $ai inc ) &
19 | wait
20 | $ai val
21 |
22 | In this example, we create an object (``AtomicInt``) that is used in
23 | three sub shells. Once all sub shells finish their work, we print the
24 | final value.
25 |
26 | .. toctree::
27 | :maxdepth: 2
28 |
--------------------------------------------------------------------------------
/src/sync/wait_group_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the wait_group module.
6 |
7 | if [ -n "${WAIT_GROUP_TEST_MOD:-}" ]; then return 0; fi
8 | readonly WAIT_GROUP_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${WAIT_GROUP_TEST_MOD}/wait_group.sh
11 | . ${WAIT_GROUP_TEST_MOD}/../testing/bunit.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function test_wait_group() {
18 | local -r wg=$(WaitGroup)
19 |
20 | ( echo 1 >/dev/null ) &
21 | $wg add $!
22 |
23 | ( echo 2 >/dev/null ) &
24 | $wg add $!
25 |
26 | assert_eq $($wg len) 2
27 |
28 | $wg wait || \
29 | assert_fail
30 | }
31 | readonly -f test_wait_group
32 |
--------------------------------------------------------------------------------
/examples/anonymous_struct_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # An example that illustrates anonymous struct.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | # Anonymous structs are created using `amake_` function; this function
10 | # is the same as `make_`, but does not accept the `struct` name.
11 | p=$(amake_ "x" 3 "y" 5)
12 | $p x
13 | # Output: 3
14 |
15 | $p y
16 | # Output: 5
17 |
18 | # Anonymous structs can be used at any place including in functions.
19 | function demo() {
20 | local a=$(amake_ "date" "$(date)" "rand" "${RANDOM}")
21 | $a to_string
22 | }
23 | demo
24 | # Output (will differ for you, depending on current time and a random number):
25 | # {
26 | # "date": "Mon 14 Aug 2023 10:22:29 AM CDT",
27 | # "rand": "32674"
28 | # }
29 |
--------------------------------------------------------------------------------
/src/lang/pipe_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the pipe module.
6 |
7 | if [ -n "${PIPE_TEST_MOD:-}" ]; then return 0; fi
8 | readonly PIPE_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${PIPE_TEST_MOD}/assert.sh
11 | . ${PIPE_TEST_MOD}/pipe.sh
12 | . ${PIPE_TEST_MOD}/../testing/bunit.sh
13 |
14 |
15 | # ----------
16 | # Functions.
17 |
18 | function test_pipe() {
19 | local p
20 | p=$(Pipe) || \
21 | assert_fail
22 | ( $p send "Text" ) &
23 |
24 | local msg
25 | msg=$($p recv) || \
26 | assert_fail
27 | assert_eq "${msg}" "Text"
28 | wait || \
29 | assert_fail
30 | }
31 | readonly -f test_pipe
32 |
--------------------------------------------------------------------------------
/.github/workflows/test-bash3.yml:
--------------------------------------------------------------------------------
1 | name: test (bash 3)
2 | run-name: ${{ github.ref_name }} on bash 3
3 | on: [push, pull_request]
4 | env:
5 | bversion: 3
6 | concurrency:
7 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
8 | cancel-in-progress: true
9 | jobs:
10 | Test:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout the repo
14 | uses: actions/checkout@v4
15 | # We change the depth to find changed files.
16 | with:
17 | fetch-depth: 2
18 | - run: bash --version
19 | - name: Install bash
20 | run: ./src/internal/ci ci_install_bash $bversion
21 | - name: Print configuration
22 | run: ./src/internal/ci ci_config $bversion
23 | - name: Run tests
24 | run: ./src/internal/ci ci_test $bversion
25 |
--------------------------------------------------------------------------------
/.github/workflows/test-bash4.yml:
--------------------------------------------------------------------------------
1 | name: test (bash 4)
2 | run-name: ${{ github.ref_name }} on bash 4
3 | on: [push, pull_request]
4 | env:
5 | bversion: 4
6 | concurrency:
7 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
8 | cancel-in-progress: true
9 | jobs:
10 | Test:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout the repo
14 | uses: actions/checkout@v4
15 | # We change the depth to find changed files.
16 | with:
17 | fetch-depth: 2
18 | - run: bash --version
19 | - name: Install bash
20 | run: ./src/internal/ci ci_install_bash $bversion
21 | - name: Print configuration
22 | run: ./src/internal/ci ci_config $bversion
23 | - name: Run tests
24 | run: ./src/internal/ci ci_test $bversion
25 |
--------------------------------------------------------------------------------
/hsabog:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Installation/include script that should be used with source + curl.
6 |
7 | INSTALL_DST="$HOME/.gobash"
8 | INSTALL_REPO="${INSTALL_DST}/gobash"
9 | INSTALL_URL="git@github.com:EngineeringSoftware/gobash"
10 |
11 | mkdir -p "${INSTALL_DST}" || \
12 | { echo "cannot make directory (${INSTALL_DST})"; exit 1; }
13 |
14 | if [ ! -d "${INSTALL_REPO}" ]; then
15 | git clone \
16 | "${INSTALL_URL}" \
17 | "${INSTALL_REPO}" > /dev/null 2>&1 || \
18 | { echo "could not clone the repo"; exit 1; }
19 | else
20 | ( cd "${INSTALL_REPO}"
21 | git pull ) > /dev/null 2>&1 || \
22 | { echo "could not pull"; exit 1; }
23 | fi
24 |
25 | . "${INSTALL_REPO}/gobash"
26 |
--------------------------------------------------------------------------------
/src/net/handler.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Handler related functions.
6 |
7 | if [ -n "${HANDLER_MOD:-}" ]; then return 0; fi
8 | readonly HANDLER_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${HANDLER_MOD}/../lang/p.sh
11 |
12 |
13 | # ----------
14 | # Functions.
15 |
16 | function Handler() {
17 | # Handler of http requests.
18 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
19 | [ $# -ne 3 ] && { ctx_wn $ctx; return $EC; }
20 | local -r path="${1}"
21 | local -r script="${2}"
22 | local -r func="${3}"
23 | shift 3 || { ctx_wn $ctx; return $EC; }
24 |
25 | make_ $ctx \
26 | "${FUNCNAME}" \
27 | "path" "${path}" \
28 | "script" "${script}" \
29 | "func" "${func}"
30 | }
31 |
--------------------------------------------------------------------------------
/examples/text_progress_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Illustrates text-based progress bar.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | function main() {
10 | # In this example, we will go over all the files in this
11 | # directory and collect their sizes. We will output the
12 | # progress in terms of processed files.
13 |
14 | local num_files=$(find "${DIR}" -maxdepth 1 -name "*_ex" | wc -l)
15 | local -r bar=$(TextProgress "${num_files}")
16 |
17 | $bar start
18 | local lst=$(List)
19 | local f
20 | for f in $(find "${DIR}" -maxdepth 1 -name "*_ex"); do
21 | $bar inc
22 | local nl=$(wc -l "${f}" | cut -f1 -d' ')
23 | $lst add "${nl}"
24 | done
25 | $bar stop
26 |
27 | $lst len
28 | }
29 |
30 | main
31 |
--------------------------------------------------------------------------------
/.github/workflows/documentation.yml:
--------------------------------------------------------------------------------
1 | name: documentation
2 | run-name: ${{ github.ref_name }} documentation
3 | on: [push, pull_request]
4 | permissions:
5 | contents: write
6 | jobs:
7 | docs:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 | - uses: actions/setup-python@v3
12 | - name: Install dependencies
13 | run: |
14 | pip install sphinx sphinx_rtd_theme myst_parser
15 | - name: Sphinx build
16 | run: |
17 | ./gobash sphinx
18 | sphinx-build doc _build
19 | - name: Deploy to GitHub Pages
20 | uses: peaceiris/actions-gh-pages@v3
21 | if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
22 | with:
23 | publish_branch: gh-pages
24 | github_token: ${{ secrets.GITHUB_TOKEN }}
25 | publish_dir: _build/
26 | force_orphan: true
27 |
--------------------------------------------------------------------------------
/doc/style.md:
--------------------------------------------------------------------------------
1 | ### Style
2 |
3 | This page is only relevant to people that are considering to
4 | contribute to `gobash`.
5 |
6 | Rules that we follow:
7 | * Use 8 spaces (no tabs) to indent code
8 | * Each function definition starts with `function`
9 | * Function name is prefixed is the name of the file (i.e., module)
10 | * Each function should return proper exit code
11 | * All functions in `gobash` accept context as the first argument
12 | * Each function should have at least one test
13 |
14 |
15 | ### Variables
16 |
17 | Below are the most common rules that we follow:
18 |
19 | * Variable names should be short
20 | * Use snake_case
21 |
22 | Below is the list of names that we like to reserve for specific purposes:
23 |
24 | ```
25 | ec - exit code from a function
26 | ```
27 |
28 | Any global variable name should be prefixed by the name of the module.
29 | We like to avoid use global variables other than constants.
30 |
--------------------------------------------------------------------------------
/doc/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 |
13 | %SPHINXBUILD% >NUL 2>NUL
14 | if errorlevel 9009 (
15 | echo.
16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
17 | echo.installed, then set the SPHINXBUILD environment variable to point
18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
19 | echo.may add the Sphinx directory to PATH.
20 | echo.
21 | echo.If you don't have Sphinx installed, grab it from
22 | echo.https://www.sphinx-doc.org/
23 | exit /b 1
24 | )
25 |
26 | if "%1" == "" goto help
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/examples/to_string_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # This example illustrates default `to_string` method and a way to
4 | # override the default implementation for a struct.
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../gobash
8 |
9 |
10 | function Person() {
11 | make_ $FUNCNAME \
12 | "name" "$1" \
13 | "age" "$2"
14 | }
15 | # Each `struct` has a default `to_string` method.
16 |
17 | # Create an instance and print.
18 | p=$(Person "Jessy" 10)
19 | $p to_string
20 |
21 | # Override the default implementation.
22 | function Person_to_string() {
23 | local -r obj="${1}"
24 | shift 1
25 |
26 | # Can print anything desired here.
27 | echo "I am $($obj name)."
28 | }
29 |
30 | # The following line will invoke the newly introduced `to_string`.
31 | $p to_string
32 |
33 | # Output of this script:
34 | # {
35 | # "name": "Jessy",
36 | # "age": "10"
37 | # }
38 | # I am Jessy.
39 |
--------------------------------------------------------------------------------
/examples/playground/list_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example that corresponds to https://go.dev/play/p/9JuKIGdWlpF from
4 | # the go documentation.
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../../gobash
8 |
9 |
10 | function main() {
11 | local l
12 | l=$(container_List) || \
13 | { ctx_w "cannot make a list"; return $EC; }
14 |
15 | local e4
16 | e4=$($l push_back 4) || \
17 | { ctx_w "cannot push 4"; return $EC; }
18 |
19 | local e1
20 | e1=$($l push_front 1) || \
21 | { ctx_w "cannot push 1"; return $EC; }
22 |
23 | $l insert_before 3 "$e4" > /dev/null || return $EC
24 | $l insert_after 2 "$e1" > /dev/null || return $EC
25 |
26 | local e=$($l front)
27 | while [ "$e" != "$NULL" ]; do
28 | echo "$($e value)"
29 | e=$($e next) || return $EC
30 | done
31 | }
32 |
33 | ctx_clear
34 | main || ctx_show
35 |
--------------------------------------------------------------------------------
/src/lang/runtime_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the runtime module.
6 |
7 | if [ -n "${LANG_RUNTIME_TEST_MOD:-}" ]; then return 0; fi
8 | readonly LANG_RUNTIME_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${LANG_RUNTIME_TEST_MOD}/assert.sh
11 | . ${LANG_RUNTIME_TEST_MOD}/os.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function test_runtime_num_cpu() {
18 | local n
19 | n=$(runtime_num_cpu) || assert_fail
20 |
21 | ! is_int "${n}" && assert_fail
22 | [ "${n}" -lt 1 ] && assert_fail
23 | return 0
24 | }
25 | readonly -f test_runtime_num_cpu
26 |
27 | function test_runtime_num_physical_cpu() {
28 | local n
29 | n=$(runtime_num_physical_cpu) || assert_fail
30 |
31 | ! is_int "${n}" && assert_fail
32 | [ "${n}" -lt 1 ] && assert_fail
33 | return 0
34 | }
35 | readonly -f test_runtime_num_physical_cpu
36 |
--------------------------------------------------------------------------------
/examples/playground/ring_link_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example that corresponds to https://go.dev/play/p/fH3iuZlP7Au from
4 | # the go documentation (https://pkg.go.dev/container/ring).
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../../gobash
8 |
9 |
10 | function main() {
11 | local r=$(container_Ring 2)
12 | local s=$(container_Ring 2)
13 |
14 | local lr=$($r len)
15 | local ls=$($s len)
16 |
17 | local i
18 | for (( i=0; i<${lr}; i++ )); do
19 | $r value 0
20 | r=$($r next)
21 | done
22 |
23 | local j
24 | for (( j=0; j<${ls}; j++ )); do
25 | $s value 1
26 | s=$($s next)
27 | done
28 |
29 | local rs
30 | rs=$($r link "$s") || return $EC
31 |
32 | function lambda() {
33 | local p="${1}"
34 | echo "${p}"
35 | }
36 | $rs do "lambda"
37 | }
38 |
39 | main
40 |
--------------------------------------------------------------------------------
/examples/template_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Illustrates a recommended (but *not* required) workflow when using
4 | # structs and collections from the library.
5 | #
6 | # In summary, it is valuable to always check context at the end of the
7 | # execution (even if you do not create your own context, but you rely
8 | # on the global context). Of course, you can combine this with your
9 | # regular debugging, using -e (which is limited) and -x.
10 |
11 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
12 | . ${DIR}/../gobash
13 |
14 |
15 | function main() {
16 | local -r ctx="${1}"
17 |
18 | local -r flags=$(Flags $ctx "Your tool name.")
19 |
20 | # Parse flags below this line.
21 |
22 | # Include any code you wish.
23 | }
24 |
25 | # Make a context for this run.
26 | ctx=$(ctx_make)
27 | main $ctx "$@" || { ctx_w $ctx "main failed"; ctx_show $ctx; exit 1; }
28 |
29 | # Recommended to print the context at the end of any execution, any
30 | # command can easily "eat" an exit status.
31 | ctx_show $ctx
32 |
--------------------------------------------------------------------------------
/doc/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # For the full list of built-in configuration values, see the documentation:
4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
5 |
6 | # -- Project information -----------------------------------------------------
7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
8 |
9 | project = 'gobash'
10 | copyright = '2024, Milos Gligoric'
11 | author = 'Milos Gligoric'
12 |
13 | # -- General configuration ---------------------------------------------------
14 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
15 |
16 | extensions = []
17 |
18 | templates_path = ['_templates']
19 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
20 |
21 |
22 |
23 | # -- Options for HTML output -------------------------------------------------
24 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
25 |
26 | html_theme = 'alabaster'
27 | html_static_path = ['_static']
28 |
--------------------------------------------------------------------------------
/doc/collections.rst:
--------------------------------------------------------------------------------
1 |
2 | Collections
3 | ===========
4 |
5 | gobash introduces two key collections: ``List`` and ``Map``. Both
6 | collections include a number of methods that can be convenient for
7 | everyday development.
8 |
9 | In the example below, we use an instance of a ``List`` to keep URLs of
10 | several GitHub projects and then close each of those projects in a
11 | loop.
12 |
13 | .. code-block:: bash
14 |
15 | #!/bin/bash
16 | . gobash/gobash
17 |
18 | lst=$(List)
19 | $lst add "https://github.com/apache/commons-math"
20 | $lst add "https://github.com/apache/commons-io"
21 |
22 | # Print length.
23 | $lst len
24 |
25 | # Clone each repo.
26 | for (( i=0; i<$($lst len); i++)); do
27 | git clone $($lst get $i)
28 | done
29 |
30 | # Print the list.
31 | $lst to_string
32 |
33 | .. note::
34 |
35 | Equality in gobash is done based on object identity. Future changes
36 | could consider using ``eq`` methods to check for equality (similar
37 | to other programming languages).
38 |
39 | .. toctree::
40 | :maxdepth: 2
41 |
--------------------------------------------------------------------------------
/src/util/binary.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Util functions to manipulate binary numbers.
6 |
7 | if [ -n "${BINARY_MOD:-}" ]; then return 0; fi
8 | readonly BINARY_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${BINARY_MOD}/../lang/p.sh
11 |
12 |
13 | # ----------
14 | # Functions.
15 |
16 | function binary_d2b() {
17 | # From decimal to binary.
18 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
19 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
20 | local -r n="${1}"
21 | shift 1 || { ctx_wn $ctx; return $EC; }
22 |
23 | # TODO: check for errors.
24 | echo "obase=2; ${n}" | bc
25 | }
26 |
27 | function binary_b2d() {
28 | # From binary to decimal.
29 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
30 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
31 | local -r n="${1}"
32 | shift 1 || { ctx_wn $ctx; return $EC; }
33 |
34 | # TODO: check for errors.
35 | echo "ibase=2;obase=A; ${n}" | bc
36 | }
37 |
--------------------------------------------------------------------------------
/examples/regexp_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Illustrates use of the regular expression API.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | function main() {
10 | local regexp
11 |
12 | regexp=$(regexp_compile ".* not [zc]omplex") || \
13 | return $EC
14 |
15 | $regexp match_string " not zomplex" || \
16 | { echo "Expecting a match."; return $EC; }
17 |
18 | $regexp match_string "something not complex" || \
19 | { echo "Expecting a match."; return $EC; }
20 |
21 | local str
22 | str=$($regexp find_string "a not zomplex text but still a match")
23 | assert_eq "a not zomplex" "${str}" "String does not match."
24 |
25 | regexp=$(regexp_compile "a (.*) b (.*) c") || \
26 | return $EC
27 |
28 | local lst
29 | lst=$($regexp find_string_submatch "a abc b x c") || \
30 | return $EC
31 |
32 | assert_eq "abc" "$($lst get 1)"
33 | assert_eq "x" "$($lst get 2)"
34 | }
35 |
36 | main
37 |
--------------------------------------------------------------------------------
/src/sync/mutex_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the mutex module.
6 |
7 | if [ -n "${MUTEX_TEST_MOD:-}" ]; then return 0; fi
8 | readonly MUTEX_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${MUTEX_TEST_MOD}/mutex.sh
11 | . ${MUTEX_TEST_MOD}/../testing/bunit.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function test_mutex() {
18 | local mu
19 | mu=$(Mutex) || \
20 | assert_fail
21 |
22 | $mu lock || \
23 | assert_fail
24 |
25 | $mu unlock || \
26 | assert_fail
27 | }
28 | readonly -f test_mutex
29 |
30 | function test_mutex_count() {
31 | local -r mu=$(Mutex)
32 |
33 | local -r count=$(Int 0)
34 |
35 | ( $mu lock; $count inc; $mu unlock ) &
36 | ( $mu lock; $count inc; $mu unlock ) &
37 | ( $mu lock; $count inc; $mu unlock ) &
38 | wait || \
39 | assert_fail
40 |
41 | assert_eq 3 "$($count val)"
42 | }
43 | readonly -f test_mutex_count
44 |
--------------------------------------------------------------------------------
/src/external/git/testdata/tags.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "ase22-ae-r",
4 | "zipball_url": "https://api.github.com/repos/EngineeringSoftware/inlinetest/zipball/refs/tags/ase22-ae-r",
5 | "tarball_url": "https://api.github.com/repos/EngineeringSoftware/inlinetest/tarball/refs/tags/ase22-ae-r",
6 | "commit": {
7 | "sha": "87bce8426ff5a68ffd34c3db2274e8c9a83dca36",
8 | "url": "https://api.github.com/repos/EngineeringSoftware/inlinetest/commits/87bce8426ff5a68ffd34c3db2274e8c9a83dca36"
9 | },
10 | "node_id": "REF_kwDOHuLWCLRyZWZzL3RhZ3MvYXNlMjItYWUtcg"
11 | },
12 | {
13 | "name": "ase22-ae",
14 | "zipball_url": "https://api.github.com/repos/EngineeringSoftware/inlinetest/zipball/refs/tags/ase22-ae",
15 | "tarball_url": "https://api.github.com/repos/EngineeringSoftware/inlinetest/tarball/refs/tags/ase22-ae",
16 | "commit": {
17 | "sha": "4ad10e253c4d1292c3c8dca0da44f25430980840",
18 | "url": "https://api.github.com/repos/EngineeringSoftware/inlinetest/commits/4ad10e253c4d1292c3c8dca0da44f25430980840"
19 | },
20 | "node_id": "REF_kwDOHuLWCLJyZWZzL3RhZ3MvYXNlMjItYWU"
21 | }
22 | ]
23 |
--------------------------------------------------------------------------------
/examples/methods_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Introduces `methods` and their association with structs.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | function Request() {
10 | local -r url="${1}"
11 | shift 1
12 |
13 | make_ $FUNCNAME \
14 | "url" "${url}"
15 | }
16 |
17 | # Adding a method (named `curl`) for the `struct` `Request`. Note that
18 | # the method name has to be prefixed by the name of the struct.
19 | function Request_curl() {
20 | # The first argument of each method is an instance on which
21 | # the method was invoked (think of it as `this` or
22 | # `self`). Subsequent arguments (if any) are given to the
23 | # method at invocation time.
24 | local -r req="${1}"
25 | shift 1
26 |
27 | # The method body can be anything.
28 | # The exit code from this method will be propagated to the caller.
29 | curl "$($req url)" 2>&1
30 | }
31 |
32 | # Create an instance.
33 | req=$(Request "https://www.google.com")
34 |
35 | # Invoke a method on the instance.
36 | $req curl
37 |
--------------------------------------------------------------------------------
/src/lang/os_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the os module.
6 |
7 | if [ -n "${LANG_OS_TEST_MOD:-}" ]; then return 0; fi
8 | readonly LANG_OS_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${LANG_OS_TEST_MOD}/assert.sh
11 | . ${LANG_OS_TEST_MOD}/os.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function test_os_name() {
18 | os_name || \
19 | assert_fail
20 | }
21 | readonly -f test_os_name
22 |
23 | function test_os_arch() {
24 | os_arch || \
25 | assert_fail
26 | }
27 | readonly -f test_os_arch
28 |
29 | function test_os_mktemp_file() {
30 | local -r f=$(os_mktemp_file)
31 | [ -f "${f}" ] || \
32 | assert_fail
33 | }
34 | readonly -f test_os_mktemp_file
35 |
36 | function test_os_mktemp_dir() {
37 | local -r d=$(os_mktemp_dir)
38 | [ -d "${d}" ] || \
39 | assert_fail
40 | }
41 | readonly -f test_os_mktemp_dir
42 |
43 | function test_os_get_pid() {
44 | os_get_pid > /dev/null || \
45 | assert_fail
46 | }
47 | readonly -f test_os_get_pid
48 |
--------------------------------------------------------------------------------
/src/util/binary_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the binary functions.
6 |
7 | if [ -n "${BINARY_TEST_MOD:-}" ]; then return 0; fi
8 | readonly BINARY_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${BINARY_TEST_MOD}/binary.sh
11 | . ${BINARY_TEST_MOD}/../testing/bunit.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function test_binary_d2b() {
18 | local res
19 |
20 | res=$(binary_d2b "3")
21 | [ "${res}" = "11" ] || \
22 | assert_fail
23 |
24 | res=$(binary_d2b "33")
25 | [ "${res}" = "100001" ] || \
26 | assert_fail
27 |
28 | return 0
29 | }
30 | readonly -f test_binary_d2b
31 |
32 | function test_binary_b2d() {
33 | local res
34 |
35 | res=$(binary_b2d "011")
36 | [ "${res}" = "3" ] || \
37 | assert_fail
38 |
39 | res=$(binary_b2d "100001")
40 | [ "${res}" = "33" ] || \
41 | assert_fail
42 |
43 | #binary_b2d "100001a" && \
44 | #assert_fail
45 |
46 | return 0
47 | }
48 | readonly -f test_binary_b2d
49 |
--------------------------------------------------------------------------------
/src/lang/result_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the result module.
6 |
7 | if [ -n "${RESULT_TEST_MOD:-}" ]; then return 0; fi
8 | readonly RESULT_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${RESULT_TEST_MOD}/assert.sh
11 | . ${RESULT_TEST_MOD}/result.sh
12 | . ${RESULT_TEST_MOD}/../testing/bunit.sh
13 |
14 |
15 | # ----------
16 | # Functions.
17 |
18 | function z_test_result() {
19 | local -r res="${1}"
20 |
21 | # Random values to simulate setting error and value.
22 | $res val 55
23 | return $EC
24 | }
25 |
26 | function test_result() {
27 | local -r res=$(Result)
28 |
29 | z_test_result "${res}" && \
30 | assert_fail
31 |
32 | $res has_value || \
33 | assert_fail
34 | }
35 | readonly -f test_result
36 |
37 | function test_result_to_string() {
38 | local -r res=$(Result)
39 |
40 | $res to_string | grep '"val": null' > /dev/null || \
41 | assert_fail
42 |
43 | $res val 55
44 | $res to_string | grep '55' > /dev/null || \
45 | assert_fail
46 | }
47 | readonly -f test_result_to_string
48 |
--------------------------------------------------------------------------------
/examples/error_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # An approach to return a detailed error from a function. This example
4 | # illustrates the use of ctx.
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../gobash
8 |
9 |
10 | function sum() {
11 | # A detailed error messages can be written to context; note
12 | # that we can use the default context, in which case we do not
13 | # need to pass the first argument.
14 | local -r ctx="${1}"
15 | local -r a="${2}"
16 | local -r b="${3}"
17 |
18 | # When we detect an error, write the message to context.
19 | [ -z "${a}" ] && ctx_w $ctx "a was not set" && return $EC
20 | [ -z "${b}" ] && ctx_w $ctx "b was not set" && return $EC
21 |
22 | ! is_int "${a}" && ctx_w $ctx "a is not an int" && return $EC
23 | ! is_int "${b}" && ctx_w $ctx "b is not an int" && return $EC
24 |
25 | echo "${a} + ${b}" | bc
26 | }
27 |
28 | ctx=$(ctx_make)
29 | # If something goes wrong, print the context.
30 | sum "$ctx" || ctx_show $ctx
31 | # Output:
32 | # a was not set
33 | # 19 sum ./error_ex
34 | # 30 main ./error_ex
35 |
36 | val=$(sum "$ctx" 5 10) || \
37 | { echo "This should never happen."; }
38 | echo "${val}"
39 | # Output: 15
40 |
--------------------------------------------------------------------------------
/examples/linked_list_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Linked list implementation (nobody should need to do this ;).
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | function Node() {
10 | make_ $FUNCNAME \
11 | "val" "${1}" \
12 | "next" "${2}"
13 | }
14 |
15 | function Node_to_string() {
16 | local -r node="${1}"
17 | shift 1 || return $EC
18 |
19 | printf "$($node val)"
20 | }
21 |
22 | function LL() {
23 | make_ $FUNCNAME \
24 | "head" "$NULL" \
25 | "size" 0
26 | }
27 |
28 | function LL_add() {
29 | local -r ll="${1}"
30 | local -r val="${2}"
31 | shift 2 || return $EC
32 |
33 | local -r node=$(Node "$val" "$($ll head)")
34 | $ll head "$node"
35 | $ll size $(( $($ll size) + 1 ))
36 | }
37 |
38 | function LL_to_string() {
39 | local -r ll="${1}"
40 | shift 1 || return $EC
41 |
42 | local c=$($ll head)
43 | while [ "$c" != "$NULL" ]; do
44 | $c to_string
45 | printf " -> "
46 | c=$($c next)
47 | done
48 | printf "null \n"
49 | }
50 |
51 | ll=$(LL)
52 | $ll add 3
53 | $ll add 5
54 | $ll to_string
55 |
--------------------------------------------------------------------------------
/doc/command-line-flags.rst:
--------------------------------------------------------------------------------
1 |
2 | Command Line Flags
3 | ==================
4 |
5 | gobash can simplify parsing command line flags. In the next example,
6 | we illustrate parsing using the ``flags`` package. Specifically, we
7 | create ``Flags`` with desired documentation and add two flags. Each
8 | flag has to include name, type (int, bool, float, or string), and
9 | documentation.
10 |
11 | .. code-block:: bash
12 |
13 | #!/bin/bash
14 | . gobash/gobash
15 |
16 | min=$(Flag "x" "int" "Min value.")
17 | max=$(Flag "y" "int" "Max value.")
18 |
19 | flags=$(Flags "Flags to demo flag parsing.")
20 | $flags add "$min"
21 | $flags add "$max"
22 |
23 | Once we build the flags, we can print a help message simply like this:
24 |
25 | .. code-block:: bash
26 |
27 | $flags help
28 |
29 | Parsing flags is then done in a few steps:
30 |
31 | .. code-block:: bash
32 |
33 | args=$(Args) # an object that will keep parsed values
34 | ctx=$(ctx_make) # context will store an issue is encountered during parsing
35 | $flags $ctx parse "$args" "$@" || \
36 | { ctx_show $ctx; exit 1; } # checking for errors
37 |
38 | $args x # print the parsed x value
39 | $args y # print the parsed y value
40 |
41 | .. toctree::
42 | :maxdepth: 2
43 |
--------------------------------------------------------------------------------
/examples/playground/http_server_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example that corresponds to "HTTP Server" on https://go.dev/play/.
4 | # License for the corresponding code https://go.dev/LICENSE?m=text.
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../../gobash
8 |
9 |
10 | function hello() {
11 | local -r res="${1}"
12 | local -r req="${2}"
13 | shift 2
14 |
15 | $res write "Hello, playground"
16 | }
17 |
18 | function main() {
19 | local -r http=$(Http "127.0.0.1" "9003")
20 | $http handle_func "/hello" "${DIR}/$(basename ${BASH_SOURCE})" "hello"
21 |
22 | log_i "Starting server..."
23 | $http listen_and_serve
24 | sleep 2
25 |
26 | log_i "Sending request and reading response..."
27 | curl "http://127.0.0.1:9003/hello" 2>&1 || \
28 | { log_e "Could not access the server."; return $EC; }
29 |
30 | $http kill_and_wait || \
31 | { log_e "Issue on the server side."; return $EC; }
32 | }
33 |
34 | # "if" is needed as the script is loaded when `hello` is executed.
35 | if [[ "${0}" == *"http_server_ex" ]]; then
36 | http_enabled || \
37 | { echo "http is not enabled"; exit 0; }
38 | unset LOG_FILE
39 | main "$@"
40 | fi
41 |
--------------------------------------------------------------------------------
/src/external/git/testdata/branches.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "junit5-features",
4 | "commit": {
5 | "sha": "bbe2867513c690765116200c3e603c2e13dfd199",
6 | "url": "https://api.github.com/repos/EngineeringSoftware/inlinetest/commits/bbe2867513c690765116200c3e603c2e13dfd199"
7 | },
8 | "protected": false,
9 | "protection": {
10 | "enabled": false,
11 | "required_status_checks": {
12 | "enforcement_level": "off",
13 | "contexts": [
14 |
15 | ],
16 | "checks": [
17 |
18 | ]
19 | }
20 | },
21 | "protection_url": "https://api.github.com/repos/EngineeringSoftware/inlinetest/branches/junit5-features/protection"
22 | },
23 | {
24 | "name": "main",
25 | "commit": {
26 | "sha": "ef380ea266b2c7b2d2e18c9b02ceb27e2f2e65e8",
27 | "url": "https://api.github.com/repos/EngineeringSoftware/inlinetest/commits/ef380ea266b2c7b2d2e18c9b02ceb27e2f2e65e8"
28 | },
29 | "protected": false,
30 | "protection": {
31 | "enabled": false,
32 | "required_status_checks": {
33 | "enforcement_level": "off",
34 | "contexts": [
35 |
36 | ],
37 | "checks": [
38 |
39 | ]
40 | }
41 | },
42 | "protection_url": "https://api.github.com/repos/EngineeringSoftware/inlinetest/branches/main/protection"
43 | }
44 | ]
45 |
--------------------------------------------------------------------------------
/src/ui/ui.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Util for carrying UI results.
6 | # TODO: do we need this or just use Result?
7 |
8 | if [ -n "${UI_MOD:-}" ]; then return 0; fi
9 | readonly UI_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
10 |
11 |
12 | # ----------
13 | # Functions.
14 |
15 | function UIResult() {
16 | # UI result.
17 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
18 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
19 | shift 0 || { ctx_wn $ctx; return $EC; }
20 |
21 | make_ $ctx \
22 | "${FUNCNAME}" \
23 | "_err" "${NULL}" \
24 | "_val" "${NULL}" \
25 | "_cancelled" "$FALSE"
26 | }
27 |
28 | function UIResult_val() {
29 | # Get the value.
30 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
31 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
32 | local -r res="${1}"
33 | shift 1 || { ctx_wn $ctx; return $EC; }
34 |
35 | $res $ctx _val
36 | }
37 |
38 | function UIResult_is_cancelled() {
39 | # Return true if it was cancelled.
40 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
41 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
42 | local -r res="${1}"
43 | shift 1 || { ctx_wn $ctx; return $EC; }
44 |
45 | is_true $ctx $($res $ctx _cancelled)
46 | }
47 |
--------------------------------------------------------------------------------
/examples/structs_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # An example that illustrates `structs` and `constructors`.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | # `struct` and `constructor` are synonyms. In future examples, we will
10 | # only use `struct` in text (but we really mean both at once).
11 | function Request() {
12 | # Normal function arguments in bash.
13 | local -r url="${1}"
14 | shift 1
15 |
16 | # `make_` invocation differentiates a struct from other
17 | # functions. `make_` allocates an instance. The first
18 | # argument is the struct name (think of it as a type). struct
19 | # name can be anything, but we will always use $FUNCNAME
20 | # (which is `Request` in this example). Name is important when
21 | # associating methods with a struct (and we love
22 | # $FUNCNAME). The arguments that follow are the name and value
23 | # of a field.
24 | make_ $FUNCNAME \
25 | "url" "${url}"
26 | }
27 |
28 | # Creating a `Request` instance is trivial.
29 | req=$(Request "https://www.google.com")
30 |
31 | # We can print the value of a field.
32 | $req url
33 |
34 | # We can also update the value.
35 | $req url "https://www.google.com/maps"
36 | # Print again.
37 | $req url
38 |
39 | # Output of this script will be:
40 | # https://www.google.com
41 | # https://www.google.com/maps
42 |
--------------------------------------------------------------------------------
/examples/playground/concurrent_pi_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example that corresponds to "Concurrent pi" on https://go.dev/play/.
4 | # License for the corresponding code https://go.dev/LICENSE?m=text.
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../../gobash
8 |
9 |
10 | function bcs() {
11 | # Unit function for bc with specific scale.
12 | local -r exp="${1}"
13 | shift 1
14 |
15 | bc <<< "scale=4; ${exp}"
16 | }
17 |
18 | function pi() {
19 | local -r n="${1}"
20 | shift 1
21 |
22 | local -r ch=$(Chan)
23 | for k in $(seq 0 "${n}"); do
24 | ( term "$ch" "${k}" ) &
25 | done
26 |
27 | local f="3.0"
28 | for k in $(seq 0 "${n}"); do
29 | local v=$($ch recv)
30 | f=$(bcs "${f} + ${v}")
31 | done
32 |
33 | echo "${f}"
34 | }
35 |
36 | function term() {
37 | local -r ch="${1}"
38 | local -r k="${2}"
39 | shift 2
40 |
41 | local -r ke=$(bcs "-1^${k}")
42 | local -r k2=$(bcs "2 * ${k} + 2")
43 | local -r k3=$(bcs "2 * ${k} + 3")
44 | local -r k4=$(bcs "2 * ${k} + 4")
45 |
46 | local val=$(bcs "4 * ${ke} / (${k2} * ${k3} * ${k4})")
47 | $ch send "${val}"
48 | }
49 |
50 | function main() {
51 | printf "MATH_PI %g\n" $MATH_PI
52 | printf "Nilakantha %g\n" $(pi 10)
53 | }
54 |
55 | main
56 |
--------------------------------------------------------------------------------
/doc/interactive-mode.rst:
--------------------------------------------------------------------------------
1 |
2 | Interactive Mode
3 | ================
4 |
5 | gobash nicely inter-operates with interactive mode, i.e.,
6 | terminal. Namely, one can import gobash into interactive terminal and
7 | use all functions and features available. In other words you get REPL
8 | for free.
9 |
10 | In the example below, open your terminal and execute some commands.
11 |
12 | .. code-block:: bash
13 |
14 | $ . gobash/gobash
15 | $ lst=$(List)
16 | $ $lst len
17 | # 0
18 | $ $lst add $RANDOM
19 | $ $lst to_string
20 | # [
21 | # "16748"
22 | # ]
23 | $ p=$(struct "x" 3 "y" 55)
24 | $ $p to_string
25 | # {
26 | # "x": "3",
27 | # "y": "55"
28 | # }
29 |
30 | One of the implications is that you can now write scripts that accept
31 | objects, and those scripts can be invoked from your terminal with
32 | objects made in the terminal process.
33 |
34 | Consider the script below (``ai``). (This is the same example we used
35 | in an earlier section to illustrate inter-process communication.)
36 |
37 | .. code-block:: bash
38 |
39 | #!/bin/bash
40 | . gobash/gobash
41 |
42 | ai="${1}"
43 | ( $ai inc ) &
44 | ( $ai inc ) &
45 | ( $ai inc ) &
46 | wait
47 | $ai val
48 |
49 | Now in a terminal execute the following sequence.
50 |
51 | .. code-block:: bash
52 |
53 | $ obj=$(AtomicInt 6)
54 | $ ./ai "$obj"
55 | # 9
56 |
57 | .. toctree::
58 | :maxdepth: 2
59 |
--------------------------------------------------------------------------------
/examples/mutex_counter_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example inspired by mutex-counter.go from the Go tutorial
4 | # (https://go.dev/tour/concurrency/9).
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../gobash
8 |
9 |
10 | function SafeCounter() {
11 | # Including mutex as one of the fields.
12 | make_ $FUNCNAME \
13 | "mu" "$(Mutex)" \
14 | "v" "$(Map)"
15 | }
16 |
17 | function SafeCounter_inc() {
18 | local -r c="${1}"
19 | local -r key="${2}"
20 | shift 2
21 |
22 | # Lock mutex kept in this instance.
23 | $($c mu) lock
24 |
25 | $($c v) inc "${key}"
26 |
27 | # Unlock when the work is done.
28 | $($c mu) unlock
29 | }
30 |
31 | function SafeCounter_value() {
32 | local -r c="${1}"
33 | local -r key="${2}"
34 | shift 2
35 |
36 | $($c mu) lock
37 | local res=$($($c v) get "${key}")
38 | $($c mu) unlock
39 |
40 | echo "${res}"
41 | }
42 |
43 | function main() {
44 | local c
45 | c=$(SafeCounter) || assert_fail
46 |
47 | echo "Run increment in parallel with 10 subshells."
48 | for (( i=0; i<10; i++ )); do
49 | ( $c inc "somekey" ) &
50 | done
51 | echo "Wait for subshells to be done."
52 | wait || assert_fail
53 |
54 | $c value "somekey"
55 | }
56 |
57 | main
58 |
59 | # Output will be:
60 | # 10
61 |
--------------------------------------------------------------------------------
/src/net/response:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 |
5 | readonly _DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 |
7 | . ${_DIR}/../lang/p.sh
8 | . ${_DIR}/../util/p.sh
9 | . ${_DIR}/request.sh
10 | . ${_DIR}/response.sh
11 |
12 |
13 | # ----------
14 | # Functions.
15 |
16 | function _response() {
17 | local -r handlers="$@"
18 |
19 | local req
20 | req=$(request_parse) || \
21 | { local ec=$?; $(response_make_bad_request) to_string; return ${ec}; }
22 |
23 | local -i len
24 | len=$($handlers len) || \
25 | { return $EC; }
26 |
27 | local selh=${NULL}
28 | local -i i
29 | for (( i=0; i<${len}; i++ )); do
30 | local h=$($handlers get ${i})
31 | if [ "$($req path)" = "$($h path)" ]; then
32 | selh="$h"
33 | fi
34 | done
35 |
36 | if is_null "$selh"; then
37 | $(response_make_not_found) to_string
38 | return $?
39 | fi
40 |
41 | local -r res=$(response_make_ok)
42 | ( . $($selh script)
43 | $($selh func) "$res" "$req" ) || \
44 | { return $EC; }
45 |
46 | $res to_string
47 | }
48 |
49 | # ----------
50 | # Main.
51 |
52 | # TODO: Pass ctx.
53 |
54 | _response "$@" || \
55 | { $(response_make_internal_server_error) to_string; }
56 |
--------------------------------------------------------------------------------
/src/lang/runtime.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Runtime functions.
6 |
7 | if [ -n "${LANG_RUNTIME_MOD:-}" ]; then return 0; fi
8 | readonly LANG_RUNTIME_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${LANG_RUNTIME_MOD}/core.sh
11 | . ${LANG_RUNTIME_MOD}/bool.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function runtime_num_cpu() {
18 | # Return the number of logical CPUs.
19 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
20 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
21 | shift 0 || { ctx_wn $ctx; return $EC; }
22 |
23 | if is_mac; then
24 | sysctl -n hw.logicalcpu_max
25 | else
26 | [ ! -f "/proc/cpuinfo" ] && \
27 | { ctx_w $ctx "no cpuinfo"; return $EC; }
28 | cat /proc/cpuinfo | grep 'processor' | wc -l
29 | fi
30 | }
31 |
32 | function runtime_num_physical_cpu() {
33 | # Return the number of physical CPUs.
34 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
35 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
36 | shift 0 || { ctx_wn $ctx; return $EC; }
37 |
38 | if is_mac; then
39 | sysctl -n hw.physicalcpu_max
40 | else
41 | [ ! -f "/proc/cpuinfo" ] && \
42 | { ctx_w $ctx "no cpuinfo"; return $EC; }
43 | cat /proc/cpuinfo | grep 'core id' | sort -u | wc -l
44 | fi
45 | }
46 |
--------------------------------------------------------------------------------
/src/lang/bash_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the bash module.
6 |
7 | if [ -n "${BASH_TEST_MOD:-}" ]; then return 0; fi
8 | readonly BASH_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${BASH_TEST_MOD}/assert.sh
11 | . ${BASH_TEST_MOD}/bash.sh
12 | . ${BASH_TEST_MOD}/../testing/bunit.sh
13 |
14 |
15 | # ----------
16 | # Functions.
17 |
18 | function test_bash_version_major() {
19 | bash_version_major || assert_fail
20 | }
21 | readonly -f test_bash_version_major
22 |
23 | function test_bash_version_minor() {
24 | bash_version_minor || assert_fail
25 | }
26 | readonly -f test_bash_version_minor
27 |
28 | function test_bash_version_path() {
29 | bash_version_patch || assert_fail
30 | }
31 | readonly -f test_bash_version_path
32 |
33 | function test_bash_version_build() {
34 | bash_version_build || assert_fail
35 | }
36 | readonly -f test_bash_version_build
37 |
38 | function test_bash_version_release() {
39 | bash_version_release || assert_fail
40 | }
41 | readonly -f test_bash_version_release
42 |
43 | function test_bash_version_arch() {
44 | bash_version_arch || assert_fail
45 | }
46 | readonly -f test_bash_version_arch
47 |
48 | function test_bash_ci_version() {
49 | local t="${1}"
50 |
51 | [ -z "${GITHUB_ACTIONS}" ] && $t skip "Not running in CI."
52 | assert_eq "$(bash_version_major)" "${GOBASH_CI_BASH_VERSION}"
53 | }
54 | readonly -f test_bash_ci_version
55 |
--------------------------------------------------------------------------------
/examples/wait_group_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example similar to the one in Go documentation
4 | # (https://pkg.go.dev/sync#example-WaitGroup).
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../gobash
8 |
9 |
10 | function main() {
11 | # This function illustrates how we create and use
12 | # WaitGroup. However, this example does not really need
13 | # WaitGroup as we wait for all sub processes; we can simply
14 | # use wait command at the end of the function to wait for
15 | # every process that we spawned. The value of this example is to
16 | # illustrate how to create a WaitGroup, processes to
17 | # it, and wait (which would be a nice approach if one has to
18 | # wait only for a subset of processes).
19 |
20 | local -r wg=$(WaitGroup)
21 |
22 | # If values are needed only locally (not passing/returning
23 | # to/from functions/processes), we use array structure
24 | # available in bash.
25 | local -r urls=(
26 | "http://www.golang.org/"
27 | "http://www.google.com/"
28 | "http://www.example.com/"
29 | )
30 |
31 | local url
32 | for url in ${urls[@]}; do
33 | ( curl "${url}" 2>&1 ) &
34 |
35 | # Just like the wait command, it is only legal to wait
36 | # for sub processes (i.e., you should not be adding
37 | # random values to a WaitGroup).
38 | $wg add $!
39 | done
40 | $wg wait
41 | }
42 |
43 | main
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2023-present, Milos Gligoric
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | 3. Neither the name of the copyright holder nor the names of its
16 | contributors may be used to endorse or promote products derived from
17 | this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/doc/motivation.rst:
--------------------------------------------------------------------------------
1 |
2 | Motivation
3 | ==========
4 |
5 | bash is a nice scripting language (especially considering its age).
6 | Close integration of interpreters with operating systems and a ton of
7 | available binaries on these systems (e.g., `awk`, `sed`, `jq`) make it
8 | a great choice for quick and concise scripting. In recent years, some
9 | of the scripting has moved over to Python (and a few other languages)
10 | due to availability of (standard) libraries and testing support.
11 | However, seeing `import subprocess; subprocess.run(["ls", "-l"])` or
12 | similar code in Python, and then using replacements for `awk`, `sed`,
13 | `grep`, `git` commands (and awkwardly processing their outputs)
14 | suggests that developers may use bash more if it had better libraries
15 | and testing support.
16 |
17 | Key motivation points:
18 |
19 | * Provide a "standard" library for bash
20 | * Provide missing language features (without designing a new language or changing interpreters)
21 | * Enable using the same set of functions across various operating systems
22 | * Enable using different interpreters (and their versions) by hiding details behind APIs
23 |
24 | Finally, in recent years, we had a feeling that programming in bash
25 | can be similar to programming in Go (e.g., an easy way to run things
26 | in parallel `()&` vs. `go`, dealing with errors via exit codes,
27 | keeping API naming alike). Definitely not saying you should program in
28 | gobash instead of Go, but if you do end up writing a few lines in
29 | `bash` then they could look similar or give a similar feel like those
30 | you write in Go.
31 |
32 | .. toctree::
33 | :maxdepth: 2
34 |
--------------------------------------------------------------------------------
/src/sync/mutex.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Mutex related structs and functions.
6 |
7 | if [ -n "${MUTEX_MOD:-}" ]; then return 0; fi
8 | readonly MUTEX_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${MUTEX_MOD}/../lang/p.sh
11 |
12 | readonly MUTEX_SLEEP_TIME=0.01
13 |
14 |
15 | # ----------
16 | # Functions.
17 |
18 | function Mutex() {
19 | # Mutex.
20 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
21 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
22 | shift 0 || { ctx_wn $ctx; return $EC; }
23 |
24 | make_ $ctx \
25 | "${FUNCNAME}" \
26 | "mud" "$(os_mktemp_dir $ctx)/mutex"
27 | }
28 |
29 | function Mutex_lock() {
30 | # Lock.
31 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
32 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
33 | local -r mu="${1}"
34 | shift 1 || { ctx_wn $ctx; return $EC; }
35 |
36 | local -r mud=$($mu $ctx mud)
37 | # Can do better than busy waiting.
38 | while :; do
39 | if mkdir "${mud}" 2>/dev/null; then
40 | break
41 | fi
42 | sleep "${MUTEX_SLEEP_TIME}"
43 | done
44 | # Hoding the lock.
45 | }
46 |
47 | function Mutex_unlock() {
48 | # Unlock.
49 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
50 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
51 | local -r mu="${1}"
52 | shift 1 || { ctx_wn $ctx; return $EC; }
53 |
54 | rm -rf "$($mu $ctx mud)"
55 | }
56 |
--------------------------------------------------------------------------------
/examples/whiptail_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # This example illustrates a way to use `whiptail` via a bash
4 | # API. `whiptail` is very nice for simple dialogs/windows, but
5 | # providing input and capturing output is not always
6 | # pleasant. Provided API simplifies that handling.
7 |
8 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 | . ${DIR}/../gobash
10 |
11 | clear || \
12 | { echo "Your terminal lacks the ability to clear the screen or position the cursor";
13 | exit 0; }
14 |
15 |
16 | # Create a Message box and show it.
17 | box=$(WTMsgBox "Info that more examples are coming.")
18 | $box show
19 |
20 | # Create an Input box, capture the input and show it.
21 | box=$(WTInputBox "Please describe how much you like it.")
22 | res=$(UIResult)
23 | $box show "$res"
24 | $res to_string
25 |
26 | # Prepare a list for a Menu.
27 | lst=$(List)
28 | $lst add "Run"
29 | $lst add "Delete this example"
30 |
31 | # Create a Menu, capture the selected item, and print the item.
32 | res=$(UIResult)
33 | box=$(WTMenu "Actions" "$lst")
34 | $box show "$res"
35 | $res to_string
36 |
37 | # Prepare a list for a Checklist.
38 | lst=$(List)
39 | $lst add "root"
40 | $lst add "etc"
41 | $lst add "bin"
42 |
43 | # Create a Checklist, capture the selected items, and print the items.
44 | box=$(WTChecklist "Directories" "$lst")
45 | res=$(UIResult)
46 | $box show "$res"
47 | $($res val) to_string
48 |
49 | # Prepare a list for a Radiolist.
50 | lst=$(List)
51 | $lst add "root"
52 | $lst add "etc"
53 | $lst add "bin"
54 |
55 | # Create a Radiolist, capture the selected item, and print the item.
56 | box=$(WTRadiolist "Directory" "$lst")
57 | res=$(UIResult)
58 | $box show "$res"
59 | $res to_string
60 |
--------------------------------------------------------------------------------
/src/database/sql.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # SQL support (experimental).
6 |
7 | if [ -n "${SQL_MOD:-}" ]; then return 0; fi
8 | readonly SQL_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${SQL_MOD}/../lang/p.sh
11 |
12 |
13 | # ----------
14 | # Functions.
15 |
16 | function SQLite() {
17 | # SQL driver.
18 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
19 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
20 | local -r path="${1}"
21 | shift 1 || { ctx_wn $ctx; return $EC; }
22 |
23 | ! is_exe $ctx "sqlite3" && \
24 | { ctx_w $ctx "no sqlite3"; return $EC; }
25 |
26 | make_ $ctx \
27 | "${FUNCNAME}" \
28 | "path" "${path}"
29 | }
30 |
31 | function SQLite_query() {
32 | # Query db.
33 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
34 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
35 | local -r db="${1}"
36 | local -r query="${2}"
37 | shift 2 || { ctx_wn $ctx; return $EC; }
38 |
39 | local -r path=$($db $ctx path)
40 | sqlite3 "${path}" "${query}"
41 | }
42 |
43 | function sql_connect() {
44 | # Connect to a database using the given driver.
45 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
46 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
47 | local -r driver="${1}"
48 | local -r path="${2}"
49 | shift 2 || { ctx_wn $ctx; return $EC; }
50 |
51 | case "${driver}" in
52 | "sqlite3") SQLite $ctx "${path}"; return $?;;
53 | *) { ctx_w $ctx "unknown driver"; return $EC; }
54 | esac
55 | }
56 |
--------------------------------------------------------------------------------
/gobash_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Tests for the gobash module.
6 |
7 | if [ -n "${GOBASH_TEST_MOD:-}" ]; then return 0; fi
8 | readonly GOBASH_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${GOBASH_TEST_MOD}/gobash
11 |
12 |
13 | # ----------
14 | # Functions.
15 |
16 | function test_gobash_help() {
17 | gobash_help > /dev/null || \
18 | assert_fail
19 | }
20 | readonly -f test_gobash_help
21 |
22 | function test_gobash_func() {
23 | gobash_func > /dev/null && \
24 | assert_fail
25 |
26 | gobash_func sys_version > /dev/null || \
27 | assert_fail
28 | }
29 | readonly -f test_gobash_func
30 |
31 | function test_gobash_test() {
32 | gobash_test > /dev/null && \
33 | assert_fail
34 |
35 | return 0
36 | }
37 | readonly -f test_gobash_test
38 |
39 | function test_gobash_lint() {
40 | gobash_lint "${BASH_SOURCE[0]}" > /dev/null && \
41 | assert_fail
42 |
43 | return 0
44 | }
45 | readonly -f test_gobash_lint
46 |
47 | # function test_gobash_doc() {
48 | # :
49 | # }
50 | # readonly -f test_gobash_doc
51 |
52 | function test_gobash_version() {
53 | gobash_version > /dev/null || \
54 | assert_fail
55 | }
56 | readonly -f test_gobash_version
57 |
58 | function test_gobash_ctx() {
59 | local ctx=$(ctx_make)
60 |
61 | main $ctx test | grep 'has to be provided' > /dev/null || \
62 | assert_fail
63 |
64 | ctx_show $ctx | grep 'arguments do not pass check' || \
65 | assert_fail
66 | }
67 | readonly -f test_gobash_ctx
68 |
--------------------------------------------------------------------------------
/examples/web_server_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # This example illustrates a way to implement a simple web server.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | # A handle function that will be invoked when user asks for /date.
10 | function handle_date() {
11 | local -r res="${1}"
12 | local -r req="${2}"
13 | shift 2
14 |
15 | $res write "Sending this date back: $(date)"
16 | }
17 |
18 | function main() {
19 | # Address and port to listen on.
20 | local -r address="127.0.0.1"
21 | local -r -i port=9003
22 |
23 | # Create an Http instance.
24 | local -r http=$(Http "${address}" "${port}")
25 |
26 | # The next line adds a handler for /date. The first argument
27 | # is `path` to be handled. The second argument is the script
28 | # that contains the function that will handle the request; the
29 | # function name is given as the third argument. (Providing the
30 | # second script is needed, because handling is done in a
31 | # subshell, so we need to know what script to run in addition
32 | # to the function.)
33 | $http handle_func "/date" "${DIR}/$(basename ${BASH_SOURCE})" "handle_date"
34 |
35 | # Start listening and serving (creates a sub process) and wait
36 | # for the sub process to finish.
37 | $http listen_and_serve_and_wait
38 | }
39 |
40 | if [[ "${0}" == *"web_server_ex" ]]; then
41 | http_enabled || \
42 | { echo "http is not enabled"; exit 0; }
43 | main "$@"
44 | fi
45 |
46 | # Once you start this script, you can go to another terminal and curl
47 | # (or any other way you like):
48 | # curl -v http://127.0.0.1:9003/date # 200 OK
49 | # curl -v http://127.0.0.1:9003/none # 404 Not Found
50 |
--------------------------------------------------------------------------------
/src/ui/textui_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the textui module.
6 |
7 | if [ -n "${TEXTUI_TEST:-}" ]; then return 0; fi
8 | readonly TEXTUI_TEST=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${TEXTUI_TEST}/textui.sh
11 | . ${TEXTUI_TEST}/ui.sh
12 | . ${TEXTUI_TEST}/../testing/bunit.sh
13 |
14 |
15 | # ----------
16 | # Functions.
17 |
18 | function test_textui_enabled() {
19 | textui_enabled
20 | }
21 | readonly -f test_textui_enabled
22 |
23 | function test_textui_menu() {
24 | local lst=$(List "blue" "red" "green" "bright yellow")
25 |
26 | local menu
27 | menu=$(TextMenu "What is your favorite color?" "${lst}") || \
28 | assert_fail
29 |
30 | local res=$(UIResult)
31 | $menu show "${res}" <<< 0 || \
32 | assert_fail
33 | assert_eq "blue" "$($res val)"
34 | }
35 | readonly -f test_textui_menu
36 |
37 | function test_textui_menu_invalid_value() {
38 | local lst=$(List "red" "blue")
39 |
40 | local menu
41 | menu=$(TextMenu "What is your favorite color?" "${lst}") || \
42 | assert_fail
43 |
44 | local res=$(UIResult)
45 | $menu show "${res}" <<< 80 && \
46 | assert_fail
47 | assert_eq "${NULL}" "$($res val)"
48 | }
49 | readonly -f test_textui_menu_invalid_value
50 |
51 | function test_textui_progress() {
52 | local tp
53 | tp=$(TextProgress 20) || assert_fail
54 |
55 | $tp start
56 | $tp inc
57 | $tp stop
58 | }
59 | readonly -f test_textui_progress
60 |
61 | function test_textui_spinner() {
62 | local ts
63 | ts=$(TextSpinner) || assert_fail
64 |
65 | $ts start
66 | sleep 3
67 | $ts stop
68 | }
69 | readonly -f test_textui_spinner
70 |
--------------------------------------------------------------------------------
/src/lang/result.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Utility for collecting results from a function invocation.
6 |
7 | if [ -n "${RESULT_MOD:-}" ]; then return 0; fi
8 | readonly RESULT_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${RESULT_MOD}/core.sh
11 | . ${RESULT_MOD}/make.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function Result() {
18 | # Result.
19 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
20 | [ $# -lt 0 -o $# -gt 1 ] && { ctx_wn $ctx; return $EC; }
21 | local -r val="${1:-${NULL}}"
22 | shift 0 || { ctx_wn $ctx; return $EC; }
23 |
24 | make_ $ctx \
25 | "${FUNCNAME}" \
26 | "val" "${val}"
27 | }
28 |
29 | function Result_has_value() {
30 | # True if value is set.
31 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
32 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
33 | local -r res="${1}"
34 | shift 1 || { ctx_wn $ctx; return $EC; }
35 |
36 | [ "${NULL}" = "$($res $ctx val)" ] && return $FALSE
37 | return $TRUE
38 | }
39 |
40 | function Result_to_string() {
41 | # String representation of the result.
42 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
43 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
44 | local -r res="${1}"
45 | shift 1 || { ctx_wn $ctx; return $EC; }
46 |
47 | if Result_has_value $ctx "$res"; then
48 | if unsafe_is_object $ctx "$($res val)"; then
49 | $($res $ctx val) $ctx to_string
50 | else
51 | echo "$($res $ctx val)"
52 | fi
53 | else
54 | local uid=$($res)
55 | unsafe_to_string $ctx "${uid}"
56 | fi
57 |
58 | return 0
59 | }
60 |
--------------------------------------------------------------------------------
/src/lang/log_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the log module.
6 |
7 | if [ -n "${LOG_TEST_MOD:-}" ]; then return 0; fi
8 | readonly LOG_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${LOG_TEST_MOD}/assert.sh
11 | . ${LOG_TEST_MOD}/log.sh
12 | . ${LOG_TEST_MOD}/../testing/bunit.sh
13 |
14 |
15 | # ----------
16 | # Functions.
17 |
18 | function test_log_i() {
19 | rm -f "${_LOG_FILE}"
20 | log_i || \
21 | assert_fail
22 | log_i "Text" || \
23 | assert_fail
24 |
25 | grep 'Text' "${_LOG_FILE}" > /dev/null || \
26 | assert_fail
27 |
28 | local n
29 | n=$(cat "${_LOG_FILE}" | grep 'INFO::' | $X_WC -l | $X_SED 's/^[[:space:]]*//') || \
30 | assert_fail
31 | assert_eq 2 "${n}"
32 | }
33 | readonly -f test_log_i
34 |
35 | function test_log_w() {
36 | rm -f "${_LOG_FILE}"
37 | log_w || \
38 | assert_fail
39 | log_w "Text" || \
40 | assert_fail
41 |
42 | grep 'Text' "${_LOG_FILE}" > /dev/null || \
43 | assert_fail
44 |
45 | local n
46 | n=$(cat "${_LOG_FILE}" | grep 'WARN::' | $X_WC -l | $X_SED 's/^[[:space:]]*//') || \
47 | assert_fail
48 | assert_eq 2 "${n}"
49 | }
50 | readonly -f test_log_w
51 |
52 | function test_log_e() {
53 | rm -f "${_LOG_FILE}"
54 | log_e || \
55 | assert_fail
56 | log_e "Text" || \
57 | assert_fail
58 |
59 | grep 'Text' "${_LOG_FILE}" > /dev/null || \
60 | assert_fail
61 |
62 | local n
63 | n=$(cat "${_LOG_FILE}" | grep 'ERROR::' | $X_WC -l | $X_SED 's/^[[:space:]]*//') || \
64 | assert_fail
65 | assert_eq 2 "${n}"
66 | }
67 | readonly -f test_log_e
68 |
--------------------------------------------------------------------------------
/examples/playground/test_function_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example that corresponds to "Test Function" on https://go.dev/play/.
4 | # License for the corresponding code https://go.dev/LICENSE?m=text.
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../../gobash
8 |
9 |
10 | function last_index() {
11 | local -r lst="${1}"
12 | local -r x="${2}"
13 | shift 2
14 |
15 | local -i i=$($lst len)
16 | for (( i-- ; i>0; i-- )); do
17 | [ "$($lst get ${i})" = "${x}" ] && echo "${i}" && return 0
18 | done
19 | echo "-1"
20 | }
21 |
22 | function test_last_index() {
23 | local -r tests=$(List)
24 |
25 | local tt
26 |
27 | tt=$(amake_ "list" "$(List 1)" "x" 1 "want" 0)
28 | $tests add "$tt"
29 |
30 | tt=$(amake_ "list" "$(List 1 1)" "x" 1 "want" 1)
31 | $tests add "$tt"
32 |
33 | tt=$(amake_ "list" "$(List 2 1)" "x" 2 "want" 0)
34 | $tests add "$tt"
35 |
36 | tt=$(amake_ "list" "$(List 1 2 1 1)" "x" 2 "want" 1)
37 | $tests add "$tt"
38 |
39 | tt=$(amake_ "list" "$(List 1 1 1 2 2 1)" "x" 3 "want" -1)
40 | $tests add "$tt"
41 |
42 | tt=$(amake_ "list" "$(List 3 1 2 2 1 1)" "x" 3 "want" 0)
43 | $tests add "$tt"
44 |
45 | local ec=0
46 | local -i i
47 | for (( i=0; i<$($tests len); i++ )); do
48 | local tt=$($tests get "${i}")
49 | local ix=$(last_index "$($tt list)" "$($tt x)")
50 | if [ "${ix}" != "$($tt want)" ]; then
51 | printf "LastIndex(%s, %s) = %s, want %s\n" \
52 | "$($($tt list) to_string | paste -sd' ')" \
53 | "$($tt x)" \
54 | "${ix}" \
55 | "$($tt want)"
56 | ec=1
57 | fi
58 | done
59 | return ${ec}
60 | }
61 |
--------------------------------------------------------------------------------
/src/lang/pipe.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Named pipe wrapper.
6 | #
7 | # @deprecated(this did not end up as planned.)
8 |
9 | if [ -n "${PIPE_MOD:-}" ]; then return 0; fi
10 | readonly PIPE_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
11 |
12 | . ${PIPE_MOD}/core.sh
13 | . ${PIPE_MOD}/make.sh
14 | . ${PIPE_MOD}/os.sh
15 |
16 | readonly PIPE_END="__closed__"
17 |
18 |
19 | # ----------
20 | # Functions.
21 |
22 | function Pipe() {
23 | # Named pipe.
24 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
25 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
26 | shift 0 || { ctx_wn $ctx; return $EC; }
27 |
28 | local -r rawd="$(os_mktemp_dir $ctx)/fifo"
29 | mkfifo "${rawd}"
30 |
31 | make_ $ctx \
32 | "${FUNCNAME}" \
33 | "rawd" "${rawd}"
34 | }
35 |
36 | function Pipe_send() {
37 | # Send.
38 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
39 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
40 | local -r pipe="${1}"
41 | local -r val="${2}"
42 | shift 2 || { ctx_wn $ctx; return $EC; }
43 |
44 | echo "${val}" >"$($pipe $ctx rawd)"
45 | }
46 |
47 | function Pipe_close() {
48 | # Close.
49 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
50 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
51 | local -r pipe="${1}"
52 | shift 1 || { ctx_wn $ctx; return $EC; }
53 |
54 | echo "${PIPE_END}" >"$($pipe $ctx rawd)"
55 | }
56 |
57 | function Pipe_recv() {
58 | # Receive.
59 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
60 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
61 | local -r pipe="${1}"
62 | shift 1 || { ctx_wn $ctx; return $EC; }
63 |
64 | read line <"$($pipe $ctx rawd)"
65 | [ "${line}" = "${PIPE_END}" ] && return $FALSE
66 |
67 | echo "${line}"
68 | return $TRUE
69 | }
70 |
--------------------------------------------------------------------------------
/src/util/set_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the set module.
6 |
7 | if [ -n "${SET_TEST_MOD:-}" ]; then return 0; fi
8 | readonly SET_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${SET_TEST_MOD}/set.sh
11 | . ${SET_TEST_MOD}/../testing/bunit.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function test_set() {
18 | local s
19 | s=$(Set) || \
20 | assert_fail
21 | }
22 | readonly -f test_set
23 |
24 | function test_set_len() {
25 | local s
26 | s=$(Set) || \
27 | assert_fail
28 |
29 | local len
30 | len=$($s len) || \
31 | assert_fail
32 | assert_eq 0 "${len}" "Len not 0."
33 |
34 | $s add 5
35 | $s add 10
36 | len=$($s len) || \
37 | assert_fail
38 | assert_eq 2 "${len}" "Len not 2."
39 |
40 | $s add 5
41 | len=$($s len) || \
42 | assert_fail
43 | assert_eq 2 "${len}" "Len not 2."
44 | }
45 | readonly -f test_set_len
46 |
47 | function test_set_contains() {
48 | local s
49 | s=$(Set) || \
50 | assert_fail
51 |
52 | $s add 5 || \
53 | assert_fail
54 | $s add 10 || \
55 | assert_fail
56 |
57 | $s contains 5 || \
58 | assert_fail
59 |
60 | local ec=0
61 | $s contains 11 || ec=$?
62 | assert_false ${ec}
63 | }
64 | readonly -f test_set_contains
65 |
66 | function test_set_clear() {
67 | local s
68 | s=$(Set) || \
69 | assert_fail
70 |
71 | $s add 5 || \
72 | assert_fail
73 | $s add 10 || \
74 | assert_fail
75 |
76 | local len
77 | len=$($s len) || \
78 | assert_fail
79 | assert_eq 2 "${len}"
80 |
81 | $s clear
82 | len=$($s len) || \
83 | assert_fail
84 | assert_eq 0 "${len}"
85 | }
86 | readonly -f test_set_clear
87 |
--------------------------------------------------------------------------------
/doc/index.rst:
--------------------------------------------------------------------------------
1 |
2 | gobash documentation
3 | ====================
4 |
5 | gobash, a self-proclaimed standard bash library, is a set of bash
6 | functions that improve programming experience in bash (by providing
7 | collections, structs, methods, APIs, testing package, command line
8 | flag parsing, etc.) without modifying the shell interpreter(s). It
9 | works with any bash version (on Linux and Mac). Parts of the API match
10 | those in Go.
11 |
12 | gobash is publicly available on `GitHub
13 | `_ under the
14 | `BSD-3-Clause license
15 | `_. The
16 | dependencies needed to run gobash are listed on that GitHub page.
17 |
18 | Here is a quick example that uses gobash (check later sections for
19 | more):
20 |
21 | .. code-block:: bash
22 |
23 | #!/bin/bash
24 |
25 | # Import the library.
26 | source /dev/stdin <<< "$(curl https://raw.githubusercontent.com/EngineeringSoftware/gobash/main/hsabog 2>/dev/null)"
27 |
28 | # Create a communication channel.
29 | ch=$(Chan)
30 | # Send a message (blocking call) in a sub process.
31 | ( lst=$(List 2 3 5); $ch send "$lst" ) &
32 |
33 | # Receive the message (blocking call) in the main process.
34 | lst=$($ch recv)
35 |
36 | $lst to_string
37 | # Output:
38 | # [
39 | # "2",
40 | # "3",
41 | # "5"
42 | # ]
43 |
44 | If you love learning by example, take a look at the `examples page
45 | `_.
46 | A quick demo of the very basic concepts using a toy example is
47 | available `here
48 | `_.
49 |
50 | .. toctree::
51 | :maxdepth: 2
52 | :caption: Contents:
53 |
54 | motivation
55 | design
56 | get-started
57 | language
58 | collections
59 | process-communication
60 | interactive-mode
61 | testing
62 | command-line-flags
63 | next
64 | api
65 |
66 | Indices and tables
67 | ==================
68 |
69 | * :ref:`genindex`
70 | * :ref:`search`
71 |
--------------------------------------------------------------------------------
/examples/result_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Illustrates `Result` to return value and error from a function.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | function sum() {
10 | # One way to return a value is by passing an "output" argument
11 | # (an instance of `Result`) to a function, which will be
12 | # populated in the function.
13 | local -r res="${1}"
14 | local -r a="${2}"
15 | local -r b="${3}"
16 |
17 | # Check all the arguments.
18 | [ -z "${res}" ] && ctx_w 'No arguments' && return $EC
19 |
20 | # When we detect an error, we write into the default context.
21 | [ -z "${a}" ] && ctx_w 'a was not set' && return $EC
22 | [ -z "${b}" ] && ctx_w 'b was not set' && return $EC
23 |
24 | ! is_int "${a}" && ctx_w 'a is not an int' && return $EC
25 | ! is_int "${b}" && ctx_w 'b is not an int' && return $EC
26 |
27 | # Set the value of the result.
28 | $res val $(( ${a} + ${b} ))
29 | return 0
30 | }
31 |
32 | ctx_clear
33 | # Not providing any argument.
34 | sum || ctx_show
35 | # Output:
36 | # No arguments
37 | # 18 sum ./result_ex
38 | # 37 main ./result_ex
39 |
40 | ctx_clear
41 | # Providing result but no other argument.
42 | res=$(Result)
43 | sum "$res" || ctx_show
44 | # Output:
45 | # a was not set
46 | # 21 sum ./result_ex
47 | # 43 main ./result_ex
48 |
49 | ctx_clear
50 | # Providing result and one argument.
51 | res=$(Result)
52 | sum "$res" 5 || ctx_show
53 | # Output:
54 | # b was not set
55 | # 22 sum ./result_ex
56 | # 52 main ./result_ex
57 |
58 | ctx_clear
59 | # Providing both arguments but wrong type.
60 | res=$(Result)
61 | sum "$res" "a" "b" || ctx_show
62 | # Output:
63 | # a is not an int
64 | # 24 sum ./result_ex
65 | # 61 main ./result_ex
66 |
67 | ctx_clear
68 | # Providing all arguments with correct types.
69 | res=$(Result)
70 | sum "$res" 10 5 || \
71 | { echo "This should never happen."; }
72 |
73 | # Get the value and then print it.
74 | echo $($res val)
75 | # Output: 15
76 |
77 | # If there is no error to_string for `Result` print the value.
78 | $res to_string
79 | # Output: 15
80 |
--------------------------------------------------------------------------------
/src/tools/blint_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the blint module.
6 |
7 | if [ -n "${BLINT_TEST_MOD:-}" ]; then return 0; fi
8 | readonly BLINT_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${BLINT_TEST_MOD}/blint.sh
11 | . ${BLINT_TEST_MOD}/../testing/bunit.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function _blint_testfile() {
18 | echo "${BLINT_TEST_MOD}/testdata/${1}"
19 | }
20 |
21 | function test_blint_check_she_bang() {
22 | local -r res=$(BLintResult)
23 | _blint_check_she_bang "${res}" $(_blint_testfile "blint_she_bang")
24 |
25 | $res has_failing || \
26 | assert_fail
27 |
28 | assert_eq 1 "$($res nfailing)"
29 | }
30 | readonly -f test_blint_check_she_bang
31 |
32 | function test_blint_check_tabs() {
33 | local -r res=$(BLintResult)
34 | _blint_check_tabs "${res}" $(_blint_testfile "blint_tabs")
35 |
36 | $res has_failing || \
37 | assert_fail
38 |
39 | assert_eq 1 "$($res nfailing)"
40 | }
41 | readonly -f test_blint_check_tabs
42 |
43 | function test_blint_check_signature() {
44 | local -r res=$(BLintResult)
45 | _blint_check_signature "${res}" $(_blint_testfile "blint_signature")
46 |
47 | $res has_failing || \
48 | assert_fail
49 |
50 | assert_eq 2 "$($res nfailing)"
51 | }
52 | readonly -f test_blint_check_signature
53 |
54 | function test_blint_check_brief_doc() {
55 | local -r res=$(BLintResult)
56 | _blint_check_brief_doc "${res}" $(_blint_testfile "blint_brief_doc")
57 |
58 | $res has_failing || \
59 | assert_fail
60 |
61 | assert_eq 1 "$($res nfailing)"
62 | }
63 | readonly -f test_blint_check_brief_doc
64 |
65 | function test_blint_check_readonly_tests() {
66 | local -r res=$(BLintResult)
67 | _blint_check_readonly_tests "${res}" $(_blint_testfile "blint_readonly_tests")
68 |
69 | $res has_failing || \
70 | assert_fail
71 |
72 | assert_eq 1 "$($res nfailing)"
73 | }
74 | readonly -f test_blint_check_readonly_tests
75 |
--------------------------------------------------------------------------------
/src/lang/bash.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # API for some bash variables.
6 | # https://tldp.org/LDP/abs/html/internalvariables.html
7 |
8 | if [ -n "${BASH_MOD:-}" ]; then return 0; fi
9 | readonly BASH_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
10 |
11 | . ${BASH_MOD}/core.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function bash_version_major() {
18 | # Return bash major number.
19 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
20 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
21 | shift 0 || { ctx_wn $ctx; return $EC; }
22 |
23 | echo "${BASH_VERSINFO[0]}"
24 | }
25 |
26 | function bash_version_minor() {
27 | # Return bash minor number.
28 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
29 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
30 | shift 0 || { ctx_wn $ctx; return $EC; }
31 |
32 | echo "${BASH_VERSINFO[1]}"
33 | }
34 |
35 | function bash_version_patch() {
36 | # Return bash patch number.
37 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
38 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
39 | shift 0 || { ctx_wn $ctx; return $EC; }
40 |
41 | echo "${BASH_VERSINFO[2]}"
42 | }
43 |
44 | function bash_version_build() {
45 | # Return bash build number.
46 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
47 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
48 | shift 0 || { ctx_wn $ctx; return $EC; }
49 |
50 | echo "${BASH_VERSINFO[3]}"
51 | }
52 |
53 | function bash_version_release() {
54 | # Return bash release string.
55 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
56 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
57 | shift 0 || { ctx_wn $ctx; return $EC; }
58 |
59 | echo "${BASH_VERSINFO[4]}"
60 | }
61 |
62 | function bash_version_arch() {
63 | # Return bash architecture string.
64 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
65 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
66 | shift 0 || { ctx_wn $ctx; return $EC; }
67 |
68 | echo "${BASH_VERSINFO[5]}"
69 | }
70 |
--------------------------------------------------------------------------------
/src/lang/core_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the core module.
6 | #
7 | # These tests intentionally do *not* use assert functions.
8 |
9 | if [ -n "${CORE_TEST_MOD:-}" ]; then return 0; fi
10 | readonly CORE_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
11 |
12 | . ${CORE_TEST_MOD}/core.sh
13 | . ${CORE_TEST_MOD}/../testing/bunit.sh
14 |
15 |
16 | # ----------
17 | # Functions.
18 |
19 | function test_core_mktemp_file() {
20 | local -r tmpd="$(core_tmp_dir)"
21 |
22 | local f
23 | f=$(core_mktemp_file "${tmpd}" "ABC@XXXX" ".z") || return $EC
24 |
25 | local -r re="${tmpd}/ABC@.....z"
26 | [[ "${f}" =~ ${re} ]] || return $EC
27 |
28 | f=$(core_mktemp_file "${tmpd}" "XXXX" ".json")
29 | [[ "${f}" = *".json" ]] || return $EC
30 |
31 | f=$(core_mktemp_file "${tmpd}" "XXXX" ".ctx")
32 | [[ "${f}" = *".ctx" ]] || return $EC
33 | }
34 | readonly -f test_core_mktemp_file
35 |
36 | function test_core_ctx_make() {
37 | local -r c1=$(ctx_make)
38 | local -r c2=$(ctx_make)
39 |
40 | [ "${c1}" != "${c2}" ]
41 | }
42 | readonly -f test_core_ctx_make
43 |
44 | function test_core_ctx_is() {
45 | local -r c=$(ctx_make)
46 |
47 | is_ctx "$c"
48 | }
49 | readonly -f test_core_ctx_is
50 |
51 | function test_core_ctx_w() {
52 | local -r ctx=$(ctx_make)
53 |
54 | ctx_w "$ctx" "random message"
55 |
56 | [ ! -f "$(core_obj_dir)/${ctx}.ctx" ] && return $EC
57 | [ ! -f "$(core_obj_dir)/${ctx}.strace" ] && return $EC
58 |
59 | return 0
60 | }
61 | readonly -f test_core_ctx_w
62 |
63 | function test_core_ctx_show() {
64 | local -r ctx=$(ctx_make)
65 |
66 | ctx_w "$ctx" "random message" || return $EC
67 | ctx_show "$ctx" | grep 'random'
68 | }
69 | readonly -f test_core_ctx_show
70 |
71 | function test_core_ctx_global() {
72 | ctx_w "random message" || return $EC
73 |
74 | [ ! -f "$(core_obj_dir)/context.txt" ] && return $EC
75 | [ ! -f "$(core_obj_dir)/strace.txt" ] && return $EC
76 |
77 | ctx_show | grep 'random'
78 | }
79 | readonly -f test_core_ctx_global
80 |
--------------------------------------------------------------------------------
/src/util/os_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the os module.
6 |
7 | if [ -n "${UTIL_OS_TEST_MOD:-}" ]; then return 0; fi
8 | readonly UTIL_OS_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${UTIL_OS_TEST_MOD}/os.sh
11 |
12 |
13 | # ----------
14 | # Functions.
15 |
16 | function test_os_stat() {
17 | local fi
18 |
19 | fi=$(os_stat "$(sys_repo_path)/README.md") || \
20 | assert_fail
21 |
22 | assert_eq "README.md" "$($fi name)"
23 | assert_gt "$($fi size)" 3000
24 | # The value might not be the same in CI.
25 | # assert_eq "-rw-rw-r--" "$($fi mode)"
26 | $fi is_dir && \
27 | assert_fail
28 | is_null "$($fi mod_time)" && \
29 | assert_fail
30 |
31 | $fi to_string | grep 'README.md' || \
32 | assert_fail
33 |
34 | local ctx
35 | ctx=$(ctx_make)
36 | fi=$(os_stat $ctx "blabblah") && \
37 | assert_fail
38 | ctx_show $ctx | grep 'incorrect path' || \
39 | assert_fail
40 | }
41 | readonly -f test_os_stat
42 |
43 | function _abc() {
44 | local a="${1}"
45 | local b="${2}"
46 |
47 | sleep 60
48 | local c=$(( ${a} + ${b} ))
49 | echo ${c}
50 | }
51 |
52 | function test_os_timeout() {
53 | os_timeout 5 "_abc" 3 4 && \
54 | assert_fail
55 |
56 | return 0
57 | }
58 | readonly -f test_os_timeout
59 |
60 | function _def() {
61 | :
62 | }
63 |
64 | function test_os_wo_timeout() {
65 | os_timeout 60 "_def" "a" "b" || \
66 | assert_fail
67 | }
68 | readonly -f test_os_wo_timeout
69 |
70 | function test_os_disable_timeout() {
71 | os_timeout 0 "_def" "a" "b" || \
72 | assert_fail
73 | }
74 | readonly -f test_os_disable_timeout
75 |
76 | function test_os_loop_n() {
77 | os_loop_n 3 "_def" || \
78 | assert_fail
79 | }
80 | readonly -f test_os_loop_n
81 |
82 | function test_os_loop_secs() {
83 | os_loop_secs 3 "_def" || \
84 | assert_fail
85 | }
86 | readonly -f test_os_loop_secs
87 |
--------------------------------------------------------------------------------
/src/util/filepath.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Util functions to manipulate file paths.
6 |
7 | if [ -n "${FILEPATH_MOD:-}" ]; then return 0; fi
8 | readonly FILEPATH_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${FILEPATH_MOD}/../lang/p.sh
11 |
12 |
13 | # ----------
14 | # Functions.
15 |
16 | function filepath_base() {
17 | # Return the last element on the path. If path is an empty
18 | # string, the output is ".".
19 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
20 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
21 | local -r path="${1}"
22 | shift 1 || { ctx_wn $ctx; return $EC; }
23 |
24 | if [ -z "${path}" ]; then
25 | echo "." || return $?
26 | else
27 | basename -- "${path}" || return $?
28 | fi
29 | }
30 |
31 | function filepath_ext() {
32 | # Return the extension, i.e., text after the final dot on the
33 | # last element of the path. It is empty if there is no dot.
34 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
35 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
36 | local -r path="${1}"
37 | shift 1 || { ctx_wn $ctx; return $EC; }
38 |
39 | local -r filename=$(basename -- "${path}")
40 | if [[ "${filename}" != *"."* ]]; then
41 | echo "" || return $?
42 | else
43 | local -r ext="${filename##*.}"
44 | echo ".${ext}" || return $?
45 | fi
46 | }
47 |
48 | function filepath_dir() {
49 | # Return all but the last element on path. Trailing / (one or
50 | # more) are ignored. If the path is empty, return ".".
51 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
52 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
53 | local -r path="${1}"
54 | shift 1 || { ctx_wn $ctx; return $EC; }
55 |
56 | dirname "${path}"
57 | }
58 |
59 | function filepath_is_abs() {
60 | # Return true if the path is absolute; otherwise false.
61 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
62 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
63 | local -r path="${1}"
64 | shift 1 || { ctx_wn $ctx; return $EC; }
65 |
66 | [[ "${path}" = /* ]]
67 | }
68 |
--------------------------------------------------------------------------------
/src/util/rand_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the rand module.
6 |
7 | if [ -n "${RAND_TEST_MOD:-}" ]; then return 0; fi
8 | readonly RAND_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${RAND_TEST_MOD}/rand.sh
11 | . ${RAND_TEST_MOD}/strings.sh
12 | . ${RAND_TEST_MOD}/../testing/bunit.sh
13 |
14 |
15 | # ----------
16 | # Functions.
17 |
18 | function test_rand_bool() {
19 | local val
20 | val=$(rand_bool) || \
21 | assert_fail
22 |
23 | is_bool "${val}" || \
24 | assert_fail
25 | }
26 | readonly -f test_rand_bool
27 |
28 | function test_rand_return() {
29 | [ rand_return ] || return 0
30 | }
31 | readonly -f test_rand_return
32 |
33 | function test_rand_args() {
34 | local val
35 | val=$(rand_args "one" "two")
36 |
37 | [ "${val}" = "one" -o "${val}" = "two" ] || \
38 | assert_fail
39 |
40 | rand_args && assert_fail
41 |
42 | return 0
43 | }
44 | readonly -f test_rand_args
45 |
46 | function test_rand_int() {
47 | local val
48 | val=$(rand_int) || \
49 | assert_fail
50 |
51 | is_int "${val}" || \
52 | assert_fail
53 | }
54 | readonly -f test_rand_int
55 |
56 | function test_rand_intn() {
57 | local val
58 | val=$(rand_intn 10) || \
59 | assert_fail
60 | assert_bw "${val}" 0 9
61 | }
62 | readonly -f test_rand_intn
63 |
64 | function test_rand_string() {
65 | local val
66 | local ctx
67 |
68 | val=$(rand_string) || \
69 | assert_fail
70 | assert_eq 32 $(strings_len "${val}")
71 |
72 | val=$(rand_string 6) || \
73 | assert_fail
74 | assert_eq 6 $(strings_len "${val}")
75 |
76 | ctx=$(ctx_make)
77 | rand_string $ctx -1 && \
78 | assert_fail
79 | ctx_show $ctx | grep 'len' || \
80 | assert_fail
81 |
82 | ctx=$(ctx_make)
83 | rand_string $ctx 33 && \
84 | assert_fail
85 | ctx_show $ctx | grep 'len' || \
86 | assert_fail
87 |
88 | val=$(rand_string 32) || \
89 | assert_fail
90 | assert_eq 32 $(strings_len "${val}")
91 | }
92 | readonly -f test_rand_string
93 |
--------------------------------------------------------------------------------
/src/lang/int_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the int module.
6 |
7 | if [ -n "${INT_TEST_MOD:-}" ]; then return 0; fi
8 | readonly INT_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${INT_TEST_MOD}/assert.sh
11 | . ${INT_TEST_MOD}/bool.sh
12 | . ${INT_TEST_MOD}/int.sh
13 | . ${INT_TEST_MOD}/../testing/bunit.sh
14 |
15 |
16 | # ----------
17 | # Functions.
18 |
19 | function test_int() {
20 | local z
21 | z=$(Int) || \
22 | assert_fail
23 | assert_eq 0 "$($z val)"
24 |
25 | local i=$(Int 33)
26 | assert_eq 33 "$($i val)"
27 | }
28 | readonly -f test_int
29 |
30 | function test_int_inc() {
31 | local i=$(Int 33)
32 | $i inc
33 | assert_eq 34 "$($i val)"
34 | }
35 | readonly -f test_int_inc
36 |
37 | function test_int_dec() {
38 | local i=$(Int 33)
39 | $i dec
40 | assert_eq 32 "$($i val)"
41 | }
42 | readonly -f test_int_dec
43 |
44 | function test_int_gt() {
45 | local i=$(Int 33)
46 |
47 | $i gt 32 || assert_fail
48 | $i gt 45 && assert_fail
49 |
50 | local i2=$(Int 34)
51 | $i gt "${i2}" && assert_fail
52 |
53 | local i3=$(Int 32)
54 | $i gt "${i3}" || assert_fail
55 | }
56 | readonly -f test_int_gt
57 |
58 | function test_int_ge() {
59 | local i=$(Int 33)
60 |
61 | $i ge 33 || assert_fail
62 | $i ge 32 || assert_fail
63 | $i ge 44 && assert_fail
64 |
65 | return 0
66 | }
67 | readonly -f test_int_ge
68 |
69 | function test_int_eq() {
70 | local i=$(Int 33)
71 |
72 | $i eq 33 || assert_fail
73 | $i eq 34 && assert_fail
74 | $i eq 32 && assert_fail
75 |
76 | return 0
77 | }
78 | readonly -f test_int_eq
79 |
80 | function test_int_lt() {
81 | local i=$(Int 33)
82 |
83 | $i lt 45 || assert_fail
84 | $i lt 33 && assert_fail
85 | $i lt 20 && assert_fail
86 |
87 | return 0
88 | }
89 | readonly -f test_int_lt
90 |
91 | function test_int_le() {
92 | local i=$(Int 33)
93 |
94 | $i le 45 || assert_fail
95 | $i le 33 || assert_fail
96 | $i le 20 && assert_fail
97 |
98 | return 0
99 | }
100 | readonly -f test_int_le
101 |
--------------------------------------------------------------------------------
/examples/playground/playground_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Tests for running available playground.
6 |
7 | if [ -n "${PLAYGROUND_TEST:-}" ]; then return 0; fi
8 | readonly PLAYGROUND_TEST=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${PLAYGROUND_TEST}/../../gobash
11 |
12 |
13 | # ----------
14 | # Functions.
15 |
16 | function _exet() {
17 | local funcn="${1}"
18 | shift 1 || return $EC
19 |
20 | "${PLAYGROUND_TEST}/${funcn#test_playground_*}" "$@"
21 | }
22 |
23 | function test_playground_clear_screen_ex() {
24 | _exet "$FUNCNAME"
25 | }
26 | readonly -f test_playground_clear_screen_ex
27 |
28 | function test_playground_concurrent_pi_ex() {
29 | _exet "$FUNCNAME"
30 | }
31 | readonly -f test_playground_concurrent_pi_ex
32 |
33 | function test_playground_hellow_world_ex() {
34 | _exet "$FUNCNAME"
35 | }
36 | readonly -f test_playground_hellow_world_ex
37 |
38 | function test_playground_http_server_ex() {
39 | _exet "$FUNCNAME"
40 | }
41 | readonly -f test_playground_http_server_ex
42 |
43 | function test_playground_sleep_ex() {
44 | _exet "$FUNCNAME"
45 | }
46 | readonly -f test_playground_sleep_ex
47 |
48 | function test_playground_test_function_ex() {
49 | _exet "$FUNCNAME"
50 | }
51 | readonly -f test_playground_test_function_ex
52 |
53 | function test_playground_ring_do_ex() {
54 | _exet "$FUNCNAME"
55 | }
56 | readonly -f test_playground_ring_do_ex
57 |
58 | function test_playground_ring_len_ex() {
59 | _exet "$FUNCNAME"
60 | }
61 | readonly -f test_playground_ring_len_ex
62 |
63 | function test_playground_ring_link_ex() {
64 | _exet "$FUNCNAME"
65 | }
66 | readonly -f test_playground_ring_link_ex
67 |
68 | function test_playground_ring_move_ex() {
69 | _exet "$FUNCNAME"
70 | }
71 | readonly -f test_playground_ring_move_ex
72 |
73 | function test_playground_ring_next_ex() {
74 | _exet "$FUNCNAME"
75 | }
76 | readonly -f test_playground_ring_next_ex
77 |
78 | function test_playground_ring_prev_ex() {
79 | _exet "$FUNCNAME"
80 | }
81 | readonly -f test_playground_ring_prev_ex
82 |
83 | function test_playground_ring_unlink_ex() {
84 | _exet "$FUNCNAME"
85 | }
86 | readonly -f test_playground_ring_unlink_ex
87 |
88 | function test_playground_list_ex() {
89 | _exet "$FUNCNAME"
90 | }
91 | readonly -f test_playground_list_ex
92 |
--------------------------------------------------------------------------------
/examples/shapes_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # An example that illustrates `methods`. The example introduces
4 | # `structs` for Circle, Square, and Rectangle, as well as a
5 | # `methods` for each of them for computing area.
6 |
7 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
8 | . ${DIR}/../gobash
9 |
10 |
11 | function Circle() {
12 | [ $# -ne 1 ] && return $EC
13 | local -r r="${1}"
14 | shift 1
15 |
16 | make_ $FUNCNAME \
17 | "r" "${r}"
18 | # We do not need to return explicitly (because the
19 | # default output is the result of the last command, but
20 | # being explicit is good in some cases).
21 | # return $?
22 | }
23 |
24 | function Circle_area() {
25 | local -r c="${1}"
26 | shift 1
27 |
28 | local result
29 | result=$(echo "$MATH_PI * $($c r) * $($c r)" | bc)
30 | echo ${result}
31 | return 0
32 | }
33 |
34 | function Square() {
35 | [ $# -ne 1 ] && return $EC
36 | local -r a="${1}"
37 | shift 1
38 |
39 | make_ $FUNCNAME \
40 | "a" "${a}"
41 | }
42 |
43 | function Square_area() {
44 | local -r obj="${1}"
45 | shift 1
46 |
47 | local result
48 | result=$(( $($obj "a") * $($obj "a") ))
49 | echo ${result}
50 | return 0
51 | }
52 |
53 | function Rectangle() {
54 | [ $# -ne 2 ] && return $EC
55 | local -r a="${1}"
56 | local -r b="${2}"
57 | shift 2
58 |
59 | make_ $FUNCNAME \
60 | "a" "${a}" \
61 | "b" "${b}"
62 | }
63 |
64 | function Rectangle_area() {
65 | local -r obj="${1}"
66 | shift 1
67 |
68 | local result
69 | result=$(( $($obj "a") * $($obj "b") ))
70 | echo ${result}
71 | return 0
72 | }
73 |
74 | function main() {
75 | lst=$(List)
76 |
77 | c=$(Circle 4)
78 | $lst add "$c"
79 |
80 | s=$(Square 4)
81 | $lst add "$s"
82 |
83 | r=$(Rectangle 2 2)
84 | $lst add "$r"
85 |
86 | assert_eq $($lst len) 3
87 |
88 | local total=0
89 | local i
90 | for (( i=0; i<$($lst len); i++ )); do
91 | local el=$($lst get ${i})
92 | total=$(echo "$($el area) + ${total}" | bc -l)
93 | done
94 | printf "Total area: %g\n" ${total}
95 | assert_has_prefix "${total}" "70"
96 | }
97 |
98 | main
99 |
--------------------------------------------------------------------------------
/src/sync/wait_group.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # WaitGroup supports waiting selectively on a group of processes.
6 |
7 | if [ -n "${WAIT_GROUP_MOD:-}" ]; then return 0; fi
8 | readonly WAIT_GROUP_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${WAIT_GROUP_MOD}/../lang/p.sh
11 | . ${WAIT_GROUP_MOD}/../util/list.sh
12 |
13 | assert_function_exists List
14 |
15 |
16 | # ----------
17 | # Functions.
18 |
19 | function WaitGroup() {
20 | # WaitGroup.
21 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
22 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
23 | shift 0 || { ctx_wn $ctx; return $EC; }
24 |
25 | make_ $ctx \
26 | "${FUNCNAME}" \
27 | "lst" "$(List $ctx)"
28 | }
29 |
30 | function WaitGroup_add() {
31 | # Add a process id to the wait group.
32 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
33 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
34 | local -r wg="${1}"
35 | local -r pid="${2}"
36 | shift 2 || { ctx_wn $ctx; return $EC; }
37 |
38 | local -r lst=$($wg $ctx lst)
39 | $lst $ctx add "${pid}"
40 | }
41 |
42 | function WaitGroup_wait() {
43 | # Wait until processes finish.
44 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
45 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
46 | local -r wg="${1}"
47 | shift 1 || { ctx_wn $ctx; return $EC; }
48 |
49 | local -r lst=$($wg $ctx lst)
50 | [ $($lst $ctx len) -eq 0 ] && return 0
51 |
52 | local ec=0
53 | local i
54 | for (( i=0; i<$($lst len); i++ )); do
55 | local pid=$($lst $ctx get ${i})
56 | wait "${pid}" || ec=$?
57 | done
58 |
59 | return ${ec}
60 | }
61 |
62 | function WaitGroup_len() {
63 | # Number of process in the group.
64 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
65 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
66 | local -r wg="${1}"
67 | shift 1 || { ctx_wn $ctx; return $EC; }
68 |
69 | echo $($($wg $ctx lst) $ctx len)
70 | }
71 |
72 | function WaitGroup_to_string() {
73 | # String representation of this group.
74 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
75 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
76 | local -r wg="${1}"
77 | shift 1 || { ctx_wn $ctx; return $EC; }
78 |
79 | $($wg $ctx lst) $ctx to_string
80 | }
81 |
--------------------------------------------------------------------------------
/src/net/request.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Request related structs and functions.
6 |
7 | if [ -n "${REQUEST_MOD:-}" ]; then return 0; fi
8 | readonly REQUEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${REQUEST_MOD}/../lang/p.sh
11 |
12 |
13 | # ----------
14 | # Functions.
15 |
16 | function Request() {
17 | # Http request.
18 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
19 | [ $# -ne 4 ] && { ctx_wn $ctx; return $EC; }
20 | local -r method="${1}"
21 | local -r path="${2}"
22 | local -r proto="${3}"
23 | local -r rawf="${4}"
24 | shift 4 || { ctx_wn $ctx; return $EC; }
25 |
26 | make_ $ctx \
27 | "${FUNCNAME}" \
28 | "method" "${method}" \
29 | "proto" "${proto}" \
30 | "path" "${path}" \
31 | "rawf" "${rawf}"
32 | }
33 |
34 | function request_parse() {
35 | # Parse http request.
36 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
37 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
38 | shift 0 || { ctx_wn $ctx; return $EC; }
39 |
40 | local -r rawf=$(os_mktemp_file $ctx)
41 | _request_read $ctx "${rawf}" ||
42 | { ctx_w $ctx "fail read"; return $EC; }
43 |
44 | local -r method=$(head -n 1 "${rawf}" | $X_CUT -f1 -d' ')
45 | local -r path=$(head -n 1 "${rawf}" | $X_CUT -f2 -d' ')
46 | local -r proto=$(head -n 1 "${rawf}" | $X_CUT -f3 -d' ')
47 |
48 | case ${method} in
49 | GET) ;;
50 | PUT) ;;
51 | DELETE) ;;
52 | POST) ;;
53 | PATCH) ;;
54 | *) { ctx_w $ctx "incorrect method"; return $EC; } ;;
55 | esac
56 |
57 | # TODO(milos): more parsing.
58 |
59 | local req
60 | req=$(Request $ctx "${method}" "${path}" "${proto}" "${rawf}") || \
61 | { ctx_w $ctx "cannot make request"; return $EC; }
62 |
63 | echo "${req}"
64 | }
65 |
66 | function _request_read() {
67 | # Read request into a file.
68 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
69 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
70 | local -r rawf="${1}"
71 | shift 1 || { ctx_wn $ctx; return $EC; }
72 |
73 | ! is_file $ctx "${rawf}" && return $EC
74 |
75 | while :; do
76 | read l
77 | l=$(echo "${l}" | sed 's/
//')
78 | [ -z "${l}" ] && break
79 | echo "${l}" >> "${rawf}"
80 | done
81 | }
82 |
--------------------------------------------------------------------------------
/examples/flags_details_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Command line flag parsing and use of those flags.
4 |
5 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 | . ${DIR}/../gobash
7 |
8 |
9 | # Checking if argument values are as expected has to be done by
10 | # developers explicitly like in this function.
11 | function check_arguments() {
12 | local -r args="${1}"
13 | shift 1
14 |
15 | # Just for fun, we require that --ignore is set.
16 | if is_false "$($args ignore)"; then
17 | echo "'ignore' has to be set."
18 | return $EC
19 | fi
20 | }
21 |
22 | # The following function is some random computation to illustrate how
23 | # code for parsing can be connected to other/existing code.
24 | function compute_loc() {
25 | local -r url="${1}"
26 | shift 1
27 |
28 | # Clone the repository into a random dir and get number of
29 | # lines of code.
30 | local tmpd=$(os_mktemp_dir)
31 | echo ${tmpd}
32 | git clone ${url} ${tmpd}
33 | cloc ${tmpd} | grep 'SUM:' | $X_AWK '{ print $5 }'
34 | }
35 |
36 | function main() {
37 | # Create an instance of flags, then add desired flags.
38 | flags=$(Flags "Example of parsing arguments.")
39 | $flags add "$(Flag ignore $BOOL 'An argument that has to be set for fun.')"
40 | $flags add "$(Flag max $INT 'Max number of repos to use.')"
41 |
42 | # Create object that will contain values.
43 | local -r args=$(Args)
44 | # Create context.
45 | local -r ctx=$(ctx_make)
46 |
47 | # Parse then check for errors.
48 | $flags $ctx parse "$args" "$@" || \
49 | { ctx_show $ctx; $flags help; return $EC; }
50 |
51 | # Invoke a method to check if arguments are valid.
52 | check_arguments "$args" || return $EC
53 |
54 | # Code below has access to arguments via the `args` instance.
55 |
56 | local -r repos=$(Map)
57 | $repos put "math" "https://github.com/apache/commons-math"
58 | $repos put "io" "https://github.com/apache/commons-io"
59 |
60 | local -r keys=$($repos keys)
61 |
62 | local i
63 | for (( i=0; i<$($keys len); i++ )); do
64 | # We ensure not to exceed the max number of URLs to
65 | # process, which can be provided as a command line
66 | # argument (--max).
67 | [ ${i} = $($args max) ] && break
68 | local key=$($keys get $i)
69 | compute_loc $($repos get "$key")
70 | done
71 | }
72 |
73 | main "$@"
74 |
--------------------------------------------------------------------------------
/src/sync/atomic_int_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the atomic_int module.
6 |
7 | if [ -n "${ATOMIC_INT_TEST_MOD:-}" ]; then return 0; fi
8 | readonly ATOMIC_INT_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${ATOMIC_INT_TEST_MOD}/atomic_int.sh
11 | . ${ATOMIC_INT_TEST_MOD}/../testing/bunit.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function test_atomic_int_inc() {
18 | local ai
19 | ai=$(AtomicInt 0) || \
20 | assert_fail
21 |
22 | ( $ai inc > /dev/null ) &
23 | ( $ai inc > /dev/null ) &
24 | ( $ai inc > /dev/null ) &
25 | wait || \
26 | assert_fail
27 | assert_eq 3 "$($ai val)"
28 | }
29 | readonly -f test_atomic_int_inc
30 |
31 | function test_atomic_int_add() {
32 | local -r ai=$(AtomicInt 5)
33 | local res
34 | res=$($ai add 10) || \
35 | assert_fail
36 | assert_eq 15 "${res}"
37 |
38 | ( $ai add 3 > /dev/null ) &
39 | ( $ai add 2 > /dev/null ) &
40 | ( $ai add 1 > /dev/null ) &
41 | wait || \
42 | assert_fail
43 | assert_eq 21 "$($ai val)"
44 | }
45 | readonly -f test_atomic_int_add
46 |
47 | function test_atomic_int_compare_and_swap() {
48 | local -r ai=$(AtomicInt 10)
49 |
50 | $ai compare_and_swap 9 20 && \
51 | assert_fail
52 | assert_eq 10 "$($ai val)"
53 |
54 | $ai compare_and_swap 10 20 || \
55 | assert_fail
56 | assert_eq 20 "$($ai val)"
57 | }
58 | readonly -f test_atomic_int_compare_and_swap
59 |
60 | function test_atomic_int_load() {
61 | local ai
62 | ai=$(AtomicInt 10) || \
63 | assert_fail
64 |
65 | local res
66 | res=$($ai load) || \
67 | assert_fail
68 | assert_eq 10 "$($ai val)"
69 | }
70 | readonly -f test_atomic_int_load
71 |
72 | function test_atomic_int_store() {
73 | local ai
74 | ai=$(AtomicInt 100) || \
75 | assert_fail
76 |
77 | $ai store 99 || \
78 | assert_fail
79 | assert_eq 99 "$($ai val)"
80 | }
81 | readonly -f test_atomic_int_store
82 |
83 | function test_atomic_int_swap() {
84 | local ai
85 | ai=$(AtomicInt 10) || \
86 | assert_fail
87 |
88 | local res
89 | res=$($ai swap 8) || \
90 | assert_fail
91 | assert_eq 10 "${res}"
92 | assert_eq 8 "$($ai val)"
93 | }
94 | readonly -f test_atomic_int_swap
95 |
--------------------------------------------------------------------------------
/src/util/set.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Set collection.
6 |
7 | # @deprecated(Remove and use Map)
8 |
9 | if [ -n "${SET_MOD:-}" ]; then return 0; fi
10 | readonly SET_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
11 |
12 | . ${SET_MOD}/../lang/p.sh
13 | . ${SET_MOD}/list.sh
14 |
15 |
16 | # ----------
17 | # Functions.
18 |
19 | function Set() {
20 | # Set collection.
21 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
22 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
23 | shift 0 || { ctx_wn $ctx; return $EC; }
24 |
25 | local list
26 | list=$(List $ctx) || \
27 | { ctx_w $ctx "cannot construct ${FUNCNAME}"; return $EC; }
28 |
29 | make_ $ctx \
30 | "${FUNCNAME}" \
31 | "list" "$list"
32 | }
33 |
34 | function Set_len() {
35 | # Size of the set.
36 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
37 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
38 | local -r set="${1}"
39 | shift 1 || { ctx_wn $ctx; return $EC; }
40 |
41 | local list
42 | list=$($set $ctx list)
43 |
44 | $list $ctx len
45 | }
46 |
47 | function Set_add() {
48 | # Add an element to the set.
49 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
50 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
51 | local -r set="${1}"
52 | local -r val="${2}"
53 | shift 2 || { ctx_wn $ctx; return $EC; }
54 |
55 | local list
56 | list=$($set $ctx list)
57 |
58 | if $list $ctx contains "${val}"; then
59 | return $FALSE
60 | fi
61 |
62 | $list $ctx add "${val}"
63 | }
64 |
65 | function Set_contains() {
66 | # Return true if the given value is in the set.
67 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
68 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
69 | local -r obj="${1}"
70 | local -r val="${2}"
71 | shift 2 || { ctx_wn $ctx; return $EC; }
72 |
73 | local list
74 | list=$($obj $ctx list)
75 |
76 | $list $ctx contains "${val}"
77 | }
78 |
79 | function Set_clear() {
80 | # Remove all elements from the set. Return (, 0) if
81 | # successful; (, $EC) otherwise.
82 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
83 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
84 | local -r obj="${1}"
85 | shift 1 || { ctx_wn $ctx; return $EC; }
86 |
87 | local list
88 | list=$($obj $ctx list)
89 |
90 | $list $ctx clear
91 | }
92 |
--------------------------------------------------------------------------------
/examples/tour/tour_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Tests for running available tour.
6 |
7 | if [ -n "${TOUR_TEST:-}" ]; then return 0; fi
8 | readonly TOUR_TEST=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${TOUR_TEST}/../../gobash
11 |
12 |
13 | # ----------
14 | # Functions.
15 |
16 | function _exet() {
17 | local funcn="${1}"
18 | shift 1 || return $EC
19 |
20 | "${TOUR_TEST}/${funcn#test_tour_*}" "$@"
21 | }
22 |
23 | function test_tour_case_details_ex() {
24 | _exet "$FUNCNAME"
25 | }
26 | readonly -f test_tour_case_details_ex
27 |
28 | function test_tour_case_ex() {
29 | _exet "$FUNCNAME"
30 | }
31 | readonly -f test_tour_case_ex
32 |
33 | function test_tour_fact_ex() {
34 | _exet "$FUNCNAME"
35 | }
36 | readonly -f test_tour_fact_ex
37 |
38 | function test_tour_for_ex() {
39 | _exet "$FUNCNAME"
40 | }
41 | readonly -f test_tour_for_ex
42 |
43 | function test_tour_func_ex() {
44 | _exet "$FUNCNAME"
45 | }
46 | readonly -f test_tour_func_ex
47 |
48 | function test_tour_if_else_ex() {
49 | _exet "$FUNCNAME"
50 | }
51 | readonly -f test_tour_if_else_ex
52 |
53 | function test_tour_if_ex() {
54 | _exet "$FUNCNAME"
55 | }
56 | readonly -f test_tour_if_ex
57 |
58 | function test_tour_infinite_ex() {
59 | _exet "$FUNCNAME"
60 | }
61 | readonly -f test_tour_infinite_ex
62 |
63 | function test_tour_loops_funcs_ex() {
64 | _exet "$FUNCNAME"
65 | }
66 | readonly -f test_tour_loops_funcs_ex
67 |
68 | function test_tour_rand_ex() {
69 | _exet "$FUNCNAME"
70 | }
71 | readonly -f test_tour_rand_ex
72 |
73 | function test_tour_select_details_ex() {
74 | _exet "$FUNCNAME"
75 | }
76 | readonly -f test_tour_select_details_ex
77 |
78 | function test_tour_select_ex() {
79 | _exet "$FUNCNAME"
80 | }
81 | readonly -f test_tour_select_ex
82 |
83 | function test_tour_test_cmd_ex() {
84 | _exet "$FUNCNAME"
85 | }
86 | readonly -f test_tour_test_cmd_ex
87 |
88 | function test_tour_until_ex() {
89 | _exet "$FUNCNAME"
90 | }
91 | readonly -f test_tour_until_ex
92 |
93 | function test_tour_variables_details_ex() {
94 | _exet "$FUNCNAME"
95 | }
96 | readonly -f test_tour_variables_details_ex
97 |
98 | function test_tour_variables_ex() {
99 | _exet "$FUNCNAME"
100 | }
101 | readonly -f test_tour_variables_ex
102 |
103 | function test_tour_welcome_ex() {
104 | _exet "$FUNCNAME"
105 | }
106 | readonly -f test_tour_welcome_ex
107 |
108 | function test_tour_while_ex() {
109 | _exet "$FUNCNAME"
110 | }
111 | readonly -f test_tour_while_ex
112 |
--------------------------------------------------------------------------------
/src/util/filepath_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the filepath module.
6 |
7 | if [ -n "${FILEPATH_TEST_MOD:-}" ]; then return 0; fi
8 | readonly FILEPATH_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${FILEPATH_TEST_MOD}/filepath.sh
11 | . ${FILEPATH_TEST_MOD}/os.sh
12 | . ${FILEPATH_TEST_MOD}/../testing/bunit.sh
13 |
14 |
15 | # ----------
16 | # Functions.
17 |
18 | function test_filepath_base() {
19 | assert_eq "baz.js" $(filepath_base "/foo/bar/baz.js")
20 | assert_eq "baz" $(filepath_base "/foo/bar/baz")
21 | assert_eq "baz" $(filepath_base "/foo/bar/baz/")
22 | assert_eq "dev.txt" $(filepath_base "dev.txt")
23 | assert_eq "todo.txt" $(filepath_base "../todo.txt")
24 | assert_eq ".." $(filepath_base "..")
25 | assert_eq "." $(filepath_base ".")
26 | assert_eq "/" $(filepath_base "/")
27 | assert_eq "." $(filepath_base "")
28 | assert_eq "/" $(filepath_base "/////")
29 | }
30 | readonly -f test_filepath_base
31 |
32 | function test_filepath_ext() {
33 | assert_eq "" "$(filepath_ext 'index')"
34 | assert_eq "" "$(filepath_ext '')"
35 | assert_eq ".js" $(filepath_ext "index.js")
36 | assert_eq ".js" $(filepath_ext "main.test.js")
37 | assert_eq ".js" $(filepath_ext "something/main.test.js")
38 | assert_eq ".js" $(filepath_ext "/something/main.test.js")
39 | }
40 | readonly -f test_filepath_ext
41 |
42 | function test_filepath_dir() {
43 | assert_eq "/foo/bar" $(filepath_dir "/foo/bar/baz.js")
44 | assert_eq "/foo/bar" $(filepath_dir "/foo/bar/baz")
45 | assert_eq "/foo/bar" $(filepath_dir "/foo/bar/baz/")
46 | assert_eq "/dirty" $(filepath_dir "/dirty//path///")
47 | assert_eq "." $(filepath_dir "dev.txt")
48 | assert_eq ".." $(filepath_dir "../todo.txt")
49 | assert_eq "." $(filepath_dir "..")
50 | assert_eq "." $(filepath_dir ".")
51 | assert_eq "/" $(filepath_dir "/")
52 | assert_eq "/" $(filepath_dir "////")
53 | assert_eq "." "$(filepath_dir '')"
54 | }
55 | readonly -f test_filepath_dir
56 |
57 | function test_filepath_is_abs() {
58 | local ec
59 |
60 | filepath_is_abs "${HOME}" || assert_fail
61 |
62 | ec=0
63 | filepath_is_abs ".bashrc" || ec=$?
64 | assert_false ${ec}
65 |
66 | ec=0
67 | filepath_is_abs ".." || ec=$?
68 | assert_false ${ec}
69 |
70 | ec=0
71 | filepath_is_abs "." || ec=$?
72 | assert_false ${ec}
73 |
74 | filepath_is_abs "/" || assert_fail
75 |
76 | ec=0
77 | filepath_is_abs "" || ec=$?
78 | assert_false ${ec}
79 | }
80 | readonly -f test_filepath_is_abs
81 |
--------------------------------------------------------------------------------
/src/lang/jqmem.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Util functions to manipulate json files.
6 |
7 | if [ -n "${JQMEM_MOD:-}" ]; then return 0; fi
8 | readonly JQMEM_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${JQMEM_MOD}/core.sh
11 | . ${JQMEM_MOD}/os.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function json_set() {
18 | # Set a field.
19 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
20 | [ $# -ne 3 ] && { ctx_wn $ctx; return $EC; }
21 | local -r f="${1}"
22 | local -r fld="${2}"
23 | local val="${3}"
24 | shift 3 || { ctx_wn $ctx; return $EC; }
25 |
26 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
27 | [ -z "${f}" ] && { ctx_w $ctx "no f"; return $EC; }
28 | [ -z "${fld}" ] && { ctx_w $ctx "no fld"; return $EC; }
29 |
30 | local -r tmpf=$(os_mktemp_file $ctx)
31 |
32 | if [ "${val}" != "null" -a \
33 | "${val}" != "true" -a \
34 | "${val}" != "false" -a \
35 | "${val}" != "[]" -a \
36 | "${val}" != "{}" ]; then
37 | # TODO(milos): numbers diff.
38 | # val=$(echo "${val}" | jq -R)
39 | val="\"${val//\"/\\\"}\""
40 | fi
41 |
42 | jq --indent 4 --argjson "${fld}" "${val}" \
43 | '. += $ARGS.named' "${f}" > "${tmpf}" 2>/dev/null || \
44 | { ctx_w $ctx "cannot set ${fld} using jq"; return $EC; }
45 |
46 | mv "${tmpf}" "${f}"
47 | }
48 |
49 | function json_get() {
50 | # Get a field.
51 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
52 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
53 | local -r f="${1}"
54 | local -r fld="${2}"
55 | shift 2 || { ctx_wn $ctx; return $EC; }
56 |
57 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
58 | [ -z "${f}" ] && { ctx_w $ctx "no f"; return $EC; }
59 | [ -z "${fld}" ] && { ctx_w $ctx "no fld"; return $EC; }
60 |
61 | jq -r '. | .'"${fld}"'' "${f}"
62 | }
63 |
64 | function json_has() {
65 | # Return true if it has a field.
66 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
67 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
68 | local -r f="${1}"
69 | local -r fld="${2}"
70 | shift 2 || { ctx_wn $ctx; return $EC; }
71 |
72 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
73 | [ -z "${f}" ] && { ctx_w $ctx "no f"; return $EC; }
74 | [ -z "${fld}" ] && { ctx_w $ctx "no fld"; return $EC; }
75 |
76 | grep --quiet '^ "'"${fld}"'":' "${f}" > /dev/null 2>&1
77 |
78 | #local res
79 | #res=$(jq 'has("'"${fld}"'")' "${f}")
80 | #[ "${res}" = "true" ]
81 | }
82 |
--------------------------------------------------------------------------------
/examples/binary_trees_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Example inspired by Equivalent Binary Trees from the Go tutorial
4 | # (https://go.dev/tour/concurrency/8).
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../gobash
8 |
9 |
10 | function Tree() {
11 | local -r val="${1}"
12 | local -r left="${2:-$NULL}"
13 | local -r right="${3:-$NULL}"
14 | shift 1
15 |
16 | [ -z "${val}" ] && return $EC
17 |
18 | make_ $FUNCNAME \
19 | "left" "$left" \
20 | "val" "$val" \
21 | "right" "$right"
22 | }
23 |
24 | function Tree_add() {
25 | local -r t="${1}"
26 | local -r n="${2}"
27 | shift 2
28 |
29 | local prev="$NULL"
30 | local curr="$t"
31 |
32 | while ! is_null "$curr"; do
33 | prev="$curr"
34 | if [ $($curr val) -lt $($n val) ]; then
35 | curr=$($curr right)
36 | else
37 | curr=$($curr left)
38 | fi
39 | done
40 |
41 | if [ $($prev val) -lt $($n val) ]; then
42 | $prev right "$n"
43 | else
44 | $prev left "$n"
45 | fi
46 | }
47 |
48 | function _tree_walk() {
49 | local -r t="${1}"
50 | local -r ch="${2}"
51 | shift 2
52 |
53 | $ch send "$($t val)"
54 |
55 | local l=$($t left)
56 | if ! is_null "$l"; then
57 | _tree_walk "$l" "$ch"
58 | fi
59 |
60 | local r=$($t right)
61 | if ! is_null "$r"; then
62 | _tree_walk "$r" "$ch"
63 | fi
64 | }
65 |
66 | function Tree_walk() {
67 | local -r t="${1}"
68 | local -r ch="${2}"
69 | shift 2
70 |
71 | _tree_walk "$t" "$ch"
72 | $ch close
73 | }
74 |
75 | function tree_new() {
76 | local -i k="${1}"
77 | shift 1
78 |
79 | [ -z "${k}" ] && return $EC
80 |
81 | local t=$NULL
82 |
83 | local -i i
84 | # Do not use shuf (not in Mac).
85 | for i in $(seq 1 10 | awk 'BEGIN {srand();} {print rand(), $0;}' | sort -n | cut -d' ' -f2-); do
86 | local val=$(( ${i} * 1000 ))
87 | local n=$(Tree "${val}" )
88 | if is_null "$t"; then
89 | t="$n"
90 | else
91 | $t add "$n"
92 | fi
93 | done
94 |
95 | echo "$t"
96 | }
97 |
98 | echo "Construct a tree."
99 | t=$(tree_new 1)
100 |
101 | echo "Create a channel and walk a tree."
102 | ch=$(Chan)
103 | ( $t walk "$ch" ) &
104 |
105 | echo "Start receiving data."
106 | while :; do
107 | if ! $ch recv; then break; fi
108 | done
109 | wait
110 |
--------------------------------------------------------------------------------
/src/lang/log.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Log functions.
6 |
7 | if [ -n "${LOG_MOD:-}" ]; then return 0; fi
8 | readonly LOG_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${LOG_MOD}/core.sh
11 | . ${LOG_MOD}/unsafe.sh
12 |
13 | # @mutable
14 | _LOG_FILE="$(core_obj_dir)/world.txt"
15 | readonly LOG_STDOUT="stdout"
16 |
17 |
18 | # ----------
19 | # Functions.
20 |
21 | function _log() {
22 | # Log.
23 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
24 | [ $# -ne 4 ] && { ctx_wn $ctx; return $EC; }
25 | local -r level="${1}"
26 | local -r func="${2}"
27 | local -r logf="${3}"
28 | local -r msg="${4}"
29 | shift 4 || { ctx_wn $ctx; return $EC; }
30 |
31 | local text
32 | if [ -z "${msg}" ]; then
33 | text="${level}::($($X_DATE)) ${func}"
34 | else
35 | text="${level}::($($X_DATE)) ${func} ${msg}"
36 | fi
37 |
38 | if [ ! -z "${logf}" ]; then
39 | echo "${text}" >> "${logf}"
40 | else
41 | echo "${text}"
42 | fi
43 |
44 | return 0
45 | }
46 |
47 | function log_output() {
48 | # Return current output.
49 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
50 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
51 | shift 0 || { ctx_wn $ctx; return $EC; }
52 |
53 | echo "${_LOG_FILE}"
54 | }
55 |
56 | function log_set_output() {
57 | # Set output (either $LOG_STDOUT or filename).
58 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
59 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
60 | local -r output="${1}"
61 | shift 1 || { ctx_wn $ctx; return $EC; }
62 |
63 | case "${output}" in
64 | ${LOG_STDOUT}) _LOG_FILE="";;
65 | *) _LOG_FILE="${output}";;
66 | esac
67 | }
68 |
69 | function log_e() {
70 | # Log error.
71 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
72 | [ $# -lt 0 ] && { ctx_wn $ctx; return $EC; }
73 | local -r msg="${1}"
74 | shift 0 || { ctx_wn $ctx; return $EC; }
75 |
76 | _log $ctx "ERROR" "$(caller 0 | $X_CUT -f2 -d' ')" "${_LOG_FILE}" "${msg}" 1>&2
77 | }
78 |
79 | function log_w() {
80 | # Log warning.
81 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
82 | [ $# -lt 0 ] && { ctx_wn $ctx; return $EC; }
83 | local -r msg="${1}"
84 | shift 0 || { ctx_wn $ctx; return $EC; }
85 |
86 | _log $ctx "WARN" "$(caller 0 | $X_CUT -f2 -d' ')" "${_LOG_FILE}" "${msg}" 1>&2
87 | }
88 |
89 | function log_i() {
90 | # Log info.
91 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
92 | [ $# -lt 0 ] && { ctx_wn $ctx; return $EC; }
93 | local -r msg="${1}"
94 | shift 0 || { ctx_wn $ctx; return $EC; }
95 |
96 | _log $ctx "INFO" "$(caller 0 | $X_CUT -f2 -d' ')" "${_LOG_FILE}" "${msg}"
97 | }
98 |
--------------------------------------------------------------------------------
/src/util/complex_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the complex module.
6 |
7 | if [ -n "${COMPLEX_TEST_MOD:-}" ]; then return 0; fi
8 | readonly COMPLEX_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${COMPLEX_TEST_MOD}/complex.sh
11 | . ${COMPLEX_TEST_MOD}/../testing/bunit.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function test_complex() {
18 | local -r c=$(Complex 10 11)
19 | assert_eq "$($c to_string)" "( 10 + 11i )"
20 | }
21 | readonly -f test_complex
22 |
23 | function test_complex_plus() {
24 | local c1
25 | c1=$(Complex 2 3) || \
26 | assert_fail
27 |
28 | local c2
29 | c2=$(Complex 4 5) || \
30 | assert_fail
31 |
32 | local res
33 | res=$($c1 plus "${c2}") || \
34 | assert_fail
35 |
36 | assert_eq 6 "$($res real)"
37 | assert_eq 8 "$($res imag)"
38 | }
39 | readonly -f test_complex_plus
40 |
41 | function test_complex_minus() {
42 | local -r c1=$(Complex 2 3)
43 | local -r c2=$(Complex 4 5)
44 |
45 | local res
46 | res=$($c1 minus "${c2}") || \
47 | assert_fail
48 |
49 | assert_eq -2 "$($res real)"
50 | assert_eq -2 "$($res imag)"
51 | }
52 | readonly -f test_complex_minus
53 |
54 | function test_complex_times() {
55 | local -r c1=$(Complex 2 3)
56 | local -r c2=$(Complex 4 5)
57 |
58 | local res
59 | res=$($c1 times "${c2}") || \
60 | assert_fail
61 |
62 | assert_eq -7 "$($res real)"
63 | assert_eq 23 "$($res imag)"
64 | }
65 | readonly -f test_complex_times
66 |
67 | function test_complex_scale() {
68 | local -r c=$(Complex 10 11)
69 |
70 | local res
71 | res=$($c scale 2.2) || \
72 | assert_fail
73 |
74 | assert_eq 22.0 "$($res real)"
75 | assert_eq 24.2 "$($res imag)"
76 | }
77 | readonly -f test_complex_scale
78 |
79 | function test_complex_conjugate() {
80 | local -r c=$(Complex 10 11)
81 |
82 | local res
83 | res=$($c conjugate) || \
84 | assert_fail
85 |
86 | assert_eq "$($res real)" "10"
87 | assert_eq "$($res imag)" "-11"
88 | }
89 | readonly -f test_complex_conjugate
90 |
91 | function test_complex_eq() {
92 | local -r c1=$(Complex 2 3)
93 | local -r c2=$(Complex 4 5)
94 | local -r c3=$(Complex 4 5)
95 |
96 | local ctx
97 |
98 | ctx=$(ctx_make)
99 | $c1 $ctx eq && \
100 | assert_fail
101 | ctx_show $ctx | grep 'incorrect number of arguments' || \
102 | assert_fail
103 |
104 | $c1 eq "" && \
105 | assert_fail
106 |
107 | $c1 eq "$c2" && \
108 | assert_fail
109 |
110 | $c2 eq "$c3" || \
111 | assert_fail
112 | }
113 | readonly -f test_complex_eq
114 |
--------------------------------------------------------------------------------
/src/util/rand.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Random value generators.
6 |
7 | if [ -n "${RAND_MOD:-}" ]; then return 0; fi
8 | readonly RAND_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${RAND_MOD}/../lang/p.sh
11 |
12 |
13 | # ----------
14 | # Functions.
15 |
16 | function rand_bool() {
17 | # Generate random bool.
18 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
19 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
20 | shift 0 || { ctx_wn $ctx; return $EC; }
21 |
22 | # No arguments to check.
23 |
24 | echo $(( ${RANDOM} % 2 ))
25 | }
26 |
27 | function rand_return() {
28 | # Return (`return`) randomly 0 or 1. This can be convenient to
29 | # use in if statements.
30 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
31 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
32 | shift 0 || { ctx_wn $ctx; return $EC; }
33 |
34 | # No arguments to check.
35 |
36 | [ $(rand_bool) = 1 ]
37 | }
38 |
39 | function rand_args() {
40 | # Return random argument given to this function.
41 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
42 | [ $# -eq 0 ] && { ctx_wn $ctx; return $EC; }
43 | shift 0 || { ctx_wn $ctx; return $EC; }
44 |
45 | # No arguments to check.
46 |
47 | local vals=( $@ )
48 | local len=${#vals[@]}
49 | local ix=$(( $RANDOM % len ))
50 | echo "${vals[${ix}]}"
51 | }
52 |
53 | function rand_int() {
54 | # Generate random int.
55 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
56 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
57 | shift 0 || { ctx_wn $ctx; return $EC; }
58 |
59 | # No arguments to check.
60 |
61 | echo "${RANDOM}"
62 | }
63 |
64 | function rand_intn() {
65 | # Generate random int up to (but excluding) the given value.
66 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
67 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
68 | # Max value to generate (excluding the value itself).
69 | local -r max="${1}"
70 | shift 1 || { ctx_wn $ctx; return $EC; }
71 |
72 | [ -z "${max}" ] && { ctx_wa $ctx "max"; return $EC; }
73 | [ ${max} -le 0 ] && { ctx_wa $ctx "max"; return $EC; }
74 |
75 | local -r delta=$(( ${max} + 1 ))
76 |
77 | local val=$(rand_int $ctx)
78 | val=$(( ${val} % ${max} ))
79 |
80 | echo "${val}"
81 | }
82 |
83 | function rand_string() {
84 | # Generate random string up to the given length.
85 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
86 | [ $# -lt 0 ] && { ctx_wn $ctx; return $EC; }
87 | # Length of the string to generate (max 32).
88 | local -r -i len="${1:-32}"
89 | shift 0 || { ctx_wn $ctx; return $EC; }
90 |
91 | [ -z "${len}" ] && { ctx_wa $ctx "len"; return $EC; }
92 |
93 | [ ${len} -le 1 -o ${len} -gt 32 ] && { ctx_wa $ctx "len"; return $EC; }
94 |
95 | echo "${RANDOM}" | $X_MD5 | head -c "${len}"; echo
96 | }
97 |
--------------------------------------------------------------------------------
/src/lang/sys_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the sys module.
6 |
7 | if [ -n "${SYS_TEST_MOD:-}" ]; then return 0; fi
8 | readonly SYS_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${SYS_TEST_MOD}/assert.sh
11 | . ${SYS_TEST_MOD}/sys.sh
12 | . ${SYS_TEST_MOD}/../testing/bunit.sh
13 |
14 |
15 | # ----------
16 | # Functions.
17 |
18 | function test_sys_stack_trace() {
19 | function main() {
20 | sys_stack_trace | grep 'main' > /dev/null
21 | }
22 | main || \
23 | assert_fail
24 | }
25 | readonly -f test_sys_stack_trace
26 |
27 | function test_sys_is_on_stack() {
28 | sys_is_on_stack "${FUNCNAME}" > /dev/null || \
29 | assert_fail
30 |
31 | sys_is_on_stack "blah" > /dev/null && \
32 | assert_fail
33 |
34 | return 0
35 | }
36 | readonly -f test_sys_is_on_stack
37 |
38 | function test_sys_line_num() {
39 | assert_eq 39 $(sys_line_num)
40 | }
41 | readonly -f test_sys_line_num
42 |
43 | function test_sys_line_prev() {
44 | # this is prev
45 | assert_eq " # this is prev" "$(sys_line_prev)"
46 | }
47 | readonly -f test_sys_line_prev
48 |
49 | function test_sys_line_next() {
50 | # this is prev
51 | assert_eq " # this is next" "$(sys_line_next)"
52 | # this is next
53 | }
54 | readonly -f test_sys_line_next
55 |
56 | function test_sys_bash_files() {
57 | sys_bash_files | grep 'sys_test.sh'
58 | }
59 | readonly -f test_sys_bash_files
60 |
61 | function test_sys_functions() {
62 | sys_functions | grep 'sys_functions' || \
63 | assert_fail
64 | }
65 | readonly -f test_sys_functions
66 |
67 | function test_sys_functions_in_file() {
68 | sys_functions_in_file "${BASH_SOURCE[0]}" | \
69 | grep '^test_sys_functions$' > /dev/null || \
70 | assert_fail
71 | }
72 | readonly -f test_sys_functions_in_file
73 |
74 | function test_sys_function_doc_lines() {
75 | local -r script="$(sys_repo_path)/src/lang/sys.sh"
76 | local val
77 |
78 | val=$(sys_function_doc_lines "${script}" "sys_version") || \
79 | assert_fail
80 | assert_eq 4 "${val}"
81 |
82 | val=$(sys_function_doc_lines "${script}" "sys_line_prev") || \
83 | assert_fail
84 | assert_eq 5 "${val}"
85 | }
86 | readonly -f test_sys_function_doc_lines
87 |
88 | function test_sys_function_doc() {
89 | local -r script="$(sys_repo_path)/src/lang/sys.sh"
90 | local actual
91 |
92 | actual=$(sys_function_doc "${script}" "sys_version") || \
93 | assert_fail
94 |
95 | local expected="Return gobash version.
96 |
97 | :return: gobash version.
98 | :rtype: string"
99 | assert_eq "${expected}" "${actual}"
100 |
101 | actual=$(sys_function_doc "${script}" "sys_line_prev") || \
102 | assert_fail
103 | }
104 | readonly -f test_sys_function_doc
105 |
--------------------------------------------------------------------------------
/src/util/fileinfo.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # FileInfo.
6 |
7 | if [ -n "${FILEINFO_MOD:-}" ]; then return 0; fi
8 | readonly FILEINFO_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${FILEINFO_MOD}/../lang/p.sh
11 |
12 |
13 | # ----------
14 | # Functions.
15 |
16 | function FileInfo() {
17 | # FileInfo struct.
18 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
19 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
20 | local -r path="${1}"
21 | shift 1 || { ctx_wn $ctx; return $EC; }
22 |
23 | [ -f "${path}" ] || \
24 | { ctx_w $ctx "incorrect path"; return $EC; }
25 |
26 | # TODO: capture all info here at the time of construction?
27 | make_ $ctx \
28 | "${FUNCNAME}" \
29 | "path" "${path}"
30 | }
31 |
32 | function FileInfo_name() {
33 | # Basename of the file.
34 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
35 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
36 | local -r fi="${1}"
37 | shift 1 || { ctx_wn $ctx; return $EC; }
38 |
39 | basename "$($fi path)"
40 | }
41 |
42 | function FileInfo_size() {
43 | # Length in bytes (unknown for non-regular files).
44 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
45 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
46 | local -r fi="${1}"
47 | shift 1 || { ctx_wn $ctx; return $EC; }
48 |
49 | local -r os=$(os_name)
50 | if [ "${os}" = "${OS_MAC}" ]; then
51 | stat -f "%z" "$($fi path)" || return $EC
52 | else
53 | stat --printf="%s\n" "$($fi path)" || return $EC
54 | fi
55 | }
56 |
57 | function FileInfo_mode() {
58 | # Mode bits for the file.
59 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
60 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
61 | local -r fi="${1}"
62 | shift 1 || { ctx_wn $ctx; return $EC; }
63 |
64 | ls -la "$($fi path)" | $X_CUT -f1 -d' '
65 | }
66 |
67 | function FileInfo_mod_time() {
68 | # Modification time.
69 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
70 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
71 | local -r fi="${1}"
72 | shift 1 || { ctx_wn $ctx; return $EC; }
73 |
74 | $X_DATE -r "$($fi path)" "+%Y-%m-%d %H:%M:%S.%N %z %Z"
75 | }
76 |
77 | function FileInfo_is_dir() {
78 | # Return true if directory.
79 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
80 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
81 | local -r fi="${1}"
82 | shift 1 || { ctx_wn $ctx; return $EC; }
83 |
84 | [ -d "$($fi path)" ]
85 | }
86 |
87 | function FileInfo_to_string() {
88 | # String format.
89 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
90 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
91 | local -r fi="${1}"
92 | shift 1 || { ctx_wn $ctx; return $EC; }
93 |
94 | $fi $ctx name
95 | $fi $ctx size
96 | $fi $ctx mode
97 | $fi $ctx mod_time
98 | }
99 |
--------------------------------------------------------------------------------
/examples/visitor_ex:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # A simple example to demo implementation of an expression calc. (Not
4 | # entirely an indented use case of the library.)
5 |
6 | readonly DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
7 | . ${DIR}/../gobash
8 |
9 |
10 | function Val() {
11 | local -r val="${1}"
12 |
13 | make_ "$FUNCNAME" \
14 | "val" "${val}"
15 | }
16 |
17 | function Val_accept() {
18 | local val="${1}"
19 | $val val
20 | }
21 |
22 | function AddExpr() {
23 | local -r l="${1}"
24 | local -r r="${2}"
25 |
26 | make_ "$FUNCNAME" \
27 | "l" "${l}" \
28 | "r" "${r}"
29 | }
30 |
31 | function AddExpr_accept() {
32 | local -r e="${1}"
33 | local -r v="${2}"
34 |
35 | $v visit_add "$e"
36 | }
37 |
38 | function MulExpr() {
39 | local -r l="${1}"
40 | local -r r="${2}"
41 |
42 | make_ "$FUNCNAME" \
43 | "l" "${l}" \
44 | "r" "${r}"
45 | }
46 |
47 | function MulExpr_accept() {
48 | local -r e="${1}"
49 | local -r v="${2}"
50 |
51 | $v visit_mul "$e"
52 | }
53 |
54 | function SubExpr() {
55 | local -r l="${1}"
56 | local -r r="${2}"
57 |
58 | make_ "$FUNCNAME" \
59 | "l" "${l}" \
60 | "r" "${r}"
61 | }
62 |
63 | function SubExp_accept() {
64 | local -r e="${1}"
65 | local -r v="${2}"
66 |
67 | $v visit_sub "$e"
68 | }
69 |
70 | function DivExpr() {
71 | local -r l="${1}"
72 | local -r r="${2}"
73 |
74 | make_ "$FUNCNAME" \
75 | "l" "${l}" \
76 | "r" "${r}"
77 | }
78 |
79 | function DivExpr_accept() {
80 | local -r e="${1}"
81 | local -r v="${2}"
82 |
83 | $v visit_div "$e"
84 | }
85 |
86 | function Visitor() {
87 | make_ "$FUNCNAME"
88 | }
89 |
90 | function Visitor_visit_add() {
91 | local -r v="${1}"
92 | local -r e="${2}"
93 |
94 | local lv=$($($e l) accept "$v")
95 | local rv=$($($e r) accept "$v")
96 | math_calc "${lv} + ${rv}"
97 | }
98 |
99 | function Visitor_visit_mul() {
100 | local -r v="${1}"
101 | local -r e="${2}"
102 |
103 | local lv=$($($e l) accept "$v")
104 | local rv=$($($e r) accept "$v")
105 | math_calc "${lv} * ${rv}"
106 | }
107 |
108 | function Visitor_visit_sub() {
109 | local -r v="${1}"
110 | local -r e="${2}"
111 |
112 | local lv=$($($e l) accept "$v")
113 | local rv=$($($e r) accept "$v")
114 | math_calc "${lv} - ${rv}"
115 | }
116 |
117 | function Visitor_visit_div() {
118 | local -r v="${1}"
119 | local -r e="${2}"
120 |
121 | local lv=$($($e l) accept "$v")
122 | local rv=$($($e r) accept "$v")
123 | math_calc "${lv} / ${rv}"
124 | }
125 |
126 | # (5 + 3) * (10 / 2)
127 | l=$(AddExpr "$(Val 5)" "$(Val 3)")
128 | r=$(DivExpr "$(Val 10)" "$(Val 2)")
129 | root=$(MulExpr "$l" "$r")
130 |
131 | v=$(Visitor)
132 | $root accept "$v"
133 |
--------------------------------------------------------------------------------
/src/lang/os.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # OS functions.
6 |
7 | if [ -n "${LANG_OS_MOD:-}" ]; then return 0; fi
8 | readonly LANG_OS_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${LANG_OS_MOD}/core.sh
11 |
12 | readonly OS_LINUX="linux"
13 | readonly OS_MAC="darwin"
14 | readonly OS_WINDOWS="windows"
15 | readonly OS_UNKNOWN="unknown"
16 |
17 |
18 | # ----------
19 | # Functions.
20 |
21 | function os_name() {
22 | # Return OS name. If OS is not known, return "$OS_UNKNOWN".
23 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
24 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
25 | shift 0 || { ctx_wn $ctx; return $EC; }
26 |
27 | case "${OSTYPE}" in
28 | linux*) echo "${OS_LINUX}";;
29 | darwin*) echo "${OS_MAC}";;
30 | win*) echo "${OS_WINDOWS}";;
31 | *) echo "${OS_UNKNOWN}";;
32 | esac
33 | }
34 |
35 | function os_arch() {
36 | # Return OS architecture.
37 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
38 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
39 | shift 0 || { ctx_wn $ctx; return $EC; }
40 |
41 | arch
42 | }
43 |
44 | function os_kill() {
45 | # Kills the given process.
46 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
47 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
48 | local -r pid="${1}"
49 | shift 1 || { ctx_wn $ctx; return $EC; }
50 |
51 | kill -9 "${pid}"
52 | }
53 |
54 | function os_mktemp() {
55 | # Make a temporary file.
56 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
57 | [ $# -lt 0 ] && { ctx_wn $ctx; return $EC; }
58 | shift 0 || { ctx_wn $ctx; return $EC; }
59 |
60 | core_mktemp_file "$@"
61 | }
62 |
63 | function os_mktemp_file() {
64 | # Make a temporary file.
65 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
66 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
67 | shift 0 || { ctx_wn $ctx; return $EC; }
68 |
69 | # No arguments to check.
70 |
71 | mktemp
72 | }
73 |
74 | function os_mktemp_dir() {
75 | # Make a temporary directory.
76 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
77 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
78 | shift 0 || { ctx_wn $ctx; return $EC; }
79 |
80 | # No arguments to check.
81 |
82 | mktemp -d
83 | }
84 |
85 | function os_remake_dir() {
86 | # Remake (delete and create) a directory.
87 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
88 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
89 | local -r d="${1}"
90 | shift 1 || { ctx_wn $ctx; return $EC; }
91 |
92 | rm -rf "${d}"
93 | mkdir -p "${d}"
94 |
95 | echo "${d}"
96 | }
97 |
98 | function os_get_pid() {
99 | # Return this process id.
100 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
101 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
102 | shift 0 || { ctx_wn $ctx; return $EC; }
103 |
104 | "${X_BASH}" -c 'echo ${PPID}'
105 | }
106 |
--------------------------------------------------------------------------------
/src/sync/chan.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Communication channel related structs and functions.
6 |
7 | if [ -n "${CHAN_MOD:-}" ]; then return 0; fi
8 | readonly CHAN_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${CHAN_MOD}/../lang/p.sh
11 | . ${CHAN_MOD}/../util/list.sh
12 | . ${CHAN_MOD}/mutex.sh
13 | . ${CHAN_MOD}/atomic_int.sh
14 |
15 | CHAN_SLEEP_TIME=0.1
16 |
17 |
18 | # ----------
19 | # Functions.
20 |
21 | function Chan() {
22 | # Channel.
23 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
24 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
25 | shift 0 || { ctx_wn $ctx; return $EC; }
26 |
27 | local d
28 | d=$(os_mktemp_dir $ctx) || { ctx_w $ctx "cannot make dir"; return $EC; }
29 |
30 | make_ $ctx \
31 | "${FUNCNAME}" \
32 | "d" "${d}" \
33 | "ais" "$(AtomicInt $ctx)" \
34 | "air" "$(AtomicInt $ctx)"
35 | }
36 |
37 | function Chan_send() {
38 | # Send a value.
39 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
40 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
41 | local -r ch="${1}"
42 | local -r val="${2}"
43 | shift 2 || { ctx_wn $ctx; return $EC; }
44 |
45 | local ix
46 | ix=$($($ch $ctx ais) $ctx inc) || \
47 | { ctx_w $ctx "cannot inc"; return $EC; }
48 |
49 | local -r d=$($ch $ctx d)
50 | local -r valf="${d}/${ix}.val"
51 | echo "$val" > "${valf}"
52 | local -r sendf="${d}/${ix}.ch"
53 | # Has to be created after we wrote the value.
54 | touch "${sendf}"
55 |
56 | while :; do
57 | [ ! -f "${sendf}" ] && break
58 | sleep "${CHAN_SLEEP_TIME}"
59 | done
60 | }
61 |
62 | function Chan_recv() {
63 | # Receive a value.
64 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
65 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
66 | local -r ch="${1}"
67 | shift 1 || { ctx_wn $ctx; return $EC; }
68 |
69 | local ix
70 | ix=$($($ch $ctx air) $ctx inc) || \
71 | { ctx_w $ctx "cannot inc"; return $EC; }
72 |
73 | local -r d=$($ch $ctx d)
74 | local -r valf="${d}/${ix}.val"
75 | local -r recvf="${d}/${ix}.ch"
76 | local -r closef="${d}/close"
77 |
78 | while :; do
79 | if [ -f "${recvf}" ]; then
80 | local val=$(cat "${valf}")
81 | rm -f "${valf}"
82 | rm -f "${recvf}"
83 | echo "${val}"
84 | break
85 | fi
86 |
87 | # No values, so check if closed.
88 | if [ -f "${closef}" ]; then
89 | return $FALSE
90 | fi
91 |
92 | sleep "${CHAN_SLEEP_TIME}"
93 | done
94 |
95 | return 0
96 | }
97 |
98 | function Chan_close() {
99 | # Close the channel.
100 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
101 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
102 | local -r ch="${1}"
103 | shift 1 || { ctx_wn $ctx; return $EC; }
104 |
105 | local -r d=$($ch $ctx d)
106 | local -r closef="${d}/close"
107 | touch "${closef}"
108 | }
109 |
--------------------------------------------------------------------------------
/src/internal/ci:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # CI integration (GitHub actions at the time of this writing).
4 | #
5 | # This script should not depend on anything in the library.
6 |
7 | readonly CI_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
8 |
9 | readonly CI_BASH3_URL="https://ftp.gnu.org/gnu/bash/bash-3.2.57.tar.gz"
10 | readonly CI_BASH4_URL="https://ftp.gnu.org/gnu/bash/bash-4.4.18.tar.gz"
11 | readonly CI_BASH5_URL="https://ftp.gnu.org/gnu/bash/bash-5.2.15.tar.gz"
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function ci_rts() {
18 | # Basic RTS to skip test runs if no bash file is modified.
19 | local -r xbash="${1}"
20 |
21 | # TODO: this function is not compatible with
22 | # cancel-in-progress: true. This is the reason we always force
23 | # runs on the main branch. (We plan to update this once we
24 | # enable storing in cache.)
25 |
26 | # Always run on the main branch.
27 | [ "$(git rev-parse --abbrev-ref HEAD)" = "main" ] && return 0
28 |
29 | # Find all bash files.
30 | local bf=$(mktemp)
31 | "${xbash}" gobash func sys_bash_files > "${bf}"
32 |
33 | # Find the list of modified files.
34 | local df=$(mktemp)
35 | git diff --name-only HEAD^ > "${df}"
36 |
37 | # Return 0 (files modified) or 1 (no file modified).
38 | grep -f "${df}" "${bf}"
39 | }
40 |
41 | function ci_install_bash() {
42 | # Install bash.
43 | local bversion="${1}"
44 | [ -z "${bversion}" ] && return 1
45 |
46 | local var="CI_BASH${bversion}_URL"
47 | local url="${!var}"
48 | echo "$FUNCNAME ${url}"
49 |
50 | local d
51 | d=$(basename "${url}" ".tar.gz") || \
52 | { echo "cannot get basename"; return 1; }
53 |
54 | wget "${url}" || \
55 | { echo "cannot wget"; return 1; }
56 |
57 | tar xfvz "${d}.tar.gz"
58 | ( cd "${d}"
59 | ./configure --prefix $(pwd) ) || \
60 | { echo "cannot configure"; return 1; }
61 |
62 | ( cd "${d}"
63 | make ) || \
64 | { echo "cannot make"; return 1; }
65 |
66 | ( cd "${d}"
67 | make install ) || \
68 | { echo "cannot make install"; return 1; }
69 |
70 | "${d}/bin/bash" --version
71 | }
72 |
73 | function ci_config() {
74 | local bversion="${1}"
75 | [ -z "${bversion}" ] && return 1
76 |
77 | local xbash="bash"
78 | local d=$(find "${CI_MOD}/../.." -name "bash-${bversion}*" -type d)
79 | [ -d "${d}" ] && xbash="${d}/bin/bash"
80 |
81 | "${xbash}" gobash func x_config
82 | }
83 |
84 | function ci_test() {
85 | local bversion="${1}"
86 | [ -z "${bversion}" ] && return 1
87 |
88 | local xbash="bash"
89 | local d=$(find "${CI_MOD}/../.." -name "bash-${bversion}*" -type d)
90 | [ -d "${d}" ] && xbash="${d}/bin/bash"
91 |
92 | if ci_rts "${xbash}"; then
93 | export GOBASH_CI_BASH_VERSION="${bversion}"
94 | "${xbash}" gobash test --verbose --paths .
95 | else
96 | echo "No testing (as no file changed)."
97 | fi
98 | }
99 |
100 | function ci_lint() {
101 | if ci_rts "bash"; then
102 | ./gobash lint --paths .
103 | else
104 | echo "No lint (as no file changed)."
105 | fi
106 | }
107 |
108 | "$@"
109 |
--------------------------------------------------------------------------------
/src/net/response.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Response related structs and functions.
6 |
7 | if [ -n "${RESPONSE_MOD:-}" ]; then return 0; fi
8 | readonly RESPONSE_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${RESPONSE_MOD}/../lang/p.sh
11 |
12 |
13 | # ----------
14 | # Functions.
15 |
16 | function Response() {
17 | # Response to http request.
18 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
19 | [ $# -ne 3 ] && { ctx_wn $ctx; return $EC; }
20 | local -r -i code="${1}"
21 | local -r info="${2}"
22 | local -r proto="${3}"
23 | shift 3 || { ctx_wn $ctx; return $EC; }
24 |
25 | make_ $ctx \
26 | "${FUNCNAME}" \
27 | "code" "${code}" \
28 | "info" "${info}" \
29 | "proto" "${proto}" \
30 | "rawf" "${NULL}"
31 | }
32 |
33 | function Response_write() {
34 | # Buffer the string into the response.
35 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
36 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
37 | local -r res="${1}"
38 | local -r str="${2}"
39 | shift 2 || { ctx_wn $ctx; return $EC; }
40 |
41 | if is_null $ctx "$($res $ctx rawf)"; then
42 | $res $ctx rawf "$(os_mktemp_file $ctx)"
43 | fi
44 | printf "${str}" >> "$($res $ctx rawf)"
45 | }
46 |
47 | function Response_to_string() {
48 | # String version of the response.
49 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
50 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
51 | local -r res="${1}"
52 | shift 1 || { ctx_wn $ctx; return $EC; }
53 |
54 | local prefix="$($res $ctx proto) $($res $ctx code) $($res $ctx info)\n"
55 |
56 | if is_null $ctx "$($res $ctx rawf)"; then
57 | printf "${prefix}"
58 | else
59 | printf "${prefix}\n$(cat $($res $ctx rawf))\n"
60 | fi
61 | }
62 |
63 | function response_make_bad_request() {
64 | # Create a response for 400.
65 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
66 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
67 | shift 0 || { ctx_wn $ctx; return $EC; }
68 |
69 | # No arguments to check.
70 |
71 | Response $ctx 400 "Bad Request" "HTTP/1.1"
72 | }
73 |
74 | function response_make_not_found() {
75 | # Create a response for 404.
76 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
77 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
78 | shift 0 || { ctx_wn $ctx; return $EC; }
79 |
80 | # No arguments to check.
81 |
82 | Response $ctx 404 "Not Found" "HTTP/1.1"
83 | }
84 |
85 | function response_make_ok() {
86 | # Create a response for 200.
87 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
88 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
89 | shift 0 || { ctx_wn $ctx; return $EC; }
90 |
91 | # No arguments to check.
92 |
93 | Response $ctx 200 "OK" "HTTP/1.1"
94 | }
95 |
96 | function response_make_internal_server_error() {
97 | # Create a response for 500.
98 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
99 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
100 | shift 0 || { ctx_wn $ctx; return $EC; }
101 |
102 | # No arguments to check.
103 |
104 | Response $ctx 500 "Internal Server Error" "HTTP/1.1"
105 | }
106 |
--------------------------------------------------------------------------------
/src/tools/bdoc.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Document generation tool.
6 |
7 | if [ -n "${BDOC_MOD:-}" ]; then return 0; fi
8 | readonly BDOC_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${BDOC_MOD}/../lang/p.sh
11 | . ${BDOC_MOD}/../util/p.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function bdoc_enabled() {
18 | # Check if this module is enabled.
19 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
20 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
21 | shift 0 || { ctx_wn $ctx; return $EC; }
22 |
23 | ! is_exe $ctx "doxygen" && return $FALSE
24 |
25 | return $TRUE
26 | }
27 |
28 | function _bdoc_file() {
29 | # Generate doc for a single file.
30 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
31 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
32 | local -r pathf="${1}"
33 | shift 1 || { ctx_wn $ctx; return $EC; }
34 |
35 | local brief=$(head -n 5 "${pathf}" | \
36 | tail -n 1 | \
37 | $X_SED 's/.*#\(.*\)/\1/g')
38 | brief=$(strings_strip "${brief}")
39 |
40 | local path=${pathf#"$(sys_repo_path)/src/"*}
41 | local n=$(strings_len "${path}")
42 | local assigns=$(strings_repeat "=" "${n}")
43 |
44 | echo "${path}"
45 | echo "${assigns}"
46 | echo
47 | echo "${brief}"
48 | echo
49 |
50 | local fun
51 | for fun in $(sys_functions_in_file "${pathf}"); do
52 | [[ "${fun}" = "_"* ]] && continue
53 | echo ".. py:function:: ${fun}"
54 | echo
55 |
56 | local tmpf=$(os_mktemp_file)
57 | sys_function_doc "${pathf}" "${fun}" > "${tmpf}" || \
58 | { ctx_w $ctx "cannot get doc for ${fun}"; return $EC; }
59 | file_prefix_each_line "${tmpf}" " "
60 | $X_CAT "${tmpf}"
61 | echo
62 | done
63 | }
64 |
65 | function bdoc_main() {
66 | # Generate documentation for gobash in the Sphinx format.
67 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
68 | [ $# -ne 0 ] && { ctx_wn $ctx; return $EC; }
69 | shift 0 || { ctx_wn $ctx; return $EC; }
70 |
71 | local -r docd="$(sys_repo_path)/doc"
72 | [ ! -d "${docd}" ] && \
73 | { ctx_w $ctx "doc dir does not exist"; return $EC; }
74 |
75 | local apid
76 | apid=$(os_remake_dir "${docd}/apis") || return $EC
77 |
78 | local content=""
79 | local f
80 | # TODO: pick files from flags.
81 | for f in $(find "$(sys_repo_path)/src" -name "*.sh" | $X_GREP -v '_test.sh'); do
82 | local package_file=${f#"$(sys_repo_path)/src/"*}
83 | local package="${package_file%/*}"
84 | local file=$(echo "${package_file}" | $X_AWK -F"/" '{print $NF}')
85 | [ "${file}" = "p.sh" ] && continue
86 |
87 | local name=$(basename "${file}" .sh)
88 | local rstf="${package//\//_}_${name}.rst"
89 |
90 | _bdoc_file "${f}" > "${apid}/${rstf}" || return $EC
91 | content+=$'\n'" apis/${rstf}"
92 | done
93 |
94 | # Make api.rst file.
95 | $X_CAT << END > "${docd}/api.rst"
96 | API
97 | ===
98 |
99 | .. toctree::
100 | :maxdepth: 2
101 | :caption: Packages:
102 |
103 | ${content}
104 |
105 | END
106 | }
107 |
--------------------------------------------------------------------------------
/src/ui/whiptail_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the whiptail module.
6 |
7 | if [ -n "${WHIPTAIL_TEST_MOD:-}" ]; then return 0; fi
8 | readonly WHIPTAIL_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${WHIPTAIL_TEST_MOD}/whiptail.sh
11 | . ${WHIPTAIL_TEST_MOD}/../testing/bunit.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function test_whiptail_doc() {
18 | whiptail_doc > /dev/null || assert_fail
19 | }
20 | readonly -f test_whiptail_doc
21 |
22 | function test_whiptail_msg_box() {
23 | local -r t="${1}"
24 | ! whiptail_enabled && $t skip "no whiptail"
25 |
26 | local box
27 | box=$(WTMsgBox "Text") || assert_fail
28 |
29 | function whiptail() {
30 | return 0
31 | }
32 | $box show || assert_fail
33 | }
34 | readonly -f test_whiptail_msg_box
35 |
36 | function test_whiptail_input_box() {
37 | local -r t="${1}"
38 | ! whiptail_enabled && $t skip "no whiptail"
39 |
40 | local box
41 | box=$(WTInputBox "Text") || assert_fail
42 |
43 | function whiptail() {
44 | echo "Result" >&3
45 | return 0
46 | }
47 | local -r res=$(UIResult)
48 | $box show "${res}"
49 | assert_eq "Result" "$($res val)"
50 | }
51 | readonly -f test_whiptail_input_box
52 |
53 | function test_whiptail_menu() {
54 | local -r t="${1}"
55 | ! whiptail_enabled && $t skip "no whiptail"
56 |
57 | local -r lst=$(List)
58 | $lst add "option one"
59 | $lst add "option two"
60 | $lst add "option three"
61 |
62 | local box
63 | box=$(WTMenu "Text" "${lst}") || assert_fail
64 |
65 | function whiptail() {
66 | echo "2." >&3
67 | return 0
68 | }
69 | local -r res=$(UIResult)
70 | $box show "${res}"
71 | assert_eq "option three" "$($res val)"
72 | }
73 | readonly -f test_whiptail_menu
74 |
75 | function test_whiptail_checklist() {
76 | local -r t="${1}"
77 | ! whiptail_enabled && $t skip "no whiptail"
78 |
79 | local -r lst=$(List)
80 | $lst add "option one"
81 | $lst add "option two"
82 | $lst add "option three"
83 |
84 | local box
85 | box=$(WTChecklist "Text" "${lst}") || \
86 | assert_fail
87 |
88 | function whiptail() {
89 | echo "1." "2." >&3
90 | return 0
91 | }
92 | local -r res=$(UIResult)
93 | $box show "${res}"
94 | local -r nlst=$($res val)
95 | assert_eq 2 "$($nlst len)"
96 | assert_eq "option two" "$($nlst get 0)"
97 | assert_eq "option three" "$($nlst get 1)"
98 | }
99 | readonly -f test_whiptail_checklist
100 |
101 | function test_whiptail_radiolist() {
102 | local -r t="${1}"
103 | ! whiptail_enabled && $t skip "no whiptail"
104 |
105 | local -r lst=$(List)
106 | $lst add "option one"
107 | $lst add "option two"
108 | $lst add "option three"
109 |
110 | local box
111 | box=$(WTRadiolist "Text" "${lst}") || \
112 | assert_fail
113 |
114 | function whiptail() {
115 | echo "1." >&3
116 | return 0
117 | }
118 | local -r res=$(UIResult)
119 | $box show "${res}"
120 | assert_eq "option two" "$($res val)"
121 | }
122 | readonly -f test_whiptail_radiolist
123 |
--------------------------------------------------------------------------------
/src/sync/chan_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the channel module.
6 |
7 | if [ -n "${CHAN_TEST_MOD:-}" ]; then return 0; fi
8 | readonly CHAN_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${CHAN_TEST_MOD}/chan.sh
11 | . ${CHAN_TEST_MOD}/../testing/bunit.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function test_chan() {
18 | local ch
19 | ch=$(Chan) || \
20 | assert_fail
21 |
22 | ( $ch send 55 ) &
23 |
24 | local i=$(Int)
25 | ( local val=$($ch recv); $i val "${val}" ) &
26 | wait || \
27 | assert_fail
28 |
29 | assert_eq 55 "$($i val)"
30 | }
31 | readonly -f test_chan
32 |
33 | function test_chan_values() {
34 | local ch
35 | ch=$(Chan) || \
36 | assert_fail
37 |
38 | ( $ch send 1 ) &
39 | ( $ch send 2 ) &
40 | ( $ch send 3 ) &
41 |
42 | local s=0
43 | local i
44 | for (( i=0; i<3; i++ )); do
45 | local val
46 | val=$($ch recv) || \
47 | assert_fail
48 | s=$(( ${s} + ${val} ))
49 | done
50 |
51 | wait || \
52 | assert_fail
53 |
54 | assert_eq 6 "${s}"
55 | }
56 | readonly -f test_chan_values
57 |
58 | function test_chan_objects() {
59 | function Point() {
60 | make_ "${FUNCNAME}" \
61 | "x" "${1}" \
62 | "y" "${2}"
63 | }
64 |
65 | local ch
66 | ch=$(Chan) || \
67 | assert_fail
68 |
69 | ( $ch send "$(Point 3 4)" ) &
70 | ( $ch send "$(Point 8 8)" ) &
71 |
72 | local i
73 | for (( i=0; i<2; i++ )); do
74 | local val
75 | val=$($ch recv) || \
76 | assert_fail
77 | is_instanceof "${val}" Point || \
78 | assert_fail
79 |
80 | [ $($val x) -eq 3 -o $($val x) -eq 8 ] || \
81 | assert_fail
82 |
83 | [ $($val y) -eq 4 -o $($val y) -eq 8 ] || \
84 | assert_fail
85 | done
86 | wait || \
87 | assert_fail
88 | }
89 | readonly -f test_chan_objects
90 |
91 | function test_chan_close() {
92 | local ch
93 | ch=$(Chan) || \
94 | assert_fail
95 |
96 | order_ch=$(Chan) || \
97 | assert_fail
98 |
99 | ( $ch send 33; $order_ch send "go" ) &
100 | ( $ch send 55; $order_ch send "go" ) &
101 |
102 | ( $order_ch recv > /dev/null; $order_ch recv > /dev/null; $ch close ) &
103 |
104 | while :; do
105 | local ec=0
106 | $ch recv > /dev/null || ec=$?
107 | is_false ${ec} && break
108 | done
109 | }
110 | readonly -f test_chan_close
111 |
112 | function test_chan_sends_recv() {
113 | local ch
114 | ch=$(Chan) || \
115 | assert_fail
116 |
117 | local -r -i n=2
118 | local i
119 | for (( i=0; i<${n}; i++ )); do
120 | ( $ch send $i ) &
121 | done
122 |
123 | for (( i=0; i<${n}; i++ )); do
124 | local ec=0
125 | $ch recv > /dev/null || ec=$?
126 | is_false ${ec} && break
127 | done
128 | wait || \
129 | assert_fail
130 | }
131 | readonly -f test_chan_sends_recv
132 |
--------------------------------------------------------------------------------
/src/util/os.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # OS util functions.
6 |
7 | if [ -n "${UTIL_OS_MOD:-}" ]; then return 0; fi
8 | readonly UTIL_OS_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${UTIL_OS_MOD}/time.sh
11 | . ${UTIL_OS_MOD}/fileinfo.sh
12 | . ${UTIL_OS_MOD}/../lang/p.sh
13 |
14 |
15 | # ----------
16 | # Functions.
17 |
18 | function os_stat() {
19 | # Return info describing a file.
20 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
21 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
22 | local path="${1}"
23 | shift 1 || { ctx_wn $ctx; return $EC; }
24 |
25 | local fi
26 | fi=$(FileInfo $ctx "${path}") || \
27 | { ctx_w $ctx "could not create FileInfo"; return $EC; }
28 |
29 | echo "${fi}"
30 | }
31 |
32 | function os_timeout() {
33 | # Run the given command (with arguments) up to timeout. A
34 | # duration of 0 disables timeout.
35 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
36 | [ $# -lt 1 ] && { ctx_wn $ctx; return $EC; }
37 | local -r max_secs="${1}"
38 | shift 1 || { ctx_wn $ctx; return $EC; }
39 |
40 | [ -z "${max_secs}" ] && { ctx_w $ctx "no max_secs"; return $EC; }
41 |
42 | # Run in background.
43 | ( "$@" ) &
44 | local -r pid=$!
45 |
46 | os_wait "${pid}" "${max_secs}"
47 | }
48 |
49 | function os_wait() {
50 | # Wait for the given process to complete or timeout.
51 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
52 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
53 | local -r pid="${1}"
54 | local -r max_secs="${2}"
55 | shift 2 || { ctx_wn $ctx; return $EC; }
56 |
57 | [ -z "${pid}" ] && { ctx_w $ctx "no pid"; return $EC; }
58 | [ -z "${max_secs}" ] && { ctx_w $ctx "no max_secs"; return $EC; }
59 |
60 | if [ "${max_secs}" -gt 0 ]; then
61 | # Launch a process that will timeout after ${max_secs}
62 | # In case of timeout, kill the process and its children.
63 | ( sleep "${max_secs}"
64 | child_pid=$(pgrep -P "${pid}" | xargs)
65 | os_kill $ctx "${child_pid}"
66 | os_kill $ctx "${pid}"
67 | ) 2> /dev/null &
68 | fi
69 |
70 | # Wait for the process, so we get the exit code.
71 | wait "${pid}" 2> /dev/null
72 | }
73 |
74 | function os_loop_n() {
75 | # Run the given command (with arguments) in a loop n time.
76 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
77 | [ $# -lt 1 ] && { ctx_wn $ctx; return $EC; }
78 | local -r n="${1}"
79 | shift 1 || { ctx_wn $ctx; return $EC; }
80 |
81 | local ec=0
82 |
83 | local i
84 | for (( i=0; i<${n}; i++ )); do
85 | "$@" || ec=$?
86 | done
87 | return ${ec}
88 | }
89 |
90 | function os_loop_secs() {
91 | # Run the given command in a loop until time expires.
92 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
93 | [ $# -lt 1 ] && { ctx_wn $ctx; return $EC; }
94 | local -r secs="${1}"
95 | shift 1 || { ctx_wn $ctx; return $EC; }
96 |
97 | local ec=0
98 |
99 | local -r stime=$(time_now_millis $ctx)
100 | while :; do
101 | "$@" || ec=$?
102 |
103 | local etime=$(time_now_millis $ctx)
104 | local duration=$(( ${etime} - ${stime} ))
105 | [ ${duration} -ge ${secs} ] && break
106 | done
107 | return ${ec}
108 | }
109 |
--------------------------------------------------------------------------------
/src/util/map.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Map collection.
6 |
7 | if [ -n "${MAP_MOD:-}" ]; then return 0; fi
8 | readonly MAP_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${MAP_MOD}/list.sh
11 |
12 |
13 | # ----------
14 | # Functions.
15 |
16 | function Map() {
17 | # Map collection.
18 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
19 | [ $# -lt 0 ] && { ctx_wn $ctx; return $EC; }
20 | shift 0 || { ctx_wn $ctx; return $EC; }
21 |
22 | local map
23 | map=$(make_ $ctx "${FUNCNAME}") || \
24 | { ctx_w "cannot make ${FUNCNAME}"; return $EC; }
25 |
26 | local -r nargs=$#
27 | local -i i
28 | for (( i=0; i<${nargs}; i+=2 )); do
29 | $map $ctx put "${1}" "${2}" || \
30 | { ctx_w $ctx "cannot put"; return $EC; }
31 |
32 | shift 2 || \
33 | { ctx_w $ctx "insufficient args"; return $EC; }
34 | done
35 |
36 | echo "${map}"
37 | }
38 |
39 | function Map_len() {
40 | # Size of the map.
41 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
42 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
43 | local -r map="${1}"
44 | shift 1 || { ctx_wn $ctx; return $EC; }
45 |
46 | unsafe_len $ctx "${map}" || \
47 | { ctx_w $ctx "cannot get map len"; return $EC; }
48 | }
49 |
50 | function Map_put() {
51 | # Add key, value pair into the map.
52 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
53 | [ $# -ne 3 ] && { ctx_wn $ctx; return $EC; }
54 | local -r map="${1}"
55 | local -r key="${2}"
56 | local -r val="${3}"
57 | shift 3 || { ctx_wn $ctx; return $EC; }
58 |
59 | [ -z "${map}" ] && { ctx_w $ctx "no map"; return $EC; }
60 | [ -z "${key}" ] && { ctx_w $ctx "no key"; return $EC; }
61 | # Value can be empty.
62 |
63 | unsafe_set_fld $ctx "${map}" "${key}" "${val}" || \
64 | { ctx_w $ctx "could not put into map"; return $EC; }
65 | }
66 |
67 | function Map_get() {
68 | # Get (value, 0) for the key. Return (null, 1) if key is not
69 | # available.
70 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
71 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
72 | local -r map="${1}"
73 | local -r key="${2}"
74 | shift 2 || { ctx_wn $ctx; return $EC; }
75 |
76 | unsafe_get_fld "${map}" "${key}"
77 | }
78 |
79 | function Map_inc() {
80 | # Increment value for the given key.
81 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
82 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
83 | local -r map="${1}"
84 | local -r key="${2}"
85 | shift 2 || { ctx_wn $ctx; return $EC; }
86 |
87 | local ec=0
88 | local val
89 | val=$($map $ctx get "${key}") || \
90 | { ec=$?; [ ${ec} -gt $FALSE ] && return ${ec}; }
91 |
92 | if [ ${ec} -eq 0 ]; then
93 | # Key is already in.
94 | $map $ctx put "${key}" "$(( ${val} + 1 ))" || \
95 | { ctx_w $ctx "cannot put"; return $EC; }
96 | else
97 | # No key.
98 | $map $ctx put "${key}" 1 || \
99 | { ctx_w $ctx "cannot put"; return $EC; }
100 | fi
101 | }
102 |
103 | function Map_keys() {
104 | # Return list of keys in the same order as in the map.
105 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
106 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
107 | local -r map="${1}"
108 | shift 1 || { ctx_wn $ctx; return $EC; }
109 |
110 | unsafe_keys $ctx "${map}"
111 | }
112 |
--------------------------------------------------------------------------------
/src/util/regexp_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Unit tests for the regexp module.
6 |
7 | if [ -n "${REGEXP_TEST_MOD:-}" ]; then return 0; fi
8 | readonly REGEXP_TEST_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${REGEXP_TEST_MOD}/regexp.sh
11 | . ${REGEXP_TEST_MOD}/../testing/bunit.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function test_regexp_match_string() {
18 | local ec
19 |
20 | regexp_match_string ".essy" "Jessy" || \
21 | assert_fail
22 |
23 | regexp_match_string "J[e|b]ssy" "Jessy" || \
24 | assert_fail
25 |
26 | regexp_match_string "J[e|b]ssy" "Jbssy" || \
27 | assert_fail
28 |
29 | ec=0
30 | regexp_match_string "J[e|b]ssy" "Jdssy" || ec=$?
31 | assert_false ${ec}
32 |
33 | ec=0
34 | regexp_match_string "*J[e|b]ssy" "Jdssy" || ec=$?
35 | assert_ec ${ec}
36 |
37 | local ctx=$(ctx_make)
38 | regexp_match_string $ctx "!~~%^^^[" "string" && return $EC
39 | ctx_show $ctx | grep 'incorrect re' || \
40 | assert_fail
41 | }
42 | readonly -f test_regexp_match_string
43 |
44 | function test_regexp_compile() {
45 | local regexp
46 | local ec
47 |
48 | regexp=$(regexp_compile "app[le]") || \
49 | assert_fail
50 |
51 | ec=0
52 | regexp=$(regexp_compile "*apple") || ec=$?
53 | assert_ec ${ec}
54 |
55 | regexp=$(regexp_compile "app[le]") || \
56 | assert_fail
57 |
58 | $regexp match_string "appe" || \
59 | assert_fail
60 |
61 | $regexp match_string "appl" || \
62 | assert_fail
63 |
64 | ec=0
65 | $regexp match_string "appb" || ec=$?
66 | assert_false ${ec}
67 |
68 | local ctx=$(ctx_make)
69 | regexp=$(regexp_compile $ctx "!~~%^^^[") && \
70 | assert_fail
71 | ctx_show $ctx | grep 'incorrect re' || \
72 | assert_fail
73 | }
74 | readonly -f test_regexp_compile
75 |
76 | function test_regexp_to_string() {
77 | local regexp
78 | regexp=$(regexp_compile "app[le]") || \
79 | assert_fail
80 | assert_eq "app[le]" "$($regexp to_string)"
81 | }
82 | readonly -f test_regexp_to_string
83 |
84 | function test_regexp_find_string() {
85 | local regexp
86 | regexp=$(regexp_compile "app[le]") || \
87 | assert_fail
88 |
89 | local str
90 | str=$($regexp find_string "some appe is an appl") || \
91 | assert_fail
92 | assert_eq "appe" "${str}"
93 | }
94 | readonly -f test_regexp_find_string
95 |
96 | function test_regexp_string_index() {
97 | local regexp
98 | regexp=$(regexp_compile "app[le]") || \
99 | assert_fail
100 |
101 | local str="some appe is an appl"
102 |
103 | local lst
104 | lst=$($regexp find_string_index "${str}") || \
105 | assert_fail
106 |
107 | local res="${str:$($lst first):$($lst second)}"
108 | assert_eq "appe" "${res}"
109 |
110 | }
111 | readonly -f test_regexp_string_index
112 |
113 | function test_regexp_string_submatch() {
114 | local regexp
115 | regexp=$(regexp_compile "a(.*)b(.*)c") || \
116 | assert_fail
117 |
118 | local str="xxxawillbmatchczzz"
119 |
120 | local lst
121 | lst=$($regexp find_string_submatch "${str}") || \
122 | assert_fail
123 |
124 | assert_eq "awillbmatchc" "$($lst get 0)"
125 | assert_eq "will" "$($lst get 1)"
126 | assert_eq "match" "$($lst get 2)"
127 | }
128 | readonly -f test_regexp_string_submatch
129 |
--------------------------------------------------------------------------------
/src/lang/x.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # gobash dependencies.
6 |
7 | if [ -n "${X_MOD:-}" ]; then return 0; fi
8 | readonly X_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | # echo - built-in
11 | # printf - built-in
12 |
13 | readonly X_PS="ps"
14 | readonly X_GREP="grep"
15 | readonly X_AWK="awk"
16 | readonly X_BASH=$("$X_PS" aux | "$X_GREP" "$$" | "$X_GREP" 'bash' | "$X_AWK" '{ print $11 }')
17 | readonly X_SED="sed"
18 | readonly X_JQ="jq"
19 | readonly X_CUT="cut"
20 | readonly X_MD5=$(which md5 || which md5sum)
21 | readonly X_SLEEP="sleep"
22 | readonly X_BC="bc"
23 |
24 | readonly X_DATE="date"
25 | readonly X_CAT="cat"
26 | readonly X_TAIL="tail"
27 | readonly X_HEAD="head"
28 | readonly X_MKTEMP="mktemp"
29 | readonly X_WC="wc"
30 | readonly X_CALLER="caller"
31 | readonly X_PGREP="pgrep"
32 | readonly X_XARGS="xargs"
33 | readonly X_DOXYGEN="doxygen"
34 |
35 |
36 | # ----------
37 | # Functions.
38 |
39 | function _x_check() {
40 | # Chck if the given command exists.
41 | local cmd="${1}"
42 |
43 | command -v "${cmd}" > /dev/null 2>&1 || \
44 | { echo "missing '${cmd}'"; return 1; }
45 | }
46 |
47 | function x_enabled() {
48 | # Check if this module is enabled (which is the case if all
49 | # dependencies are available).
50 |
51 | [ -z "${BASH}" ] && \
52 | { echo "this library only supports bash"; return 1; }
53 |
54 | command > /dev/null 2>&1 || \
55 | { echo "missing 'command'"; return 1; }
56 |
57 | _x_check "${X_PS}" || return 1
58 | _x_check "${X_GREP}" || return 1
59 | _x_check "${X_AWK}" || return 1
60 | _x_check "${X_BASH}" || return 1
61 | _x_check "${X_SED}" || return 1
62 | _x_check "${X_JQ}" || return 1
63 | _x_check "${X_CUT}" || return 1
64 | _x_check "${X_MD5}" || return 1
65 | _x_check "${X_BC}" || return 1
66 | }
67 |
68 | function x_bash_version() {
69 | # Print bash version.
70 |
71 | # Could/should use PIPESTATUS in version functions.
72 |
73 | "${X_BASH}" --version 2>&1 | head -n 1 || \
74 | "no version for ${X_BASH}"
75 | }
76 |
77 | function x_ps_version() {
78 | # Print ps version.
79 |
80 | "${X_PS}" --version 2>&1 | head -n 1 || \
81 | "no version for ${X_PS}"
82 | }
83 |
84 | function x_grep_version() {
85 | # Print grep version.
86 |
87 | "${X_GREP}" --version 2>&1 | head -n 1 || \
88 | "no version for ${X_GREP}"
89 | }
90 |
91 | function x_awk_version() {
92 | # Print awk version.
93 |
94 | "${X_AWK}" -W version 2>&1 | head -n 1 || \
95 | "no version for ${X_AWK}"
96 | }
97 |
98 | function x_sed_version() {
99 | # Print sed version.
100 |
101 | "${X_SED}" --version 2>&1 | head -n 1 || \
102 | "no version for ${X_SED}"
103 | }
104 |
105 | function x_jq_version() {
106 | # Print jq version.
107 |
108 | "${X_JQ}" --version 2>&1 | head -n 1 || \
109 | "no version for ${X_JQ}"
110 | }
111 |
112 | function x_cut_version() {
113 | # Print cut version.
114 |
115 | "${X_CUT}" --version 2>&1 | head -n 1 || \
116 | "no version for ${X_CUT}"
117 | }
118 |
119 | function x_bc_version() {
120 | # Print bc version
121 |
122 | "${X_BC}" --version 2>&1 | head -n 1 || \
123 | "no version for ${X_BC}"
124 | }
125 |
126 | function x_config() {
127 | # Print system configuration.
128 |
129 | x_bash_version
130 | x_ps_version
131 | x_grep_version
132 | x_awk_version
133 | x_sed_version
134 | x_jq_version
135 | x_cut_version
136 | x_bc_version
137 | }
138 |
--------------------------------------------------------------------------------
/src/lang/int.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # https://github.com/EngineeringSoftware/gobash/blob/main/LICENSE
4 | #
5 | # Int related structs and functions.
6 |
7 | if [ -n "${INT_MOD:-}" ]; then return 0; fi
8 | readonly INT_MOD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
9 |
10 | . ${INT_MOD}/core.sh
11 | . ${INT_MOD}/make.sh
12 |
13 |
14 | # ----------
15 | # Functions.
16 |
17 | function Int() {
18 | # An integer.
19 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
20 | [ $# -lt 0 ] && { ctx_wn $ctx; return $EC; }
21 | local -r val="${1:-0}"
22 | shift 0 || { ctx_wn $ctx; return $EC; }
23 |
24 | make_ $ctx \
25 | "${FUNCNAME}" \
26 | "val" "${val}"
27 | }
28 |
29 | function Int_inc() {
30 | # Increment value.
31 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
32 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
33 | local -r i="${1}"
34 | shift 1 || { ctx_wn $ctx; return $EC; }
35 |
36 | $i $ctx val $(( $($i $ctx val) + 1 ))
37 | }
38 |
39 | function Int_dec() {
40 | # Decrement value.
41 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
42 | [ $# -ne 1 ] && { ctx_wn $ctx; return $EC; }
43 | local -r i="${1}"
44 | shift 1 || { ctx_wn $ctx; return $EC; }
45 |
46 | $i $ctx val $(( $($i $ctx val) - 1 ))
47 | }
48 |
49 | function Int_gt() {
50 | # Return true if greater or equal to other.
51 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
52 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
53 | local -r i="${1}"
54 | local other="${2}"
55 | shift 2 || { ctx_wn $ctx; return $EC; }
56 |
57 | # Support int and Int.
58 | if ! is_int $ctx "${other}"; then
59 | other=$($other $ctx val)
60 | fi
61 |
62 | [ $($i $ctx val) -gt ${other} ]
63 | }
64 |
65 | function Int_ge() {
66 | # Return true if greater or equal to other.
67 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
68 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
69 | local -r i="${1}"
70 | local other="${2}"
71 | shift 2 || { ctx_wn $ctx; return $EC; }
72 |
73 | # Support int and Int.
74 | if ! is_int $ctx "${other}"; then
75 | other=$($other $ctx val)
76 | fi
77 |
78 | [ $($i $ctx val) -ge ${other} ]
79 | }
80 |
81 | function Int_eq() {
82 | # Return true if equal to other.
83 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
84 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
85 | local -r i="${1}"
86 | local other="${2}"
87 | shift 2 || { ctx_wn $ctx; return $EC; }
88 |
89 | # Support int and Int.
90 | if ! is_int $ctx "${other}"; then
91 | other=$($other $ctx val)
92 | fi
93 |
94 | [ $($i $ctx val) -eq ${other} ]
95 | }
96 |
97 | function Int_lt() {
98 | # Return true if less than other.
99 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
100 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
101 | local -r i="${1}"
102 | local other="${2}"
103 | shift 2 || { ctx_wn $ctx; return $EC; }
104 |
105 | # Support int and Int.
106 | if ! is_int $ctx "${other}"; then
107 | other=$($other $ctx val)
108 | fi
109 |
110 | [ $($i $ctx val) -lt ${other} ]
111 | }
112 |
113 | function Int_le() {
114 | # Return true if less than or equal to other.
115 | local ctx; is_ctx "${1}" && ctx="${1}" && shift
116 | [ $# -ne 2 ] && { ctx_wn $ctx; return $EC; }
117 | local -r i="${1}"
118 | local other="${2}"
119 | shift 2 || { ctx_wn $ctx; return $EC; }
120 |
121 | # Support int and Int.
122 | if ! is_int $ctx "${other}"; then
123 | other=$($other $ctx val)
124 | fi
125 |
126 | [ $($i $ctx val) -le ${other} ]
127 | }
128 |
--------------------------------------------------------------------------------