├── .github └── workflows │ ├── documentation.yml │ ├── lint.yml │ ├── test-bash3.yml │ ├── test-bash4.yml │ ├── test-bash5.yml │ └── test-mac.yml ├── .gitignore ├── LICENSE ├── README.md ├── doc ├── Makefile ├── api.rst ├── collections.rst ├── command-line-flags.rst ├── conf.py ├── design.rst ├── get-started.rst ├── gobash.gif ├── index.rst ├── interactive-mode.rst ├── language.rst ├── make.bat ├── motivation.rst ├── next.rst ├── process-communication.rst ├── style.md └── testing.rst ├── examples ├── README.md ├── anonymous_struct_ex ├── binary_trees_ex ├── chan_ex ├── error_ex ├── examples_test.sh ├── file_ex ├── flags_details_ex ├── flags_ex ├── hello_world_ex ├── linked_list_ex ├── list_ex ├── log_ex ├── map_ex ├── methods_ex ├── mutex_counter_ex ├── playground │ ├── clear_screen_ex │ ├── concurrent_pi_ex │ ├── hellow_world_ex │ ├── http_server_ex │ ├── list_ex │ ├── playground_test.sh │ ├── ring_do_ex │ ├── ring_len_ex │ ├── ring_link_ex │ ├── ring_move_ex │ ├── ring_next_ex │ ├── ring_prev_ex │ ├── ring_unlink_ex │ ├── sleep_ex │ └── test_function_ex ├── regexp_ex ├── result_ex ├── rewrites │ ├── flink_data_generator_ex │ └── jattack_ex ├── shapes_ex ├── strings_ex ├── structs_ex ├── template_ex ├── text_menu_ex ├── text_progress_ex ├── text_spinner_ex ├── to_json_ex ├── to_string_ex ├── tour │ ├── case_details_ex │ ├── case_ex │ ├── fact_ex │ ├── for_ex │ ├── func_ex │ ├── if_else_ex │ ├── if_ex │ ├── infinite_ex │ ├── loops_funcs_ex │ ├── rand_ex │ ├── select_details_ex │ ├── select_ex │ ├── test_cmd_ex │ ├── tour_test.sh │ ├── until_ex │ ├── variables_details_ex │ ├── variables_ex │ ├── welcome_ex │ └── while_ex ├── user_ex ├── visitor_ex ├── wait_group_ex ├── web_server_ex └── whiptail_ex ├── gobash ├── gobash_test.sh ├── hsabog └── src ├── container ├── list.sh ├── list_test.sh ├── p.sh ├── ring.sh └── ring_test.sh ├── database ├── p.sh └── sql.sh ├── external └── git │ ├── github.sh │ ├── github_test.sh │ └── testdata │ ├── branch_info.json │ ├── branches.json │ ├── jobs.json │ ├── pr.json │ ├── pr_commits.json │ ├── runs.json │ └── tags.json ├── internal └── ci ├── lang ├── assert.sh ├── assert_test.sh ├── bash.sh ├── bash_test.sh ├── bool.sh ├── bool_test.sh ├── core.sh ├── core_test.sh ├── int.sh ├── int_test.sh ├── jqmem.sh ├── jqmem_test.sh ├── log.sh ├── log_test.sh ├── make.sh ├── make_test.sh ├── os.sh ├── os_test.sh ├── p.sh ├── pipe.sh ├── pipe_test.sh ├── result.sh ├── result_test.sh ├── runtime.sh ├── runtime_test.sh ├── sys.sh ├── sys_test.sh ├── unsafe.sh ├── unsafe_test.sh └── x.sh ├── net ├── handler.sh ├── http.sh ├── http_test.sh ├── p.sh ├── request.sh ├── response └── response.sh ├── p.sh ├── sync ├── atomic_int.sh ├── atomic_int_test.sh ├── chan.sh ├── chan_test.sh ├── mutex.sh ├── mutex_test.sh ├── p.sh ├── wait_group.sh └── wait_group_test.sh ├── testing ├── bunit.sh ├── bunit_result.sh ├── bunit_test.sh ├── p.sh ├── testdata │ └── test_bunit_junitxml.xml └── testt.sh ├── tools ├── bdoc.sh ├── bdoc_test.sh ├── blint.sh ├── blint_test.sh ├── p.sh └── testdata │ ├── blint_brief_doc │ ├── blint_readonly_tests │ ├── blint_she_bang │ ├── blint_signature │ └── blint_tabs ├── ui ├── p.sh ├── textui.sh ├── textui_test.sh ├── ui.sh ├── whiptail.sh └── whiptail_test.sh └── util ├── binary.sh ├── binary_test.sh ├── char.sh ├── complex.sh ├── complex_test.sh ├── file.sh ├── file_test.sh ├── fileinfo.sh ├── filepath.sh ├── filepath_test.sh ├── flags.sh ├── flags_test.sh ├── list.sh ├── list_test.sh ├── map.sh ├── map_test.sh ├── math.sh ├── math_test.sh ├── os.sh ├── os_test.sh ├── p.sh ├── rand.sh ├── rand_test.sh ├── regexp.sh ├── regexp_test.sh ├── set.sh ├── set_test.sh ├── strings.sh ├── strings_test.sh ├── time.sh ├── time_test.sh ├── user.sh └── user_test.sh /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *~ 3 | .objects/ 4 | doc/_build 5 | 6 | # Auto generated in CI. 7 | doc/apis/ 8 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /doc/gobash.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EngineeringSoftware/gobash/6c0aa7f472ce694fcf8cb0db3e5f6046e00d687f/doc/gobash.gif -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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_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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/testing/testdata/test_bunit_junitxml.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 14 | 15 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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_she_bang: -------------------------------------------------------------------------------- 1 | 2 | x=33 3 | -------------------------------------------------------------------------------- /src/tools/testdata/blint_signature: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function fun() 4 | { 5 | : 6 | } 7 | 8 | function fun2() { 9 | : 10 | } 11 | -------------------------------------------------------------------------------- /src/tools/testdata/blint_tabs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # why does anyone use tabs 4 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------