diff --git a/.editorconfig b/.editorconfig index ea876ca..4f370db 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,29 +1,29 @@ +# Copyright (c) Alexandre Gomes Gaigalas +# SPDX-License-Identifier: ISC + # Coral Editor Configuration # -# These are settings required for a nice and cozy -# formatting of our source. -# -# See http://editorconfig.org/ +# These are settings required for our source. # +# See http://editorconfig.org/ and make sure you install proper +# support for your editor. # Settings for all files # # - Unix-style newlines # - Adds a newline to the end of file if not present # - Removes trailing spaces from lines -# - Suggests 78 width line lenght (RFC 5322 Section 2.1.1) -# +# - Suggests 78 width line length (RFC 5322 Section 2.1.1) [*] - end_of_line = lf - insert_final_newline = true - trim_trailing_whitespace = true - rulers = 78 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +rulers = 78 # Settings for shell script files # # - Use tabs, required for HEREDOCs to work properly # - Use a 4-space tab width -# [*.sh] - indent_style = tab - tab_width = 4 +indent_style = tab +tab_width = 4 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5928ffb --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,17 @@ +name: CI Build + +on: + push: + branches: + - "main" + pull_request: + branches: + - "main" + +job: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: make docker-matrix + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0d04b1f..0000000 --- a/.travis.yml +++ /dev/null @@ -1,71 +0,0 @@ -language: bash -services: - - docker - -before_install: - # Update brew catalogue if going to install anything - - ${BREW:+brew update} - - ${BREW:+brew install ${BREW}} - - # Build spec_doc module - - ${spec_doc_shell} ./lib/dev module_assemble module_assemble module_assemble - - ${spec_doc_shell} ./module_assemble spec_doc spec_doc - - chmod +x spec_doc - -script: - - spec_doc_shell="${spec_doc_shell}" ${spec_doc_shell} ./spec_doc $(find ${TARGET}) - -matrix: - include: - # All Linux builds as docker images - - env: LINUX_DOCKER= spec_doc_shell=sh TARGET="doc/test/*" - os: linux - - # OS X Targeted Builds - - env: HIGH_SIERRA= spec_doc_shell="bash" TARGET="doc/spec/*" - os: osx - osx_image: xcode9.3 - - - env: SIERRA= spec_doc_shell="bash" TARGET="doc/spec/*" - os: osx - osx_image: xcode9.2 - - - env: SIERRA= spec_doc_shell="bash" TARGET="doc/spec/*" - os: osx - osx_image: xcode9.1 - - - env: SIERRA= spec_doc_shell="bash" TARGET="doc/spec/*" - os: osx - osx_image: xcode9 - - - env: SIERRA= spec_doc_shell="bash" TARGET="doc/spec/*" - os: osx - osx_image: xcode8.3 - - - env: EL_CAPITAN= spec_doc_shell="bash" TARGET="doc/spec/*" - os: osx - osx_image: xcode8 - - - env: EL_CAPITAN= spec_doc_shell="bash" TARGET="doc/spec/*" - os: osx - osx_image: xcode7.3 - - - env: YOSEMITE= spec_doc_shell="bash" TARGET="doc/spec/*" - os: osx - osx_image: xcode6.4 - - # BREW BUILDS - - env: BREW="bash" spec_doc_shell="bash" TARGET="doc/spec/*" - os: osx - - - env: BREW="zsh" spec_doc_shell="zsh" TARGET="doc/spec/*" - os: osx - - - env: BREW="ksh" spec_doc_shell="ksh" TARGET="doc/spec/*" - os: osx - - - env: BREW="mksh"spec_doc_shell="mksh" TARGET="doc/spec/*" - os: osx - - - env: BREW="dash" spec_doc_shell="dash" TARGET="doc/spec/*" - os: osx diff --git a/LICENSE b/LICENSE index e61d39b..02721e9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,19 +1,15 @@ -Copyright (c) 2017 Alexandre Gaigalas +ISC License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) 2024 Alexandre Gomes Gaigalas -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..19d71e7 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +IMAGE = alganet/shell-versions:all + +.PHONY: help docker-matrix + +help: + @echo "Usage: make docker-matrix" + +docker-matrix: + @docker run -it --rm \ + -v $(PWD):/opt/coral \ + -w /opt/coral \ + $(IMAGE) \ + sh test/matrix -c "./entrypoint.sh tap 'test/*/*'" diff --git a/README.md b/README.md index 90aa9bf..56ac2e0 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,34 @@ 🐚 coral -======= +======== -A modest shell script library that _works everywhere_. +a **modular** library to create **portable shell scripts** that run everywhere. -> We are under _prototype_ development. Things are expected to work just -fine though. +## Introduction -`coral` is designed to fill strategic gaps in _Shell Script_ by -providing portable libraries that work across [multiple shells](doc/test/common.md). +_coral_ is meant to solve the script portability problem the hard way: by writing reusable code and testing it. ---- +There is no compilation or build step. Once you clone the repository, you're good to go: -### [require.sh](doc/spec/require.md) +```sh +./entrypoint.sh help +``` -A modern module loader with support for circular references and multiple -paths. +# Testing -All `coral` libraries are exported as reusable modules. +Run all tests locally against the default shell: -### [spec/doc.sh](doc/spec/spec_doc.md) +```sh +./entrypoint.sh tap +``` -A [literate](https://en.wikipedia.org/wiki/Literate_programming) test -runner that runs documents written for humans as automated tests. +Run a single test. For example, the pseudoarray implementation: -It is used to test all `coral` software. +```sh +sh entrypoint.sh tap 'test/_idiom/005-Arr.sh' +``` -### [module/assemble.sh](doc/spec/module_assemble.md) - -A shell script bundler that can create single executables from multiple -shell sources, supports runtime evaluated code and plays along with -`require.sh`. - -It is used to build the `coral` releases. - -### [lib/dev](doc/spec/lib_dev.md) - -Used to bootstrap the modules, build and develop `coral` applications -from scratch. - ---- +Run all tests inside an ephemeral docker container against all shells: +```sh +make docker-matrix +``` diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 40f27b4..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,51 +0,0 @@ -version: "0.0.{build}" -os: Windows Server 2012 R2 -build: off -deploy: off - -install: - # Update pacman if going to install anything - - cmd: call %SHELLENV% -lc "${PACMAN:+pacman --needed --noconfirm -Sy pacman-mirrors}" - - cmd: call %SHELLENV% -lc "${PACMAN:+pacman --noconfirm -Sy}" - - # Install pacman dependencies - - cmd: call %SHELLENV% -lc "${PACMAN:+pacman --noconfirm -S $PACMAN}" - - # Build spec_doc module - - cmd: call %SHELLENV% -lc "cd $APPVEYOR_BUILD_FOLDER; ${spec_doc_shell} ./lib/dev module_assemble module_assemble module_assemble" - - cmd: call %SHELLENV% -lc "cd $APPVEYOR_BUILD_FOLDER; ${spec_doc_shell} ./module_assemble spec_doc spec_doc" - - cmd: call %SHELLENV% -lc "cd $APPVEYOR_BUILD_FOLDER; chmod +x spec_doc" - -test_script: - - cmd: call %SHELLENV% -lc "cd $APPVEYOR_BUILD_FOLDER; spec_doc_shell=${spec_doc_shell} ${spec_doc_shell} ./spec_doc $(find doc/spec/*)" - -environment: - matrix: - - spec_doc_shell: bash - PACMAN: bash - SHELLENV: C:\msys64\usr\bin\bash - - - spec_doc_shell: dash - PACMAN: dash - SHELLENV: C:\msys64\usr\bin\bash - - - spec_doc_shell: mksh - PACMAN: mksh - SHELLENV: C:\msys64\usr\bin\bash - - - spec_doc_shell: ksh - PACMAN: ksh - SHELLENV: C:\msys64\usr\bin\bash - - - spec_doc_shell: busybox sh - PACMAN: busybox - SHELLENV: C:\msys64\usr\bin\bash - - - spec_doc_shell: bash - SHELLENV: C:\MinGW\msys\1.0\bin\bash - - - spec_doc_shell: bash - SHELLENV: C:\cygwin\bin\bash - - - spec_doc_shell: bash - SHELLENV: C:\cygwin64\bin\bash diff --git a/doc/spec/fs_basename.md b/doc/spec/fs_basename.md deleted file mode 100644 index fdbd8f8..0000000 --- a/doc/spec/fs_basename.md +++ /dev/null @@ -1,44 +0,0 @@ -fs_basename -=========== - -Extracts the file name portion of a path and prints it out. - - dirname filename - | | - __________________ ____________ - /some/example/path/to/this_is_a_file - -Sample Usage ------------- - -Extracting the file name `bar` from the `foo/bar` path: - -```console test -$ ./lib/dev fs_basename foo/bar -bar -``` - -Specification -------------- - -Dealing with file names: - -```console test -$ ./lib/dev fs_basename foo/bar/baz -baz -$ ./lib/dev fs_basename foo/bar/baz.lorem -baz.lorem -$ ./lib/dev fs_basename foo/bar/.ipsum -.ipsum -``` - -Dealing with multiple and absolute slashes: - -```console test -$ ./lib/dev fs_basename foo/bar//.ipsum -.ipsum -$ ./lib/dev fs_basename foo/bar/////.ipsum -.ipsum -$ ./lib/dev fs_basename /some/example/path/to/this_is_a_file -this_is_a_file -``` diff --git a/doc/spec/fs_dirname.md b/doc/spec/fs_dirname.md deleted file mode 100644 index 2bc17a1..0000000 --- a/doc/spec/fs_dirname.md +++ /dev/null @@ -1,41 +0,0 @@ -fs_dirname -========== - -Extracts the directory name portion of a path and prints it out. - - dirname filename - | | - __________________ ____________ - /some/example/path/to/this_is_a_file - -Sample Usage ------------- - -Extracting the directory name `foo` from the `foo/bar` path: - -```console test -$ ./lib/dev fs_dirname foo/bar -foo -``` - -Dealing with file names: - -```console test -$ ./lib/dev fs_dirname foo/bar/baz -foo/bar -$ ./lib/dev fs_dirname foo/bar/baz.lorem -foo/bar -$ ./lib/dev fs_dirname foo/bar/.ipsum -foo/bar -``` - -Dealing with multiple and absolute slashes: - -```console test -$ ./lib/dev fs_dirname foo/bar//.ipsum -foo/bar -$ ./lib/dev fs_dirname foo/bar/////.ipsum -foo/bar -$ ./lib/dev fs_dirname /some/example/path/to/this_is_a_file -/some/example/path/to -``` diff --git a/doc/spec/fs_path.md b/doc/spec/fs_path.md deleted file mode 100644 index 543e0c2..0000000 --- a/doc/spec/fs_path.md +++ /dev/null @@ -1,52 +0,0 @@ -fs_path -======= - -Finds files in a list of paths. Useful for when you want to look for -executables without changing the $PATH and a replacement for `which` -in many cases. - -Sample Usage ------------- - -Simplest usage emulates the built-in `$PATH` lookup: - -```console task -$ ./lib/dev fs_path sh -/bin/sh -$ ./lib/dev fs_path cp -/bin/cp -``` - -You can also provide your own path for lookup, using `:` as path -separator: - -```console task -$ ./lib/dev fs_path sh '/bin:/usr/bin' -/bin/sh -``` - -Specification -------------- - -First, let's create some folders and files to test our paths: - -```console test -$ mkdir some_dir -$ printf 'Some content...' > ./some_dir/SOME_FILE.txt -``` - -Using the current folder as path should work, in case you want to use -a variable and leave '.' as a default. - -```console test -$ printf '' > ./SOME_FILE.txt -$ ./lib/dev fs_path SOME_FILE.txt . -./SOME_FILE.txt -``` - -We can then use the fs_path function to look for files there: - -```console test -$ ./lib/dev fs_path SOME_FILE.txt "./some_dir" -./some_dir/SOME_FILE.txt -``` diff --git a/doc/spec/fs_tempdir.md b/doc/spec/fs_tempdir.md deleted file mode 100644 index 8c0bb72..0000000 --- a/doc/spec/fs_tempdir.md +++ /dev/null @@ -1,60 +0,0 @@ -fs_tempdir -========== - -Creates temporary directories with a partial random name, useful for -automated scripts and a safe replacement for `mktemp -d` - -Sample Usage ------------- - -You can use a default prefix (`tempdir.sh`): - -```console task -$ ./lib/dev fs_tempdir -/tmp/tempdir.sh.e8UKrp -``` - -...or provide your own prefix: - -```console task -$ ./lib/dev fs_tempdir 'myprefix' -/tmp/myprefix.ropAjR -``` - -You might want to store the path in a variable: - -```console task -$ my_temp="$(./lib/dev fs_tempdir 'myprefix')" -$ printf '%s\n' "${my_temp}" -/tmp/myprefix.fopyoK -``` - -Specification ------------- - -Simplest usage generates a temporary directory with a random name: - -```console test -$ my_tempdir="$(./lib/dev fs_tempdir)" -$ test -n "${my_tempdir}" || echo 'Fail' -$ rm -r "${my_tempdir}" -``` - -You can use a prefix name for your directory: - -```console test -$ ./lib/dev fs_tempdir 'zoid' > my_tempdir_path.txt -$ my_tempdir="$(./lib/dev fs_lines my_tempdir_path.txt | sed -n '/zoid/p')" -$ test -n "${my_tempdir}" || echo 'Fail' -$ rm -r "${my_tempdir}" my_tempdir_path.txt -``` - -You should be able to create multiple distinct temporary directories. - -```console test -$ my_tempdir="$(./lib/dev fs_tempdir)" -$ other_tempdir="$(./lib/dev fs_tempdir)" -$ test "${my_tempdir}" != "${other_tempdir}" || echo 'Fail' -$ rm -r "${my_tempdir}" -$ rm -r "${other_tempdir}" -``` diff --git a/doc/spec/lib_dev.md b/doc/spec/lib_dev.md deleted file mode 100644 index 41fddfc..0000000 --- a/doc/spec/lib_dev.md +++ /dev/null @@ -1,47 +0,0 @@ -Building `coral` from scratch -============================= - -The `coral` repository does not contain a built version of any library. - -In order to work with the modules [pre-assembling](module_assemble.md), -we need to load them in a bootstrap environment. - -Introduction ------------- - -To make this easy, we provide the `./lib/dev` module. You can use it -to load any other module from the `coral/lib` repository: - -```console test -$ ./lib/dev fs_basename hello/world -world -``` - -It will also look for modules in whatever the current folder you are in, -allowing quick prototyping: - -```sh file example.sh -# example.sh - -example () -{ - echo hello -} -``` - -```console test -$ ./lib/dev example -hello -``` - -Building the Builder --------------------- - -Let's first build the `module_assemble` module itself. We can then use -it to assemble anything we want and run it independently from `./lib/dev`: - -```console test -$ ./lib/dev module_assemble module_assemble module_assemble -$ test -e module_assemble && echo Exists -Exists -``` diff --git a/doc/spec/math_random.md b/doc/spec/math_random.md deleted file mode 100644 index e89d0e4..0000000 --- a/doc/spec/math_random.md +++ /dev/null @@ -1,25 +0,0 @@ -math_random -=========== - -Generates random numbers. It will try different methods to supply a -portable implementation. - -Sample Usage ------------- - -Generating a random number with maximum 20 chars: - -```console task -$ ./lib/dev math_random -3827653 -``` - -Specification -------------- - -```console test -$ my_random="$(./lib/dev math_random)" -$ test -n "${my_random}" || echo 'Fail Empty number' -$ test "${my_random}" = "$(printf %s "${my_random}" | sed 's/[^0-9]//g')" -$ test $? = 0 || echo "Fail: non-numeric chars on ${my_random}" -``` diff --git a/doc/spec/module_assemble.md b/doc/spec/module_assemble.md deleted file mode 100644 index 2cf2e48..0000000 --- a/doc/spec/module_assemble.md +++ /dev/null @@ -1,36 +0,0 @@ -module_assemble -=============== - -Assembles modules and their dependencies into standalone files. - ---- - -Let's start first with a simple module: - -```sh file mymodule.sh -mymodule () -{ - echo 1 -} -``` - -The `module_assemble` function takes two arguments: an input module -name and an option output module name. - - -```console test -$ MODULE=mymodule -$ ./lib/dev module_assemble $MODULE $MODULE -$ echo $MODULE -mymodule -$ sh ./mymodule -1 -``` - -If no output is provided, the assembled module will be printed out. - -```console task -$ MODULE=mymodule -$ assemble_path=: ./lib/dev module_assemble $MODULE | sh -1 -``` diff --git a/doc/spec/require.md b/doc/spec/require.md deleted file mode 100644 index bf22aa8..0000000 --- a/doc/spec/require.md +++ /dev/null @@ -1,269 +0,0 @@ -require -======= - -A modern module loader for many shell script interpreters. - ---- - -`require.sh` is a module runner that needs to be loaded within a -bootstrap. This is the bootstrap for testing it: - -```sh file require_test -#!/bin/sh - -entrypoint='require_test' -require_path="${require_path:-.}" - -require () -{ - require "${@:-}" -} - -require_test () -{ - local name="${1}" - shift - - require "${name}" - "${name%*.sh}" "${@:-}" -} - -# Require modules from the source folder -. 'lib/script/support' -. 'lib/require.sh' -. 'lib/script/entrypoint' - -``` - -The [lib_dev](lib_dev.md) module is also a `require.sh` bootstrap. - -Now we define some module. First a standalone one: - -```sh file hello.sh - -hello () -{ - echo hello -} -``` - -We make `world.sh` depend on `hello.sh`: - -```sh file world.sh - -require 'hello.sh' - -world () -{ - hello - echo world -} - -``` - -Running it should show messages in order as modules are executed: - -```console test -$ sh ./require_test world.sh -hello -world -``` - -### Self-references - -We can create a module that contains self-references and `require.sh` -will know how to ignore it: - -```sh file loop.sh - -require 'loop.sh' - -loop () -{ - echo loop -} -``` - -Running it will require the module a single time: - -```console test -$ sh ./require_test loop.sh -loop -``` - -### Circular references - -The same applies for circular references, the chain will execute only -a single time from the first time the module appears: - -```sh file lorem.sh - -require 'ipsum.sh' - -lorem () -{ - echo lorem -} -``` - -```sh file ipsum.sh - -require 'lorem.sh' - -ipsum () -{ - lorem - echo ipsum -} - -``` - -```console test -$ sh ./require_test ipsum.sh -lorem -ipsum -``` - -### Hooks - -Other modules might change the behavior of `require.sh`. This is the -case of [module_assemble](module_assemble.md). - -These hooks need to attend some expectations from the require module: - -```sh file hooks.sh - -hook_on_request () -{ - echo 'Request:' "${1:-}" 1>&2 - require_on_request "${1:-}" -} -hook_on_search () -{ - echo 'Search:' "${1:-}" 1>&2 - require_on_search "${1:-}" -} -hook_on_include () -{ - echo 'Include:' "${1:-}" 1>&2 - require_on_include "${1:-}" -} - -hooks () -{ - export require_on_request='hook_on_request' - export require_on_search='hook_on_search' - export require_on_include='hook_on_include' - export require_loaded='' - - require 'world.sh' -} - -``` - -These sample hooks only log what has been loaded: - -```console test -$ sh ./require_test hooks.sh -Request: world.sh -Search: world.sh -Include: ./world.sh -Request: hello.sh -Search: hello.sh -Include: ./hello.sh -``` - -### Advanced Hooks - -You might use hooks to emulate the presence of modules that don't exist -or manipulate their lookup flow: - -```sh file hooks.sh - -hook_on_request () -{ - echo 'Request:' "${1:-}" 1>&2 - test "${1:-}" = "never_finds.sh" && return 0 - test "${1:-}" = "always_requests.sh" && return 1 - require_on_request "${1:-}" -} -hook_on_search () -{ - echo 'Search:' "${1:-}" 1>&2 - test "${1:-}" = "always_errors.sh" && return 0 - require_on_search "${1:-}" -} -hook_on_include () -{ - test "${1:-}" = "never_includes.sh" && return 0 - echo 'Include:' "${1:-}" 1>&2 - require_on_include "${1:-}" -} - -hooks () -{ - local require_on_request='hook_on_request' - local require_on_search='hook_on_search' - local require_on_include='hook_on_include' - local require_loaded='' - - require 'always_requests.sh' - require 'always_requests.sh' - require 'never_finds.sh' - require 'never_includes.sh' - require 'always_errors.sh' - require 'world.sh' -} - -``` - -```console test -$ printf '' > always_requests.sh -$ printf '' > never_finds.sh -$ printf '' > always_errors.sh -$ printf '' > never_includes.sh -$ sh ./require_test hooks.sh -Request: always_requests.sh -Search: always_requests.sh -Include: ./always_requests.sh -Request: always_requests.sh -Search: always_requests.sh -Include: ./always_requests.sh -Request: never_finds.sh -Request: never_includes.sh -Search: never_includes.sh -Include: ./never_includes.sh -Request: always_errors.sh -Search: always_errors.sh -Could not find dependency 'always_errors.sh' -``` - -### Paths - -By default, `require.sh` should use the `$require_path` var to look for -modules. - -```sh file path1/hey.sh - -hey () -{ - echo hey -} -``` - -```sh file path2/friend.sh - -require 'hey.sh' - -friend () -{ - hey - echo friend -} -``` - -```console test -$ require_path=path1:path2:lib sh ./require_test friend.sh -hey -friend -``` diff --git a/doc/spec/script_entrypoint.md b/doc/spec/script_entrypoint.md deleted file mode 100644 index ad90396..0000000 --- a/doc/spec/script_entrypoint.md +++ /dev/null @@ -1,18 +0,0 @@ -script_entrypoint -================= - -The entrypoint is a simple shell script that runs the command stored -in the `entrypoint` variable passing along all arguments. - -Here's a sample using `echo` as an entrypoint: - -```console test -$ entrypoint=echo ${SHELL} ./lib/script/entrypoint Hello World -Hello World -``` - -If no entrypoint is provided, it does nothing: - -```console test -$ entrypoint='' ${SHELL} ./lib/script/entrypoint Hello -``` diff --git a/doc/spec/script_support.md b/doc/spec/script_support.md deleted file mode 100644 index 6e4dfc6..0000000 --- a/doc/spec/script_support.md +++ /dev/null @@ -1,124 +0,0 @@ -script/support -============== - -It contains settings to make all shells behave similarly. - -### Bail on errors - -The module will configure any shell to exit if a command returns an untreated -error code. - -```sh file bail_erros.sh - -. "./lib/script/support" - -# This command should fail before echo is sent -false - -echo 'This is never shown' -``` - - -```console test -$ sh ./bail_erros.sh -$ echo $? -1 -``` - -### Bail on undefined vars - -Using undefined variables will also lead to an error. - -```sh file bail_erros.sh - -. "./lib/script/support" - -# This command should fail before echo is sent -echo $zoid -echo 'This is never shown' -``` - - -```console test -$ sh ./bail_erros.sh > out 2>&1 -$ test $? = 0 -$ echo $? -1 -``` - -### Support for local variables on ksh and yash - -Both ksh and yash does not have the `local` keyword, which is emulated by -aliasing it to `typeset`. - -```sh file local_vars.sh - -. "./lib/script/support" - -display_local_var () -{ - local myvar=123 - - echo $myvar -} - -display_local_var -``` - -```console test -$ sh ./local_vars.sh -123 -``` - -### Do not expand globs - -The module ensures no glob patterns are expanded acidentally. - -```sh file expand_glob.sh - -. "./lib/script/support" - -do_not_expand_glob () -{ - echo foo/* -} - -do_not_expand_glob -``` - - -```console test -$ mkdir -p foo/bar -$ mkdir -p foo/baz -$ mkdir -p foo/bat -$ sh ./expand_glob.sh -foo/* -``` - -### Split words on for statements - -Some shells do not split words without extra configuration, the `support` -module configures this automatically. - -```sh file split_words.sh - -. "./lib/script/support" - -do_split_words () -{ - for word in here are some words - do - echo $word - done -} - -do_split_words -``` - -```console test -$ sh ./split_words.sh -here -are -some -words -``` diff --git a/doc/spec/shell_assertion.md b/doc/spec/shell_assertion.md deleted file mode 100644 index 2b95d84..0000000 --- a/doc/spec/shell_assertion.md +++ /dev/null @@ -1,34 +0,0 @@ -shell_assertion -=============== - -Runs a console session and verifies if the output matches it. - -For example, you might have a session saved as `session.txt`: - -```txt file session.txt -$ echo 1 -1 -``` - -Then you can run it using `shell_assertion`: - -```console test -$ ./lib/dev shell_assertion session.txt - $ echo 1 - 1 -``` - -The session will be replayed in cased of success. In case of any error, -a diff will be presented, as in the `errordiff.txt` file: - -```txt file errordiff.txt -$ echo 1 -Not 1 -``` - -```console test -$ ./lib/dev shell_assertion errordiff.txt - $ echo 1 -+ 1 -- Not 1 -``` diff --git a/doc/spec/spec_doc.md b/doc/spec/spec_doc.md deleted file mode 100644 index 943ed5b..0000000 --- a/doc/spec/spec_doc.md +++ /dev/null @@ -1,194 +0,0 @@ -spec_doc -======== - -It runs Markdown specifications and reports their results as [TAP][TAP]. -It has only three features: - - - **File Mocking**: To allow placing stubs and mock files during the runs. - - **Console Simulation**: To emulate and verify console interactions. - - **Setup Code**: To inject functions or variables in test sessions. - -All other functionality relies on Markdown and Shell Script themselves. - ---- - -A specification is a program written in [Markdown][MD]. [Code blocks][CB] are -used to include formal code examples. - -The command will run these files as a test suite, using blocks -with special [info strings][IS] to play different roles in the test suite. - -Normal Blocks -------------- - -Standard blocks are ignored. - - ``` - This is a block, but has no info string. It will be ignored by coral spec. - ``` - -Language blocks are also ignored. - - ```sh - This is a shell block, but language alone is not enough to trigger a role. - ``` - -Mocking files -------------- - -You can simulate files present in the disk during the time of the test -by using the `file` infostring: - - - ```txt file example.txt - A "file" block will save that file in the working directory when running - tests. - ``` - -These files will be removed automatically after the test is done (at -the end of the document). - -Simulating Console Interactions -------------------------------- - -You can use `console test` infostrings to simulate console interactions. -It checks if the real output matches the expected in the specification. - - ```console test - $ echo 'A console test will run commands and check their outputs.' - A console test will run commands and check their outputs. - ``` - - ```console test - $ echo 1 - This would fail. This is not the expected output for `echo 1` - ``` - -All console interactions should support variables persistency: - - ```console test - $ foo=bar - ``` - - Then something... - - ```console test - $ echo $foo - bar - ``` - -Return codes are also preserved, allowing a fluent writing style: - - ```console test - $ do_something - ``` - - We will check if it worked.... - - ```console test - $ echo $? - 0 - ``` - -Ignoring Output ---------------- - -If you want to run a task, check if it has returned a success code -but ignore its output, you can use the `console task` infostring: - - ```console task - $ echo 'A console task ignores output' - This will not fail! - ``` - -Of course you can also direct it to `/dev/null`, but the main difference -is that when some `task` fails, the error will still present the failed -output. - -Setup Code ----------- - -A setup block can be used to declare functions, define variables or load -scripts. - - ```sh setup - echo 'This will run before each console execution' - ``` - -Running Tests -------------- - -You can either use a compiled version or load it using `./lib/dev`: - -```console task -$ ./lib/dev spec_doc doc/spec/fs_basename.md -# using 'sh' -# -# file 'doc/spec/fs_basename.md' -ok 1 - ./lib/dev fs_basename foo/bar -ok 2 - ./lib/dev fs_basename foo/bar/baz -ok 3 - ./lib/dev fs_basename foo/bar/baz.lorem -ok 4 - ./lib/dev fs_basename foo/bar/.ipsum -ok 5 - ./lib/dev fs_basename foo/bar//.ipsum -ok 6 - ./lib/dev fs_basename foo/bar/////.ipsum -ok 7 - ./lib/dev fs_basename /some/example/path/to/this_is_a_file -# SUCCESS -1..8 -``` -Multiple files can be fed to the command, which also will make the -TAP test counter consider all of them: - -```console task -$ ./lib/dev spec_doc doc/spec/fs_basename.md doc/spec/fs_dirname.md -# using 'sh' -# -# file 'doc/spec/fs_basename.md' -ok 1 - ./lib/dev fs_basename foo/bar -ok 2 - ./lib/dev fs_basename foo/bar/baz -ok 3 - ./lib/dev fs_basename foo/bar/baz.lorem -ok 4 - ./lib/dev fs_basename foo/bar/.ipsum -ok 5 - ./lib/dev fs_basename foo/bar//.ipsum -ok 6 - ./lib/dev fs_basename foo/bar/////.ipsum -ok 7 - ./lib/dev fs_basename /some/example/path/to/this_is_a_file -# file 'doc/spec/fs_dirname.md' -ok 8 - ./lib/dev fs_dirname foo/bar -ok 9 - ./lib/dev fs_dirname foo/bar/baz -ok 10 - ./lib/dev fs_dirname foo/bar/baz.lorem -ok 11 - ./lib/dev fs_dirname foo/bar/.ipsum -ok 12 - ./lib/dev fs_dirname foo/bar//.ipsum -ok 13 - ./lib/dev fs_dirname foo/bar/////.ipsum -ok 14 - ./lib/dev fs_dirname /some/example/path/to/this_is_a_file -# SUCCESS -1..15 -``` - -The output is TAP-compliant and can be fed to any harness system -directly or saved to a file. - -Failures will include a diff of the output that didn't matched: - -```console -$ ./lib/dev spec_doc doc/spec/fs_basename.md -# using 'sh' -# -# file 'doc/spec/fs_basename.md' -ok 1 - ./lib/dev fs_basename foo/bar -not ok 2 - ./lib/dev fs_basename foo/bar/baz -# Failure on doc/spec/fs_basename.md line 13 -1c1 -< ba ---- -> baz -ok 3 - ./lib/dev fs_basename foo/bar/baz.lorem -ok 4 - ./lib/dev fs_basename foo/bar/.ipsum -ok 5 - ./lib/dev fs_basename foo/bar//.ipsum -ok 6 - ./lib/dev fs_basename foo/bar/////.ipsum -ok 7 - ./lib/dev fs_basename /some/example/path/to/this_is_a_file -# FAILURE (1 of 8 assertions failed) -1..8 -``` - -[MD]: http://commonmark.org/help/ -[CB]: http://commonmark.org/help/ -[IS]: http://spec.commonmark.org/0.12/#info-string -[TAP]: https://testanything.org/tap-specification.html diff --git a/doc/test/common.md b/doc/test/common.md deleted file mode 100644 index 2821cdf..0000000 --- a/doc/test/common.md +++ /dev/null @@ -1,365 +0,0 @@ -🐚 coral compatibility -====================== - -This document describes how to run the `coral` test suite across several -different environment targets. - -Building --------- - -First we build the `spec_doc` executable from the library: - -```console task -$ ./lib/dev module_assemble spec_doc spec_doc -``` - -Basic Run ---------- - -The basic test suite should run all document and library tests. We run -it on the host machine to ensure the tests are properly working. - -```console task -$ sh ./spec_doc $(find doc/spec/*) -``` - -Testing Other Shells --------------------- - -Each other environment will be tested inside a Docker container for -better isolation. - -For that, we'll need a `Dockerfile` - -```Dockerfile file Dockerfile -FROM ubuntu:trusty - -ARG APT -ENV APT $APT - -RUN ${APT:+apt-get -y update} -RUN ${APT:+apt-get -y dist-upgrade} - -ARG PPA -ENV PPA $PPA - -RUN ${PPA:+apt-get -y install software-properties-common} -RUN ${PPA:+add-apt-repository --enable-source -y ppa:$PPA} -RUN ${PPA:+apt-get -y update} -RUN ${APT:+apt-get -y install $APT} - -RUN mkdir -p /usr/local/share/coral -COPY [".", "/usr/local/share/coral"] -WORKDIR /usr/local/share/coral -``` - -This image will allow us to install multiple shells to test their -compatibility: - - -### Default Ubuntu dash - -```console task -$ docker build --build-arg "TARGET=sh" -t dash . -$ docker run --rm -e spec_doc_shell="dash" -t dash dash -c 'dash ./spec_doc $(find doc/spec/*)' -``` - -### Default Ubuntu bash - -```console task -$ docker build --build-arg "TARGET=bash" -t bash . -$ docker run --rm -e spec_doc_shell="bash" -t bash bash -c 'bash ./spec_doc $(find doc/spec/*)' -``` - -### Default Ubuntu yash - -```console task -$ docker build --build-arg APT="yash" -t yash . -$ docker run --rm -e spec_doc_shell="yash" -t yash yash -c 'yash ./spec_doc $(find doc/spec/*)' -``` - -### Default Ubuntu posh - -```console task -$ docker build --build-arg APT="posh" -t posh . -$ docker run --rm -e spec_doc_shell="posh" -t posh posh -c 'posh ./spec_doc $(find doc/spec/*)' -``` - -### Default Ubuntu zsh - -```console task -$ docker build --build-arg APT="zsh" -t zsh . -$ docker run --rm -e spec_doc_shell="zsh" -t zsh zsh -c 'zsh ./spec_doc $(find doc/spec/*)' -``` - -### Default Ubuntu mksh - -```console task -$ docker build --build-arg APT="mksh" -t mksh . -$ docker run --rm -e spec_doc_shell="mksh" -t mksh mksh -c 'mksh ./spec_doc $(find doc/spec/*)' -``` - -### Default Ubuntu pdksh - -```console task -$ docker build --build-arg APT="pdksh" -t pdksh . -$ docker run --rm -e spec_doc_shell="pdksh" -t pdksh pdksh -c 'pdksh ./spec_doc $(find doc/spec/*)' -``` - -### Default Ubuntu sash - -```console task -$ docker build --build-arg APT="sash" -t sash . -$ docker run --rm -e spec_doc_shell="sash" -t sash sash -c 'sash ./spec_doc $(find doc/spec/*)' -``` - -### Default Ubuntu ksh - -```console task -$ docker build --build-arg APT="ksh" -t ksh . -$ docker run --rm -e spec_doc_shell="ksh" -t ksh ksh -c 'ksh ./spec_doc $(find doc/spec/*)' -``` - -### Default Ubuntu zsh-beta - -```console task -$ docker build --build-arg APT="zsh-beta" -t zsh-beta . -$ docker run --rm -e spec_doc_shell="zsh-beta" -t zsh-beta zsh-beta -c 'zsh-beta ./spec_doc $(find doc/spec/*)' -``` - -### Default Ubuntu busybox - -```console task -$ docker build --build-arg APT="busybox" -t busybox . -$ docker run --rm -e spec_doc_shell="busybox sh" -t busybox busybox sh -c 'busybox sh ./spec_doc $(find doc/spec/*)' -``` - -### Bash 2.05b - -```console task -$ docker build --build-arg "PPA=team-mayhem/multishell" --build-arg "APT=bash-2.05b.13" -t bash2 . -$ docker run --rm -e spec_doc_shell="bash-2.05b.13" -t bash2 bash-2.05b.13 -c 'bash-2.05b.13 ./spec_doc $(find doc/spec/*)' -``` - -### Bash 3.0 - -```console task -$ docker build --build-arg "PPA=team-mayhem/multishell" --build-arg "APT=bash-3.0.16" -t bash3 . -$ docker run --rm -e spec_doc_shell="bash-3.0.16" -t bash3 bash-3.0.16 -c 'bash-3.0.16 ./spec_doc $(find doc/spec/*)' -``` - -### Bash 3.1 - -```console task -$ docker build --build-arg "PPA=team-mayhem/multishell" --build-arg "APT=bash-3.1.9" -t bash31 . -$ docker run --rm -e spec_doc_shell="bash-3.1.9" -t bash31 bash-3.1.9 -c 'bash-3.1.9 ./spec_doc $(find doc/spec/*)' -``` - -### Bash 3.2 - -```console task -$ docker build --build-arg "PPA=team-mayhem/multishell" --build-arg "APT=bash-3.2.9" -t bash32 . -$ docker run --rm -e spec_doc_shell="bash-3.2.9" -t bash32 bash-3.2.9 -c 'bash-3.2.9 ./spec_doc $(find doc/spec/*)' -``` - -### Bash 4.0 - -This version is unsupported! - -### Bash 4.1 - -```console task -$ docker build --build-arg "PPA=team-mayhem/multishell" --build-arg "APT=bash-4.1.9" -t bash41 . -$ docker run --rm -e spec_doc_shell="bash-4.1.9" -t bash41 bash-4.1.9 -c 'bash-4.1.9 ./spec_doc $(find doc/spec/*)' -``` - -### Bash 4.2 - -```console task -$ docker build --build-arg "PPA=team-mayhem/multishell" --build-arg "APT=bash-4.2.53" -t bash42 . -$ docker run --rm -e spec_doc_shell="bash-4.2.53" -t bash42 bash-4.2.53 -c 'bash-4.2.53 ./spec_doc $(find doc/spec/*)' -``` - -### Bash 4.3 - -```console task -$ docker build --build-arg "PPA=team-mayhem/multishell" --build-arg "APT=bash-4.3.9" -t bash43 . -$ docker run --rm -e spec_doc_shell="bash-4.3.9" -t bash43 bash-4.3.9 -c 'bash-4.3.9 ./spec_doc $(find doc/spec/*)' -``` - -Default Shells From Common Images ---------------------------------- - -To ensure no surprises, `coral` is automatically tested targeting the default shell -implementations of multiple popular images. - -### Default Busybox - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" busybox:latest sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -### Default Alpine sh - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" alpine:latest sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" alpine:edge sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" alpine:3.6 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" alpine:3.5 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" alpine:3.4 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" alpine:3.3 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" alpine:3.2 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" alpine:3.1 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -### Default Amazon Linux sh - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" amazonlinux:latest sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" amazonlinux:2017.12 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" amazonlinux:2017.09 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" amazonlinux:2017.03 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" amazonlinux:2016.09 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -### Default CentOS sh - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" centos:latest sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" centos:7.4.1708 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" centos:7.3.1611 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" centos:7.2.1511 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" centos:7.1.1503 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" centos:7.0.1406 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" centos:6 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -### Default Fedora sh - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" fedora:latest sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" fedora:27 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" fedora:26 sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -### Default Debian sh - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" debian:latest sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" debian:buster sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" debian:jessie sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" debian:sid sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" debian:stable sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" debian:testing sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" debian:unstable sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -### Default Arch sh - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" base/archlinux:latest sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" archlinux/base:latest sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -### Default OpenSuse sh - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" opensuse:latest sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - - -### Default Ubuntu sh - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" ubuntu:latest sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" ubuntu:artful sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" ubuntu:xenial sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -```console task -$ docker run --rm -v "$(pwd):/coral" -w "/coral" -e spec_doc_shell="sh" ubuntu:trusty sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` diff --git a/doc/test/extreme.md b/doc/test/extreme.md deleted file mode 100644 index 4f698aa..0000000 --- a/doc/test/extreme.md +++ /dev/null @@ -1,158 +0,0 @@ -Experimental Shells -------------------- - -To test `coral` on experimental shells, let's first build the testing -library using the host shell. - -```console task -$ ./lib/dev module_assemble spec_doc spec_doc -``` - -### Minimal Linux - -First, let's create a build environment: - -```Dockerfile file Dockerfile.builder -FROM alpine:latest - -RUN apk add --no-cache \ - bzip2 \ - coreutils \ - curl \ - gcc \ - linux-headers \ - make \ - musl-dev \ - tzdata - -ENV BUSYBOX_VERSION 1.27.2 - -RUN set -ex; \ - tarball="busybox-${BUSYBOX_VERSION}.tar.bz2"; \ - curl -fL -o busybox.tar.bz2 "https://busybox.net/downloads/$tarball"; \ - mkdir -p /usr/src/busybox; \ - tar -xf busybox.tar.bz2 -C /usr/src/busybox --strip-components 1; \ - rm busybox.tar.bz2* - -WORKDIR /usr/src/busybox - -# https://www.mail-archive.com/toybox@lists.landley.net/msg02528.html -# https://www.mail-archive.com/toybox@lists.landley.net/msg02526.html -RUN sed -i 's/^struct kconf_id \*$/static &/g' scripts/kconfig/zconf.hash.c_shipped - -RUN set -ex; \ - \ - setConfs=' \ - CONFIG_LAST_SUPPORTED_WCHAR=0 \ - CONFIG_STATIC=y \ - CONFIG_BUSYBOX=y \ - CONFIG_FEATURE_INSTALLER=y \ - CONFIG_COMM=y \ - CONFIG_CP=y \ - CONFIG_FIND=y \ - CONFIG_MKDIR=y \ - CONFIG_MV=y \ - CONFIG_RM=y \ - CONFIG_SED=y \ - CONFIG_SORT=y \ - CONFIG_SH_IS_ASH=y \ - CONFIG_ASH=y \ - CONFIG_BASH_IS_NONE=y \ - CONFIG_ASH_OPTIMIZE_FOR_SIZE=y \ - CONFIG_ASH_ECHO=y \ - CONFIG_ASH_RANDOM_SUPPORT=y \ - CONFIG_ASH_TEST=y \ - CONFIG_ASH_PRINTF=y \ - CONFIG_FEATURE_SH_MATH=y \ - CONFIG_FEATURE_SH_EXTRA_QUIET=y \ - '; \ - \ - unsetConfs=' \ - CONFIG_LONG_OPTS \ - '; \ - \ - make allnoconfig; \ - \ - for conf in $unsetConfs; do \ - sed -i \ - -e "s!^$conf=.*\$!# $conf is not set!" \ - .config; \ - done; \ - \ - for confV in $setConfs; do \ - conf="${confV%=*}"; \ - sed -i \ - -e "s!^$conf=.*\$!$confV!" \ - -e "s!^# $conf is not set\$!$confV!" \ - .config; \ - if ! grep -q "^$confV\$" .config; then \ - echo "$confV" >> .config; \ - fi; \ - done; \ - \ - make oldconfig; \ - \ -# trust, but verify - for conf in $unsetConfs; do \ - ! grep -q "^$conf=" .config; \ - done; \ - for confV in $setConfs; do \ - grep -q "^$confV\$" .config; \ - done; \ - cat .config - -RUN set -ex \ - && make -j "$(nproc)" \ - busybox \ - && ./busybox \ - && mkdir -p rootfs/bin \ - && ln -vL busybox rootfs/bin/ \ - && chroot rootfs /bin/busybox --install /bin \ - && find rootfs - -# create /tmp -RUN mkdir -p rootfs/tmp \ - && chmod 1777 rootfs/tmp - -# test and make sure it works -RUN chroot rootfs /bin/sh -xc 'busybox' -``` - -Now we create a container just with busybox: - -```Dockerfile file Dockerfile -FROM scratch -ADD "busybox.tar.bz2" / -CMD ["sh"] -``` - -And we finally can run the tests on a minimal linux: - -```console task -$ docker build -t coral-builder -f Dockerfile.builder . -$ docker run --rm "coral-builder" tar cC rootfs . | bzip2 > "busybox.tar.bz2" -$ docker build -t "coral" . -$ docker run --rm -v "$(pwd):/coral" -w "/coral" coral sh -c 'sh ./spec_doc $(find doc/spec/*)' -``` - -Oil Shell ---------- - -First we create a container with a compiled oil: - -```Dockerfile file Dockerfile.oil -FROM ubuntu:latest - -RUN apt-get update -y -RUN apt-get install -y build-essential curl unzip python-dev gawk time libreadline-dev -RUN curl -fL -o oil.zip https://github.com/oilshell/oil/archive/master.zip -RUN unzip oil.zip -d / -RUN cd /oil-master && mkdir -p /oil-master/_tmp && bash /oil-master/build/dev.sh minimal -``` - -Now we can test it: - -```console task -$ docker build -t osh -f Dockerfile.oil . -$ docker run --rm -v "$(pwd):/coral" -w "/coral" osh sh -c '/oil-master/bin/osh ./spec_doc $(find doc/spec/*)' -``` diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..a4d5255 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,14 @@ +# Copyright (c) Alexandre Gomes Gaigalas +# SPDX-License-Identifier: ISC + +__OPT__="${-:-}" +__PAT__="$PATH" +__PWD__="$PWD" +__SLF__="$PWD/$0" +__LIB__="${__SLF__%\/*}" + +. "${__LIB__}/idiom/compat.sh" # Most primitive shims and checks. This must be first. +. "${__LIB__}/idiom/data.sh" # Pseudo-datastructures. +. "${__LIB__}/idiom/require.sh" # Module loader. +. "${__LIB__}/idiom/wrapper.sh" # Actually runs the thing. + diff --git a/idiom/compat.sh b/idiom/compat.sh new file mode 100644 index 0000000..181a3f4 --- /dev/null +++ b/idiom/compat.sh @@ -0,0 +1,78 @@ +# Copyright (c) Alexandre Gomes Gaigalas +# SPDX-License-Identifier: ISC + +# Characters that are weird to represent should use these variables. +__TAB__=" " +__EOL__=" +" + + +LC_ALL=C # Not all shells support modern charsets, we'll deal with it later. +IFS=$__EOL__ # Don't break my variables by spaces +PATH="" # We don't need other programs + +_quiet () { "$@" >/dev/null 2>&1; } # Runs with no stdout/stderr +_noerr () { "$@" 2>/dev/null || :; } # Runs and ignores failures and stderr +_reply () { eval "REPLY='${*:-}'"; } # Feeds a reply +_has () { _quiet command -v "$1"; } # Checks if a command is available + +set -euf + +# Feature-detects glob stuff +if _quiet setopt sh_word_split no_glob no_multios ignore_braces +then _glob () { setopt glob glob_subst; _reply $@; unsetopt glob; } +else _glob () { set +f; _reply $@; set -f; } +fi + +# Feature-detects printing functions +REPLY="$( + _noerr printf %s%b "\\061 " "\\062 " + _noerr echo -n -E "\\063 " +)" + +case "$REPLY" in +"\\061 2 "*) + _print () { printf %b "${*:-}"; } + _write () { printf %s "${*:-}"; } + ;; +"\\063 ") + _print () { echo -n -e "${*:-}"; } + _write () { echo -n -E "${*:-}"; } + ;; +*) + _print () { echo -n "${*:-}"; } + # Only posh will use this, as it doesn't have a non-escaping builtin echo + _write () { + set -- "${*:-}" + IFS='\' + set -- $1 + IFS=$__EOL__ + echo -n "$1" + shift + while test $# -gt 0 + do + echo -n "\\\\$1" + shift + done + } + ;; +esac + +# Escapes multiple lines into a single one with \n in between +_inline () { + set -- ${*:-} + test $# -gt 0 || return 0; + while test $# -gt 1 + do + _write "$1\\n" + shift + done + _write "$1${__EOL__}" +} + +# Shim for local +if _has alias && _has typeset +then alias local=typeset +fi + +REPLY= diff --git a/idiom/data.sh b/idiom/data.sh new file mode 100644 index 0000000..7dc2343 --- /dev/null +++ b/idiom/data.sh @@ -0,0 +1,127 @@ +# Copyright (c) Alexandre Gomes Gaigalas +# SPDX-License-Identifier: ISC + +# Pseudo Data Structures + +_S=0 _L=0 _S=0 _A=0 # Some counters for Str, Lst, Set and Arr + +# Evaluates an expression within brackets +exp () { + local _t= + local _e= + + case $# in + 0) Str '' ; return ;; + 1) Str $1 ; return ;; + 2) ${@:-} ; return ;; + esac + + set -- \: "$@" + for _t in "$@" + do + case "$_t" in + \:) + shift $# + ;; + \[) + set -- "$_e" "${@:-}" + _e= + ;; + \]) + eval $_e + _e="${1:-}${1:+ }$_R" + shift + ;; + _[SLSA]*) + _e="${_e:-}${_e:+ }$_t" + ;; + [A-Z][a-z]*) + test -z "$_e" && + _e="${_e:-}${_e:+ }$_t" || + eval "_e=\"\${_e:-}\${_e:+ }\$$_t\"" + ;; + *) + ERROR $_t + ;; + esac + done + case $_e in + _[SLSA]*) _R=$_e ;; + *) eval $_e ;; + esac +} + +# Evaluates an expression and assigns the result to a variable +val () { + local _n=$1 _o=$2 + shift 2 + exp ${@:-} + case $_o in + \=) eval $_n=\$_R ;; + esac +} + +# The Str pseudotype constructor +Str () { + _S=$((_S + 1)) + _R=_S$_S + eval "$_R=\${*:-}" +} + +Str_add () { + eval "$_R=\${*:-}" +} + +# The Lst pseudotype constructor +Lst () { + _L=$((_L + 1)) + _R=_L$_L + eval "$_R=\"\${$_R:-}\${*:-}\"" +} + +Lst_add () { + _R=$1 + shift + eval "$_R=\"\${$_R:-}\$*\"" +} + +# The Set pseudotype constructor +Set () { + _S=$((_S + 1)) + _R=_S$_S + eval "$_R=" + Set_add $_R "${@:-}" +} + +Set_add () { + _R=$1 + while test $# -gt 1 + do shift ; eval " + case \"\$$_R\$__EOL__\" in + *\"\$__EOL__\$1\$__EOL__\"*);; + *)$_R=\"\$$_R\$__EOL__\$1\";; + esac" + done +} + +# The Arr pseudotype constructor +Arr () { + _A=$((_A + 1)) + _R=_A$_A + eval "$_R=-1" + if test $# -gt 0 + then + eval "$_R=-1" + Arr_add $_R "$@" + else + eval "$_R=0" + fi +} + +Arr_add () { + _R=$1 + eval "$_R=$(($_R + $#))" + while test $# -gt 1 + do shift ; eval "${_R}i$(($_R - $#))=\$1" + done +} diff --git a/idiom/require.sh b/idiom/require.sh new file mode 100644 index 0000000..7b46ab7 --- /dev/null +++ b/idiom/require.sh @@ -0,0 +1,9 @@ +# Copyright (c) Alexandre Gomes Gaigalas +# SPDX-License-Identifier: ISC + +require () { + set -- "$1" "${1%%_*}" + set -- "$2" "${1#"${2}_"}" + . "${__LIB__}/module/$1/$2.sh" + REPLY="${1}_${2}" +} diff --git a/idiom/wrapper.sh b/idiom/wrapper.sh new file mode 100644 index 0000000..c44b509 --- /dev/null +++ b/idiom/wrapper.sh @@ -0,0 +1,22 @@ +# Copyright (c) Alexandre Gomes Gaigalas +# SPDX-License-Identifier: ISC + +# Shim for local on ksh93 +if eval "REPLY=;_ltest(){ local REPLY=x;} 2>/dev/null" && test "$REPLY" = x +then + unset -f ltest + for __decl in $(typeset +f) + do + __name=${__decl%"()"} + __body="$(typeset -f "$__name" || :)" + eval "function $__name ${__body#"$__decl"}" + case $__OPT__ in *x*) typeset -fx $__name;; esac + done + unset __decl __name __body + REPLY= +fi + +test $# = 0 || require "$1" +shift +unset -f require +"${REPLY:-}" "${@:-}" diff --git a/lib/dev b/lib/dev deleted file mode 100755 index d49c880..0000000 --- a/lib/dev +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -## - # lib/dev - Bootstraps and runs modules - ## - -dev_library_dir="$(cd "${0%/*}";printf %s "${PWD}")" - -. "${dev_library_dir}/script/support" -. "${dev_library_dir}/require.sh" - -entrypoint="${1:-false}" -entrypoint="${entrypoint%.sh}" -dev_library_file="$(echo "${entrypoint}" | sed 's|_|/|g').sh" -module_get_folder="${dev_library_dir}" -require_path="${require_path:-.:${dev_library_dir}}" -shift - -require "${dev_library_file}" -require_loaded=' require.sh ' -. "${dev_library_dir}/script/entrypoint" diff --git a/lib/fs/basename.sh b/lib/fs/basename.sh deleted file mode 100644 index 70782bf..0000000 --- a/lib/fs/basename.sh +++ /dev/null @@ -1,10 +0,0 @@ -## - # fs_basename.sh - gets the name part of a path - ## - -fs_basename () -{ - # Prints out the first argument ignoring everything until the first - # slash '/' - echo "${1##*/}" -} diff --git a/lib/fs/dirname.sh b/lib/fs/dirname.sh deleted file mode 100644 index ee9afa7..0000000 --- a/lib/fs/dirname.sh +++ /dev/null @@ -1,16 +0,0 @@ -## - # fs_dirname.sh - gets the directory part of a path - ## - -require 'string/rtrim.sh' - -fs_dirname () -{ - local name - - name="${1}" - - # Prints out the first argument up until the first slash, trimming - # out any slashes on the right side right after - string_rtrim "/" "${name%/*}" -} diff --git a/lib/fs/lines.sh b/lib/fs/lines.sh deleted file mode 100644 index aae202c..0000000 --- a/lib/fs/lines.sh +++ /dev/null @@ -1,29 +0,0 @@ -## - # fs_lines.sh - writes contents of files to stdout - ## - -fs_lines () -{ - IFS= - - if test -e "${1:-}" - then - # Reading from file descriptor - fs_lines_loop < "${1}" - else - # Reading from stdin - fs_lines_loop - fi - - return 0 -} - -fs_lines_loop () -{ - local line - - while read -r line - do - printf '%s\n' "${line}" - done -} diff --git a/lib/fs/path.sh b/lib/fs/path.sh deleted file mode 100644 index 8e991a7..0000000 --- a/lib/fs/path.sh +++ /dev/null @@ -1,24 +0,0 @@ -## - # fs_path.sh - solves filesystem paths - ## - -fs_path () -{ - local IFS - local solved - local target_path - - target_path="${2:-${PATH:-}}" - IFS=':' - - for solved in ${target_path} - do - if test -f "${solved}/${1}" - then - printf '%s\n' "${solved}/${1}" - return - fi - done - - return 0 -} diff --git a/lib/fs/tempdir.sh b/lib/fs/tempdir.sh deleted file mode 100644 index dea9013..0000000 --- a/lib/fs/tempdir.sh +++ /dev/null @@ -1,24 +0,0 @@ -## - # fs_tempdir.sh - creates temporary directories - ## - -require 'math/random.sh' - -fs_tempdir () -{ - local prefix - local systmp - local tempdir - - prefix="${1:-tempdir.sh}" - systmp="${TMPDIR:-/tmp}" - tempdir="$(mktemp -d "${systmp}/${prefix}.XXXXXX" 2>/dev/null || :)" - - if test -z "${tempdir:-}" - then - tempdir="${systmp}/${prefix}.$(math_random)" - mkdir -m 'u+rwx' "${tempdir}" - fi - - printf '%s\n' "${tempdir}" -} diff --git a/lib/math/random.sh b/lib/math/random.sh deleted file mode 100644 index c1247c4..0000000 --- a/lib/math/random.sh +++ /dev/null @@ -1,29 +0,0 @@ -## - # math_random.sh - generates random numbers - ## - -require 'string/repeat.sh' - -math_random () -{ - # We are checking if random is empty here, so it is OK to use it. - #shellcheck disable=SC2039 - ( - echo ${RANDOM:-$(math_random_device)} - ) -} - -math_random_device () -{ - od -A n -t u1 -N100 '/dev/random' | { - local random_integers - read -r random_integers - printf %s "${random_integers}" | - sed " - s/[^0-9]//g - s/^0*// - s/^\($(string_repeat '.' 20)\).*$/\1/ - " - return 0 - } -} diff --git a/lib/module/assemble.sh b/lib/module/assemble.sh deleted file mode 100644 index 9a25a7b..0000000 --- a/lib/module/assemble.sh +++ /dev/null @@ -1,163 +0,0 @@ -## - # module_assemble.sh - bundles modules into standalone executables - ## - -require 'require.sh' -require 'script/entrypoint' -require 'script/support' -require 'fs/basename.sh' -require 'fs/tempdir.sh' -require 'fs/lines.sh' - -module_assemble () -{ - local assemble_input - local assemble_output - local assemble_dir - export require_on_include - export require_on_request - export require_loaded - - assemble_input="${1:-}" - assemble_output="${2:--}" - require_on_include='module_assemble_on_include' - require_on_request='module_assemble_on_request' - require_loaded=' ' - assemble_dir="$(fs_tempdir 'module_assemble')" - - trap 'module_assemble_exit' 2 - - printf '' > "${assemble_dir}/sources" - module_assemble_contents "${assemble_input}" > "${assemble_dir}/output" - - if test "-" = "${assemble_output}" - then - fs_lines "${assemble_dir}/output" - else - cp "${assemble_dir}/output" "${assemble_output}" - fi - - module_assemble_clean -} - -module_assemble_clean () -{ - rm -Rf "${assemble_dir}" - return 0 -} - -module_assemble_exit () -{ - rm -Rf "${assemble_dir}" - exit 1 -} - -module_assemble_contents () -{ - local input - local input_file - - input="${1:-}" - input_file="$(printf '%s\n' "${input}" | sed 's|_|/|g').sh" - - require_source 'script/support' - printf '%s\n' "entrypoint='${input}'" - module_assemble_dependencies "${input_file}" - require_source 'script/entrypoint' -} - -module_assemble_dependencies () -{ - local input_file - local require_sources - - input_file="${1:-}" - - printf '' > "${assemble_dir}/required_modules" - printf '' > "${assemble_dir}/required_calls" - printf '%s\n' 'require () ( : )' > "${assemble_dir}/require" - require "${input_file}" - - require_sources="$(fs_lines "${assemble_dir}/sources")" - - if test -n "${require_sources}" - then - require_source 'fs/lines.sh' >> "${assemble_dir}/required_modules" - fs_lines <<-SOURCES_SNIPPET >> "${assemble_dir}/required_modules" - require_source () - { - if test -z "\${1:-}" - then - return - ${require_sources} - else - fs_lines "\$(require_path "\${1}")" - fi - } - SOURCES_SNIPPET - fi - - printf '%s\n' "require_loaded='${require_loaded}'" - printf '%s\n' "require_path=\"${assemble_path:-${require_path:-}}\"" - - if require_is_loaded "require.sh" "" - then - printf '%s\n' "eval \"\$(require_source 'require.sh')\"" >> "${assemble_dir}/required_calls" - fi - - fs_lines "${assemble_dir}/require" - fs_lines "${assemble_dir}/required_modules" - fs_lines "${assemble_dir}/required_calls" -} - - -module_assemble_on_include () -{ - local script_target - local script_target_name - local assemble_dependency - local contents - - script_target="${1}" - assemble_dependency="${2:-}" - - script_target_name="$(fs_basename "${script_target}")" - #contents="$(fs_lines "${script_target}")" - - test "${script_target_name%*.sh}.sh" = "${script_target_name}" || return 0 - - if test "${assemble_dependency}" != "require.sh" && - test "${assemble_dependency}" != "fs/lines.sh" - then - #printf '%s\n' "${contents}" >> "${assemble_dir}/required_modules" - printf '%s\n' "eval \"\$(require_source '${assemble_dependency}')\"" >> "${assemble_dir}/required_calls" - require_on_include "${@:-}" - fi -} - - -module_assemble_on_request () -{ - local assemble_dependency - local remaining_params - - assemble_dependency="${1}" - shift - remaining_params="${*:-}" - - if true - then - fs_lines <<-SOURCES_SNIPPET >> "${assemble_dir}/sources" - elif test "\${1}" = "${assemble_dependency}" - then - fs_lines <<-'FILESOURCE_SNIPPET' - $( - require_source "${assemble_dependency}" | - sed 's/^./ &/'; - printf \\t\\t%s 'FILESOURCE_SNIPPET' - ) - SOURCES_SNIPPET - fi - - require_on_request "${assemble_dependency}" "${@:-}" -} diff --git a/lib/module/get.sh b/lib/module/get.sh deleted file mode 100644 index 00bb03e..0000000 --- a/lib/module/get.sh +++ /dev/null @@ -1,66 +0,0 @@ -## - # module_get.sh - Manages dependency modules - ## - -require 'require.sh' -require 'net/fetch.sh' -require 'fs/tempdir.sh' - -module_get () -{ - local repo - local module_get_channel - export require_on_search - export require_on_request - export require_on_include - local target - local target_file - - repo='https://raw.githubusercontent.com/alganet/coral/master/lib' - module_get_folder="${module_get_folder:-$(fs_tempdir module_get)}" - module_get_channel="${module_get_channel:-${repo}}" - require_on_search='module_get_on_search' - require_on_request='module_get_on_request' - require_on_include='module_get_on_include' - target="${1:-}" - shift - - target_file="$(printf '%s\n' "${target}" | sed 's|_|/|g').sh" - - require "${target_file}" - - "${target}" "${@:-}" -} - -module_get_on_request () -{ - if test "--channel" = "${1}" - then - module_get_channel="${3:-${repo}}" - return 0 - fi - - require_on_request "${@:-}" -} - -module_get_on_include () -{ - module_get_channel="${repo}" - require_on_include "${@:-}" -} - -module_get_on_search () -{ - local found - - found="$(require_on_search "${@:-}")" - - if test -z "${found}" - then - net_fetch "${module_get_channel}/${1}" "${module_get_folder}/${1}" || return - printf '%s\n' "${module_get_folder}/${1}" - return - fi - - printf '%s\n' "${found}" -} diff --git a/lib/net/fetch.sh b/lib/net/fetch.sh deleted file mode 100644 index 9215c0a..0000000 --- a/lib/net/fetch.sh +++ /dev/null @@ -1,50 +0,0 @@ -## - # net_fetch.sh - obtains files from the network - ## - -require 'fs/dirname.sh' -require 'fs/tempdir.sh' - -net_fetch () -{ - local net_fetch_command - local tempdir - local download_dir - - if curl --help >/dev/null 2>&1 - then - net_fetch_command='curl --fail -kL' - elif wget --help >/dev/null 2>&1 - then - net_fetch_command='wget -qO-' - fi - - if test -z "${net_fetch_command:-}" - then - return 1 - fi - - if test -n "${2:-}" - then - tempdir="$(fs_tempdir 'net_fetch')" - mkdir -p "$(fs_dirname "${tempdir}/${2}")" - trap 'net_fetch_clear' 2 - - if ${net_fetch_command} "${1}" 2>/dev/null > "${tempdir}/${2}" - then - download_dir="$(fs_dirname "${2}")" - mkdir -p "${download_dir}" - mv "${tempdir}/${2}" "${download_dir}" - net_fetch_clear - fi - - return 0 - fi - - ${net_fetch_command} "${1}" 2>/dev/null -} - -net_fetch_clear () -{ - rm -Rf "${tempdir}" -} diff --git a/lib/require.sh b/lib/require.sh deleted file mode 100644 index 69f93fd..0000000 --- a/lib/require.sh +++ /dev/null @@ -1,114 +0,0 @@ -## - # require.sh - a portable shell script file loader - ## - -require () -{ - local suffix=".sh" - local previous="${dependency:-require}" - local dependency="${1}" - shift - - require_loaded="${require_loaded:- }" - - if require_is_loaded "${dependency}" "${previous}" "${@:-}" - then - return 0 - fi - - if ! require_include "${dependency}" "${@:-}" - then - echo "Could not find dependency '${dependency}'" - exit $? - fi -} - -require_on_include () -{ - local required_file="${1}" - - test "${dependency%*${suffix}}${suffix}" = "${dependency}" || return 0 - - set -- - . "${required_file}" -} - -require_on_request () -{ - local dependency="${1}" - - if test "--${dependency#*--}" = "${dependency}" - then - return 0 - fi - - if test "${require_loaded#* ${dependency} *}" = \ - "${require_loaded}" - then - return 1 - fi -} - -require_on_search () -{ - require_path "${1}" -} - -require_path () -{ - local solved - local target_path="${2:-${require_path:-}}" - local IFS - - IFS=':' - - for solved in ${target_path} - do - if test -f "${solved}/${1}" - then - printf %s "${solved}/${1}" - return - fi - done -} - -require_include () -{ - local dependency="${1}" - shift - local location - - location="$( - ${require_on_search:-require_on_search} \ - "${dependency}" "${@:-}" - )" - - test -f "${location}" || return 69 - - require_loaded="${require_loaded:- }${dependency} " - - ${require_on_include:-require_on_include} \ - "${location}" "${dependency}" "${@:-}" || - return 1 -} - -require_is_loaded () -{ - dependency="${1}" - previous="${2}" - require_loaded="${require_loaded:- }" - - ${require_on_request:-require_on_request} "${@:-}" -} - -require_source () -{ - local IFS - - IFS='' - - while read -r line - do - printf '%s\n' "${line}" - done < "$(require_path "${1}")" -} diff --git a/lib/script/entrypoint b/lib/script/entrypoint deleted file mode 100755 index 37fa255..0000000 --- a/lib/script/entrypoint +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -## - # script/entrypoint - a modular command runner - ## - -# Run command named by the entrypoint variable or nothing (the : command) -"${entrypoint:-:}" "${@:-}" diff --git a/lib/script/support b/lib/script/support deleted file mode 100644 index fccb67c..0000000 --- a/lib/script/support +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh - -## - # script/support - silently set sane options for all shells - ## - -# Options for all shells -# -# -e: Exit if any command has an uncaught error code -# -u: Exit on any use of an undefined variable -# -f: Do not expand glob patterns -set -euf - -# Mimic local variables on ksh by aliasing it to typeset -# -# This command is ignored by shells other than ksh -# -if ( test -n "${KSH_VERSION:-}" && - test -z "${KSH_VERSION##*Version AJM*}" ) || - test -n "${YASH_VERSION:-}" -then - alias local=typeset -elif test -n "${BASH_VERSION-}" -then - set -o posix -elif test -n "${ZSH_VERSION-}" -then - # Unset options for zsh to make it more portable - # - # NO_MATCH: Avoid expanding extra filename patterns - # NO_SH_WORD_SPLIT: Make the word split on zsh behave like POSIX - # - # This command is ignored in shells other than zsh. - # - unsetopt GLOB NO_MATCH NO_SH_WORD_SPLIT >/dev/null 2>&1 || : -fi diff --git a/lib/shell/assertion.sh b/lib/shell/assertion.sh deleted file mode 100644 index 3bb3469..0000000 --- a/lib/shell/assertion.sh +++ /dev/null @@ -1,109 +0,0 @@ -## - # shell_assertion.sh - runs and verifies terminal session interactions - ## - -require 'shell/sandbox.sh' - -shell_assertion () -{ - local assertion_dir - local assertion - local previous_dir - local message - local instructions - local result - local sandbox_code - local expectation - local expectation_lines - local message_line - local oldifs - local sandbox_id - - assertion_dir="${2:-.}" - assertion="${3:-_shell_assertion_report}" - previous_dir="${PWD}" - message= - instructions= - result=true - sandbox_code=0 - expectation= - expectation_lines=0 - message_line=1 - oldifs="${IFS}" - sandbox_id="$(math_random)" - - cd "${assertion_dir}" - - IFS='' - while read -r message - do - IFS="${oldifs}" - if test "\$ ${message#*\$ }" = "${message}" - then - ${assertion} "${instructions}" "${expectation}" "${result}" - instructions="${message#*\$ }" - shell_sandbox_previous_code="${sandbox_code}" \ - shell_sandbox "${assertion_dir}/.${sandbox_id}" "${instructions}" \ - > "${assertion_dir}/.assertion_result" 2>&1 && - sandbox_code="${?}" || - sandbox_code="${?}" - result="$( - _shell_assertion_import_result \ - < "${assertion_dir}/.assertion_result" - )" - - expectation= - else - _shell_assertion_collect_expectation - fi - message_line=$((message_line + 1)) - IFS='' - done < "${1}" - IFS="${oldifs}" - - ${assertion} "${instructions}" "${expectation}" "${result}" - instructions="${message#*\$ }" - - cd "${previous_dir}" -} - - -_shell_assertion_import_result () -{ - sed 's/^.*$/# - &/' -} - - -_shell_assertion_collect_expectation () -{ - if test -z "${expectation}" && test -z "${message}" - then - expectation="# - $(printf \\n)" - elif test -z "${expectation}" - then - expectation="$(printf '%s\n' "# - ${message}")" - else - expectation="$( - printf '%s\n' "${expectation}" - printf '%s\n' "# - ${message}" - )" - fi - - expectation_lines=$((expectation_lines + 1)) -} - -_shell_assertion_report () -{ - local line_report="${1}" - - if test "${2}" = "${3}" - then - echo " $ ${line_report}" - echo "${2}" | sed 's/# - \(.*\)/ \1/' - elif test ! -z "${1}" - then - echo " $ ${line_report}" - echo "${3}" | sed 's/# - \(.*\)/+ \1/' - echo "${2}" | sed 's/# - \(.*\)/- \1/' - fi -} diff --git a/lib/shell/route.sh b/lib/shell/route.sh deleted file mode 100644 index 4a30c26..0000000 --- a/lib/shell/route.sh +++ /dev/null @@ -1,57 +0,0 @@ -## - # shell_route.sh - dispatches commands and options to shell functions - ## - -shell_route () -{ - local namespace="${1}" - local argument="${2:-}" - local short - local long - local long_name - local long_value - local target - - if test "" = "${argument}" - then - "${namespace}_${shell_route_default:-empty_command}" - return $? - fi - - short="${argument#*-}" - long="${short#*-}" - - shift 2 - - if test "${argument}" = "--${long}" - then - long_name="${long%%=*}" - - if test "${long}" != "${long_name}" - then - long_value="${long#*=}" - long="${long_name}" - set -- "${long_value}" "${@:-}" - fi - - target="${namespace}_${shell_route_option:-option_}${long}" - elif test "${argument}" = "-${short}" - then - target="${namespace}_${shell_route_option:-option}_${short}" - elif test -z "${shell_route_options_only:-}" - then - target="${namespace}_${shell_route_command:-command}_${long}" - else - return 1 - fi - - set -- "${target:-:}" "${@:-}" - - if test -n "${shell_route_name:-}" - then - echo "${@:-}" - return - fi - - "${@:-}" -} diff --git a/lib/shell/sandbox.sh b/lib/shell/sandbox.sh deleted file mode 100644 index 3e249d4..0000000 --- a/lib/shell/sandbox.sh +++ /dev/null @@ -1,54 +0,0 @@ -## - # shell_sandbox.sh - runs shell code controlling variable input/output - ## - -require 'shell/vars.sh' -require 'math/random.sh' - -shell_sandbox () -{ - local sandbox_file="${1:-./.shell_sandbox}" - local sandbox_instructions="${2:-:}" - local shell_sandbox_previous_code="${shell_sandbox_previous_code:-0}" - local return_code=0 - local shell_sandbox_shell="${shell_sandbox_shell:-sh}" - local signature - - signature="$(math_random)" - - test -f "${sandbox_file}" || printf '' > "${sandbox_file}" || return 1 - - ${shell_sandbox_shell} <<-EXTERNAL && return_code=$? || return_code=$? - $(require_source 'shell/vars.sh') - - . "${sandbox_file}" - - PATH="\${PATH}:." - SHELL="${shell_sandbox_shell}" - TERM= - - ${shell_sandbox_setup:-} - - _sandbox_${signature} () { return "${shell_sandbox_previous_code}"; } - - set | shell_vars > "${sandbox_file}${signature}.prev" - - test '0' = "${shell_sandbox_previous_code}" || _sandbox_${signature} - ${sandbox_instructions} - external_code=\$? - - set | shell_vars > "${sandbox_file}${signature}.next" - - exit \${external_code} - EXTERNAL - - comm -3 -1 \ - "${sandbox_file}${signature}.prev" \ - "${sandbox_file}${signature}.next" 2>/dev/null | - sed '/^external_code/d' >> "${sandbox_file}" - - test -n "${sandbox_file}${signature}.prev" || rm "${sandbox_file}${signature}.prev" - test -n "${sandbox_file}${signature}.next" || rm "${sandbox_file}${signature}.next" - - return ${return_code} -} diff --git a/lib/shell/vars.sh b/lib/shell/vars.sh deleted file mode 100644 index 5b61a12..0000000 --- a/lib/shell/vars.sh +++ /dev/null @@ -1,39 +0,0 @@ -## - # shell_vars.sh - normalizes shell variables - ## - -shell_vars () -{ - shell_vars_fill | sort -n -} - -shell_vars_fill () -{ - shell_var_line='' - shell_var_value='' - - if test -n "${POSH_VERSION-}" - then - while read -r shell_var_line - do - shell_var_value="$(eval printf %s "\${${shell_var_line}}")" - printf '%s\n' "${shell_var_line}='${shell_var_value}'" - done - return - fi - - while read -r shell_var_line - do - if test "${shell_var_line#*=}" != "${shell_var_line}" && - test "${shell_var_line#BASHOPTS=*}" = "${shell_var_line}" && - test "${shell_var_line#PATH=*}" = "${shell_var_line}" && - test "${shell_var_line#LINENO=*}" = "${shell_var_line}" && - test "${shell_var_line#IFS=*}" = "${shell_var_line}" - then - printf '%s\n' "${shell_var_line}" - elif test "${shell_var_line%% ()} ()" = "${shell_var_line}" - then - return - fi - done -} diff --git a/lib/spec/doc.sh b/lib/spec/doc.sh deleted file mode 100644 index 51fad75..0000000 --- a/lib/spec/doc.sh +++ /dev/null @@ -1,278 +0,0 @@ -## - # spec_doc.sh - a literate test runner - ## - -require 'fs/basename.sh' -require 'fs/dirname.sh' -require 'fs/tempdir.sh' -require 'fs/lines.sh' -require 'shell/assertion.sh' - -spec_doc () -{ - local spec_doc_shell - local test_number - local fail_number - local test_result - local previous_IFS - local spec_doc_tmp - - spec_doc_shell="${spec_doc_shell:-sh}" - test_number=1 - fail_number=0 - test_result=0 - previous_IFS="${IFS}" - - trap 'spec_doc_clean' 2 >/dev/null 2>&1 || : - - printf '%s\n' "# using '${spec_doc_shell:-sh}'" - - if test -z "${*:-}" - then - printf '%s\n' "# " - printf '%s\n' "# FAILURE (no .md files found)" - test_result=1 - return - fi - - target_files="$(printf '%s\n' "${@:-}")" - - spec_doc_tmp="$(fs_tempdir 'spec_doc')" - - printf '%s\n' "# " - - cp -R "${PWD}" "${spec_doc_tmp}/pwd" - - for target_file in ${target_files} - do - if test ! -f "${target_file}" - then - continue - fi - printf '%s\n' "# file '${target_file}'" - mkdir -p "${spec_doc_tmp}/${target_file}.workspace" - cp -R "${spec_doc_tmp}/pwd/." "${spec_doc_tmp}/${target_file}.workspace" - spec_doc_parse "${spec_doc_tmp}/${target_file}.workspace" "${target_file}" - done - - if test "${fail_number}" -gt 0 - then - printf '%s\n' "# FAILURE (${fail_number} of ${test_number} assertions failed)" - printf '%s\n' "1..${test_number}" - test_result=1 - elif test "${test_number}" -gt 0 - then - printf '%s\n' "# SUCCESS" - printf '%s\n' "1..${test_number}" - else - printf '%s\n' "# FAILURE (no tests found)" - test_result=1 - fi - - spec_doc_return "${test_result}" -} - -spec_doc_return () -{ - rm -Rf "${spec_doc_tmp}" - return "${1:-1}" -} - -spec_doc_parse () -{ - local spec_directory - local line - local open_fence - local possible_fence - local line_number - local line_last_open_fence - local setup - local previous_IFS - - line_last_open_fence=0 - line_number=1 - spec_directory="${1}" - setup='true' - previous_IFS="${IFS}" - - mkdir -p "${spec_directory}/.spec" - - IFS='' - while read -r line - do - IFS="${previous_IFS}" - possible_fence="${line#*\`\`\`}" - - if test -z "${open_fence:-}" - then - if test "${line}" = "\`\`\`${possible_fence}" - then - open_fence="${possible_fence}" - line_last_open_fence="${line_number}" - # Intentionally splitting arguments in variable here - # shellcheck disable=SC2086 - spec_doc_fence_open ${open_fence} - fi - else - if test "${line}" = "\`\`\`${possible_fence}" - then - # Intentionally splitting arguments in variable here - # shellcheck disable=SC2086 - spec_doc_fence_close ${open_fence} - open_fence= - else - # Intentionally splitting arguments in variable here - # shellcheck disable=SC2086 - spec_doc_fence_line ${open_fence} - fi - fi - - line_number=$((line_number + 1)) - IFS='' - done < "${2}" - IFS="${previous_IFS}" -} - -spec_doc_fence_open () -{ - local language - local key - local value - local file_path - - language="${1:-}" - key="${2:-}" - value="${3:-}" - file_path='' - - if test "file" = "${key}" - then - file_path="${spec_directory}/${value}" - if test "$(fs_basename "${file_path}")" != "${file_path}" - then - mkdir -p "$(fs_dirname "${file_path}")" - fi - printf '' > "${file_path}" - elif test "console" = "${language}" && - test ! -z "${key}" - then - printf '' > "${spec_directory}/.spec/console" - elif test "setup" = "${key}" && test "${language}" != "console" - then - printf '' > "${spec_directory}/.spec/setup" - fi -} - -spec_doc_fence_line () -{ - local language - local key - local value - - language="${1:-}" - key="${2:-}" - value="${3:-}" - - if test "file" = "${key}" - then - printf '%s\n' "$line" >> "${spec_directory}/${value}" - elif test "console" = "${language}" && - test ! -z "${key}" - then - printf '%s\n' "$line" >> "${spec_directory}/.spec/console" - elif test "setup" = "${key}" && test "${language}" != "console" - then - printf '%s\n' "$line" >> "${spec_directory}/.spec/setup" - fi -} - -spec_doc_fence_close () -{ - local language - local key - local value - - language="${1:-}" - key="${2:-}" - value="${3:-}" - - if test "console" = "${language}" && - test "test" = "${key}" - then - spec_doc_run_console spec_doc_report_single_result - elif test "console" = "${language}" && - test "task" = "${key}" - then - spec_doc_run_console spec_doc_report_code_result - elif test "setup" = "${key}" && test "${language}" != "console" - then - spec_doc_collect_setup - fi -} - -spec_doc_collect_setup () -{ - setup="$(fs_lines "${spec_directory}/.spec/setup")" -} - -spec_doc_run_console () -{ - local sandbox_code - - shell_sandbox_setup="${setup:-}" \ - shell_sandbox_shell="${spec_doc_shell:-}" \ - shell_assertion \ - "${spec_directory}/.spec/console" "${spec_directory}" "${@:-}" && - sandbox_code=$? || - sandbox_code=$? -} - -spec_doc_report_single_result () -{ - local line_report - - line_report="${test_number} - ${1}" - - if test "${2}" = "${3}" - then - test_number=$((test_number + 1)) - printf '%s\n' "ok ${line_report}" - elif test ! -z "${1}" - then - test_number=$((test_number + 1)) - fail_number=$((fail_number + 1)) - error_line="${line_last_open_fence}" - printf '%s\n' "not ok ${line_report}" - printf '%s\n' "# Failure on ${target_file} line ${error_line}" - printf '%s\n' "${3}" | sed 's/# - \(.*\)/\1/' > "${spec_directory}/.assertion_output" - printf '%s\n' "${2}" | sed 's/# - \(.*\)/\1/' > "${spec_directory}/.assertion_expectation" - diff "${spec_directory}/.assertion_expectation" "${spec_directory}/.assertion_output" - fi -} - -spec_doc_report_code_result () -{ - local line_report - - line_report="${test_number} - ${1}" - - if test -z "${1}" - then - return - fi - - test_number=$((test_number + 1)) - - if test '0' = "${sandbox_code}" - then - printf '%s\n' "ok ${line_report}" - else - fail_number=$((fail_number + 1)) - error_line="${line_last_open_fence}" - printf '%s\n' "not ok ${line_report}" - printf '%s\n' "# Failure on ${target_file} line ${error_line}" - printf '%s\n' "# Output" - printf '%s\n' "${3}" | sed 's/# - \(.*\)/# \1/' - printf '%s\n' "# Exit Code: ${sandbox_code}" - fi -} diff --git a/lib/string/ltrim.sh b/lib/string/ltrim.sh deleted file mode 100644 index 3aeab37..0000000 --- a/lib/string/ltrim.sh +++ /dev/null @@ -1,9 +0,0 @@ -string_ltrim () -{ - while test "${2}" = "${2#${1}}${1}" - do - set -- "${1}" "${2#${1}}" - done - - printf '%s\n' "${2}" -} diff --git a/lib/string/repeat.sh b/lib/string/repeat.sh deleted file mode 100644 index deed4ca..0000000 --- a/lib/string/repeat.sh +++ /dev/null @@ -1,4 +0,0 @@ -string_repeat () -{ - printf "%${2:-10}s" | sed "s/ /${1:-}/g" -} diff --git a/lib/string/rtrim.sh b/lib/string/rtrim.sh deleted file mode 100644 index 7ea9b29..0000000 --- a/lib/string/rtrim.sh +++ /dev/null @@ -1,9 +0,0 @@ -string_rtrim () -{ - while test "${2}" = "${2%${1}}${1}" - do - set -- "${1}" "${2%${1}}" - done - - printf '%s\n' "${2}" -} diff --git a/lib/string/trim.sh b/lib/string/trim.sh deleted file mode 100644 index 1789c76..0000000 --- a/lib/string/trim.sh +++ /dev/null @@ -1,7 +0,0 @@ -require 'string/ltrim.sh' -require 'string/rtrim.sh' - -string_trim () -{ - string_rtrim "${1}" "$(string_ltrim "${1}" "${2}")" -} diff --git a/module/tap/tap.sh b/module/tap/tap.sh new file mode 100644 index 0000000..6dcb85f --- /dev/null +++ b/module/tap/tap.sh @@ -0,0 +1,76 @@ +# Copyright (c) Alexandre Gomes Gaigalas +# SPDX-License-Identifier: ISC + +tap_assert () { + if ! test "x$1" = "x$2" + then + _print " # expected: " + _inline "$1" + _print " # result: " + _inline "$2" + fi +} + +tap_tap () { + local \ + input="${1:-"test/*/*"}" file= line= lineno= match= tests= test_spec= \ + errors= test_name= test_count=0 fail_count=0 + + if test -f "$input" + then REPLY="$input" + else _glob "$input.sh" + fi + + _write "TAP version 14" "" + for file in $REPLY + do + lineno=0 + val tests = [ Set ] + + while read -r line || test -n "$line" + do + lineno=$((lineno + 1)) + match="${line%" () {"}" + if test "$line" = "$match () {" + then + val test_spec = [ Lst ] + Set_add $test_spec $match $lineno + Set_add $tests $test_spec + fi + done < "$file" + + eval "tests=\${$tests# }" # Set_all + + . $file + + for test_spec in $tests + do + eval "test_spec=\${$test_spec# }" # Set_all + set -- $test_spec + test_name="$1" + test_line="$2" + test_count=$((test_count + 1)) + errors="$(set +x; $test_name 2>&1 || :)" + if test -n "$errors" + then + fail_count=$((fail_count + 1)) + _write "not ok ${test_count} - $test_name" \ + "${errors}" \ + " # at: $file:$test_line${__TAB__}" "" "" + else + _write "ok ${test_count} - $test_name" "" + fi + done + done + + _write "1..${test_count}" "" + local test_results="($((test_count - fail_count))/$test_count)" + if test "$fail_count" -gt 0 + then + _write "# FAIL $test_results" "" + return 1 + else + _write "# PASS $test_results" "" + return 0 + fi +} diff --git a/test/_idiom/001-stdout.sh b/test/_idiom/001-stdout.sh new file mode 100644 index 0000000..051d33d --- /dev/null +++ b/test/_idiom/001-stdout.sh @@ -0,0 +1,30 @@ +# Copyright (c) Alexandre Gomes Gaigalas +# SPDX-License-Identifier: ISC + +test_write () { + tap_assert '' "$(_write)" + tap_assert '\0101' "$(_write '\0101')" + tap_assert 'x\\x' "$(_write 'x\\x')" + tap_assert 'x\nx' "$(_write 'x\nx')" + tap_assert 'x\tx' "$(_write 'x\tx')" + tap_assert "123123" "$(_write 123; _write 123)" + tap_assert "123${__EOL__}456123" "$(_write 123 456; _write 123)" +} + +test_print () { + tap_assert '' "$(_print)" + tap_assert 'A' "$(_print '\0101')" + tap_assert 'x\x' "$(_print 'x\\x')" + tap_assert "x${__EOL__}x" "$(_print 'x\nx')" + tap_assert "x${__TAB__}x" "$(_print 'x\tx')" + tap_assert "123123" "$(_print 123; _print 123)" + tap_assert "123${__EOL__}456123" "$(_print 123 456; _print 123)" +} + +test_inline_unary_zerolength () { + tap_assert '' "$(_inline; _inline)" +} + +test_inline () { + tap_assert 'foo\nbar' "$(_inline "foo${__EOL__}bar")" +} diff --git a/test/_idiom/002-Str.sh b/test/_idiom/002-Str.sh new file mode 100644 index 0000000..0e378f2 --- /dev/null +++ b/test/_idiom/002-Str.sh @@ -0,0 +1,20 @@ +# Copyright (c) Alexandre Gomes Gaigalas +# SPDX-License-Identifier: ISC + +test_Str_noargs () { + Str + eval deref=\$$_R + tap_assert "$deref" "" +} + +test_Str_unary_zerolength () { + Str "" + eval deref=\$$_R + tap_assert "$deref" "" +} + +test_Str_simple () { + Str "Some text" + eval deref=\$$_R + tap_assert "$deref" "Some text" +} diff --git a/test/_idiom/003-Lst.sh b/test/_idiom/003-Lst.sh new file mode 100644 index 0000000..cf8e785 --- /dev/null +++ b/test/_idiom/003-Lst.sh @@ -0,0 +1,20 @@ +# Copyright (c) Alexandre Gomes Gaigalas +# SPDX-License-Identifier: ISC + +test_Lst_noargs () { + Lst + eval deref=\$$_R + tap_assert "" "$deref" +} + +test_Lst_unary_zerolength () { + Lst "" + eval deref=\$$_R + tap_assert "" "$deref" +} + +test_Lst_simple () { + Lst foo bar baz + eval deref=\$$_R + tap_assert "foo${__EOL__}bar${__EOL__}baz" "$deref" +} diff --git a/test/_idiom/004-Set.sh b/test/_idiom/004-Set.sh new file mode 100644 index 0000000..2b5b0c6 --- /dev/null +++ b/test/_idiom/004-Set.sh @@ -0,0 +1,38 @@ +# Copyright (c) Alexandre Gomes Gaigalas +# SPDX-License-Identifier: ISC + +test_Set_noargs () { + Set + eval deref=\$$_R + tap_assert "${__EOL__}" "$deref" +} + +test_Set_unary_zerolength () { + Set "" + eval deref=\$$_R + tap_assert "${__EOL__}" "$deref" +} + +test_Set_simple () { + Set foo bar baz + eval deref=\$$_R + tap_assert "${__EOL__}foo${__EOL__}bar${__EOL__}baz" "$deref" +} + +test_Set_uniqueness_A () { + Set foo foo bar + eval deref=\$$_R + tap_assert "${__EOL__}foo${__EOL__}bar" "$deref" +} + +test_Set_uniqueness_B () { + Set foo bar foo + eval deref=\$$_R + tap_assert "${__EOL__}foo${__EOL__}bar" "$deref" +} + +test_Set_uniqueness_C () { + Set bar foo foo + eval deref=\$$_R + tap_assert "${__EOL__}bar${__EOL__}foo" "$deref" +} diff --git a/test/_idiom/005-Arr.sh b/test/_idiom/005-Arr.sh new file mode 100644 index 0000000..1c88576 --- /dev/null +++ b/test/_idiom/005-Arr.sh @@ -0,0 +1,20 @@ +# Copyright (c) Alexandre Gomes Gaigalas +# SPDX-License-Identifier: ISC + +test_Arr_noargs () { + Arr + eval deref=\$$_R + tap_assert "0" "$deref" +} + +test_Arr_unary_zerolength () { + Arr "" + eval deref=\$$_R + tap_assert "1" "$deref" +} + +test_Arr_simple () { + Arr foo bar baz + eval deref=\$$_R + tap_assert "3" "$deref" +} diff --git a/test/_idiom/006-val.sh b/test/_idiom/006-val.sh new file mode 100644 index 0000000..b04dabb --- /dev/null +++ b/test/_idiom/006-val.sh @@ -0,0 +1,23 @@ +# Copyright (c) Alexandre Gomes Gaigalas +# SPDX-License-Identifier: ISC + +test_val_noargs () { + local my_variable= + val my_variable = + eval deref=\$$my_variable + tap_assert "" "$deref" +} + +test_val_unary_zerolength () { + local my_variable= + val my_variable = "" + eval deref=\$$my_variable + tap_assert "" "$deref" +} + +test_val_simple () { + local my_variable= + val my_variable = "Some text" + eval deref=\$$my_variable + tap_assert "Some text" "$deref" +} diff --git a/test/matrix b/test/matrix new file mode 100644 index 0000000..390e402 --- /dev/null +++ b/test/matrix @@ -0,0 +1,161 @@ +# Copyright (c) Alexandre Gomes Gaigalas +# SPDX-License-Identifier: ISC + +set -euf + +test_count_each= +run_count=0 +fail_count=0 +shell_count=0 +skip_count=0 + +matrix_run () { + run_count=$((run_count + 1)) + + if ! command -v "$1" 2>&1 >/dev/null + then + echo "ok $run_count - $1 # SKIP" + skip_count=$((skip_count + 1)) + return + fi + + test_results="$("$@" | tail -n1)" + test_count="${test_results#*/}" + test_count="${test_count%\)}" + + if test -z "$test_count_each" + then test_count_each=$test_count + fi + + if eval test 1 = "\$((${test_results#*"("})" + then + if ! test $test_count_each != $test_count + then + echo -n "ok " + else + echo -n "not ok " + fail_count=$((fail_count + 1)) + fi + else + echo -n "not ok " + fail_count=$((fail_count + 1)) + fi + echo "$run_count - $1 $test_results" +} + +echo "TAP version 14" + +for SHELL in \ + "/opt/bash_3.0.22/bin/bash" \ + "/opt/bash_3.1.23/bin/bash" \ + "/opt/bash_3.2.57/bin/bash" \ + "/opt/bash_4.0.44/bin/bash" \ + "/opt/bash_4.1.17/bin/bash" \ + "/opt/bash_4.2.53/bin/bash" \ + "/opt/bash_4.3.48/bin/bash" \ + "/opt/bash_4.4.23/bin/bash" \ + "/opt/bash_5.0.18/bin/bash" \ + "/opt/bash_5.1.16/bin/bash" \ + "/opt/bash_5.2.21/bin/bash" \ + "/opt/bash_5.3-alpha/bin/bash" \ + "/opt/busybox_1.27.2/bin/busybox ash" \ + "/opt/busybox_1.28.4/bin/busybox ash" \ + "/opt/busybox_1.29.3/bin/busybox ash" \ + "/opt/busybox_1.30.1/bin/busybox ash" \ + "/opt/busybox_1.31.1/bin/busybox ash" \ + "/opt/busybox_1.32.1/bin/busybox ash" \ + "/opt/busybox_1.33.2/bin/busybox ash" \ + "/opt/busybox_1.34.1/bin/busybox ash" \ + "/opt/busybox_1.35.0/bin/busybox ash" \ + "/opt/busybox_1.36.1/bin/busybox ash" \ + "/opt/dash_0.5.10.2/bin/dash" \ + "/opt/dash_0.5.11.5/bin/dash" \ + "/opt/dash_0.5.12/bin/dash" \ + "/opt/dash_0.5.5.1/bin/dash" \ + "/opt/dash_0.5.6.1/bin/dash" \ + "/opt/dash_0.5.7/bin/dash" \ + "/opt/dash_0.5.8/bin/dash" \ + "/opt/dash_0.5.9.1/bin/dash" \ + "/opt/ksh_shvrA93uplusm-v1.0.1/bin/ksh" \ + "/opt/ksh_shvrA93uplusm-v1.0.2/bin/ksh" \ + "/opt/ksh_shvrA93uplusm-v1.0.3/bin/ksh" \ + "/opt/ksh_shvrA93uplusm-v1.0.4/bin/ksh" \ + "/opt/ksh_shvrA93uplusm-v1.0.6/bin/ksh" \ + "/opt/ksh_shvrA93uplusm-v1.0.7/bin/ksh" \ + "/opt/ksh_shvrA93uplusm-v1.0.8/bin/ksh" \ + "/opt/ksh_shvrB2020-2020.0.0/bin/ksh" \ + "/opt/ksh_shvrChistory-b_2010-06-21/bin/ksh" \ + "/opt/ksh_shvrChistory-b_2010-10-26/bin/ksh" \ + "/opt/ksh_shvrChistory-b_2011-03-10/bin/ksh" \ + "/opt/ksh_shvrChistory-b_2012-08-01/bin/ksh" \ + "/opt/ksh_shvrChistory-b_2016-01-10/bin/ksh" \ + "/opt/loksh_6.7.5/bin/loksh" \ + "/opt/loksh_6.8.1/bin/loksh" \ + "/opt/loksh_6.9/bin/loksh" \ + "/opt/loksh_7.0/bin/loksh" \ + "/opt/loksh_7.1/bin/loksh" \ + "/opt/loksh_7.3/bin/loksh" \ + "/opt/loksh_7.4/bin/loksh" \ + "/opt/loksh_7.5/bin/loksh" \ + "/opt/mksh_R45/bin/mksh" \ + "/opt/mksh_R46/bin/mksh" \ + "/opt/mksh_R47/bin/mksh" \ + "/opt/mksh_R48b/bin/mksh" \ + "/opt/mksh_R49/bin/mksh" \ + "/opt/mksh_R50f/bin/mksh" \ + "/opt/mksh_R51/bin/mksh" \ + "/opt/mksh_R52c/bin/mksh" \ + "/opt/mksh_R53a/bin/mksh" \ + "/opt/mksh_R54/bin/mksh" \ + "/opt/mksh_R55/bin/mksh" \ + "/opt/mksh_R56c/bin/mksh" \ + "/opt/mksh_R57/bin/mksh" \ + "/opt/mksh_R58/bin/mksh" \ + "/opt/mksh_R59c/bin/mksh" \ + "/opt/oksh_6.5/bin/oksh" \ + "/opt/oksh_6.6/bin/oksh" \ + "/opt/oksh_6.7.1/bin/oksh" \ + "/opt/oksh_6.8.1/bin/oksh" \ + "/opt/oksh_6.9/bin/oksh" \ + "/opt/oksh_7.0/bin/oksh" \ + "/opt/oksh_7.1/bin/oksh" \ + "/opt/oksh_7.2/bin/oksh" \ + "/opt/oksh_7.3/bin/oksh" \ + "/opt/oksh_7.4/bin/oksh" \ + "/opt/oksh_7.5/bin/oksh" \ + "/opt/posh_0.12.6/bin/posh" \ + "/opt/posh_0.13.2/bin/posh" \ + "/opt/posh_0.14.1/bin/posh" \ + "/opt/zsh_4.2.7/bin/zsh" \ + "/opt/zsh_5.0.8/bin/zsh" \ + "/opt/zsh_5.1.1/bin/zsh" \ + "/opt/zsh_5.2/bin/zsh" \ + "/opt/zsh_5.3.1/bin/zsh" \ + "/opt/zsh_5.4.2/bin/zsh" \ + "/opt/zsh_5.5.1/bin/zsh" \ + "/opt/zsh_5.6.2/bin/zsh" \ + "/opt/zsh_5.7.1/bin/zsh" \ + "/opt/zsh_5.8.1/bin/zsh" \ + "/opt/zsh_5.9/bin/zsh" \ + "/opt/osh_0.14.2/bin/osh" \ + "/opt/osh_0.15.0/bin/osh" \ + "/opt/osh_0.16.0/bin/osh" \ + "/opt/osh_0.17.0/bin/osh" \ + "/opt/osh_0.18.0/bin/osh" \ + "/opt/osh_0.19.0/bin/osh" \ + "/opt/osh_0.20.0/bin/osh" \ + "/opt/osh_0.21.0/bin/osh" \ + "/opt/osh_0.22.0/bin/osh" +do + shell_count=$((shell_count + 1)) + matrix_run $SHELL "$@" +done + +if test $fail_count -gt 0 +then + echo "# MATRIX FAIL ($shell_count runs, $skip_count skipped, $fail_count failures)" + return 1 +else + echo "# MAXTRIX PASS ($shell_count runs, $skip_count skipped)" + return 0 +fi