diff --git a/06-automation/06-02-tools-bats/.ecrc b/06-automation/06-02-tools-bats/.ecrc new file mode 100644 index 0000000..480b412 --- /dev/null +++ b/06-automation/06-02-tools-bats/.ecrc @@ -0,0 +1,18 @@ +{ + "Verbose": true, + "Debug": false, + "IgnoreDefaults": false, + "SpacesAftertabs": false, + "NoColor": false, + "Exclude": ["src/test/docs_helper/.*", "src/main/bash/zsdoc/data/.*"], + "AllowedContentTypes": [], + "PassedFiles": ["src/", "dupa.yml"], + "Disable": { + "EndOfLine": false, + "Indentation": false, + "InsertFinalNewline": false, + "TrimTrailingWhitespace": false, + "IndentSize": false, + "MaxLineLength": false + } +} diff --git a/06-automation/06-02-tools-bats/.editorconfig b/06-automation/06-02-tools-bats/.editorconfig new file mode 100644 index 0000000..aff0218 --- /dev/null +++ b/06-automation/06-02-tools-bats/.editorconfig @@ -0,0 +1,11 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +insert_final_newline = true + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/06-automation/06-02-tools-bats/.gitignore b/06-automation/06-02-tools-bats/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/06-automation/06-02-tools-bats/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/06-automation/06-02-tools-bats/Makefile b/06-automation/06-02-tools-bats/Makefile new file mode 100644 index 0000000..60f2057 --- /dev/null +++ b/06-automation/06-02-tools-bats/Makefile @@ -0,0 +1,53 @@ +BUILD_DIR=build +SHELLCHECK_VERSION=v0.7.1 + +.PHONY: test +test: shellcheck zsd bats editorconfigchecker + + +# Zadanie uruchamiające editorconfig-checkera +.PHONY: editorconfigchecker +editorconfigchecker: install_editorconfigchecker_if_missing + $(BUILD_DIR)/editorconfigchecker/bin/ec + +# Zadanie instalujące editorconfigchecker, jesli nie został już zainstalowany / ściągnięty +.PHONY: install_editorconfigchecker_if_missing +install_editorconfigchecker_if_missing: + bash -c "(test -f $(BUILD_DIR)/editorconfigchecker/bin/ec || tools/build-helper.sh download-editorconfigchecker)" + +# Zadanie generujące dokumentację dla skryptów Bashowych. +.PHONY: zsd +zsd: install_zshelldoc_if_missing + tools/build-helper.sh generate-zsd + +# Zadanie instalujące ZShell Doc, jeśli nie został już zainstalowany / ściągnięty. +.PHONY: install_zshelldoc_if_missing +install_zshelldoc_if_missing: + bash -c "(which zsd || test -f $(BUILD_DIR)/zsd/bin/zsd && echo 'zsd zainstalowany') || tools/build-helper.sh install-zsd" + +# Zadanie uruchamiające Shellcheck. +.PHONY: shellcheck +shellcheck: install_shellcheck_if_missing + $(eval SHELLCHECK_CMD = $(shell bash -c "which shellcheck || echo '$(BUILD_DIR)/shellcheck-$(SHELLCHECK_VERSION)/shellcheck'")) + find src -name "*.sh" | grep -v /zsdoc/ | xargs $(SHELLCHECK_CMD) + +# Zadanie instalujące Shellcheck jeśli go nie ma. +.PHONY: install_shellcheck_if_missing +install_shellcheck_if_missing: + bash -c "(which shellcheck || test -f $(BUILD_DIR)/shellcheck-$(SHELLCHECK_VERSION)}/shellcheck && echo 'shellcheck zainstalowany') || tools/build-helper.sh download-shellcheck" + +# Zadanie uruchamiające testy Bats. +.PHONY: bats +bats: install_bats_if_missing + $(eval BATS_CMD = $(shell bash -c "which bats || echo '$(BUILD_DIR)/bats/bin/bats'")) + $(BATS_CMD) -t src/test/bats + +# Zadanie instalujące Bats, jeśli nie został już zainstalowany / ściągnięty. +.PHONY: install_bats_if_missing +install_bats_if_missing: submodules-init + bash -c "(which bats || test -f $(BUILD_DIR)/bats/bin/bats && echo 'bats zainstalowany') || tools/build-helper.sh download-bats" + +# Zadanie inicjujące submoduły gitowe. +.PHONY: submodules-init +submodules-init: + tools/build-helper.sh initialize-submodules diff --git a/06-automation/06-02-tools-bats/README.adoc b/06-automation/06-02-tools-bats/README.adoc new file mode 100644 index 0000000..dc9c472 --- /dev/null +++ b/06-automation/06-02-tools-bats/README.adoc @@ -0,0 +1,17 @@ += Testowanie Skryptów z Frameworkiem Bats [06-02] + +Narzędzia do testowania i analizy statycznej skryptów są opakowane w Makefile, który poza ich uruchamianiem posiada też polecenia do ich zaciągnięcia. Czyli mozna po prostu uruchomić komendy: +```bash +make test +``` +by wszystko skonfigurować, a następnie testy Bats / Shellcheck / Editorconfig. + +== Kod + +W pliku `tools/build-helper.sh` mamy skrypty Bashowe pomagające przy buildzie. Skrypt potrafi np. dociągnąć różne aplikacje. + +W katalogu `src/main/bash` mamy skrypty Bashowe, które chcemy przetestować. + +W katalogu `src/test/bats` mamy skrypty Bats do testów skryptów bashowych. + +W katalogu `src/main/bash/zsdoc` mamy dokumentację w Asciidoctor skryptów Bashowych (przez projekt https://github.com/zdharma/zshelldoc) diff --git a/06-automation/06-02-tools-bats/src/main/bash/script.sh b/06-automation/06-02-tools-bats/src/main/bash/script.sh new file mode 100755 index 0000000..6d007cb --- /dev/null +++ b/06-automation/06-02-tools-bats/src/main/bash/script.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Exit immediately if a simple command exits with a non-zero status +set -o errexit +# When errtrace is enabled, the ERR trap is also triggered when the error (a command returning a nonzero code) occurs inside a function or a subshell. +set -o errtrace +# If set, the return value of a pipeline is the value of the last (rightmost) command to +# exit with a non-zero status, or zero if all commands in the pipeline exit successfully. +# By default, pipelines only return a failure if the last command errors. +# When used in combination with set -e, pipefail will make a script exit if any command +# in a pipeline errors. +set -o pipefail + +# synopsis {{{ +# Receives a request from an external system. +# }}} + +export CURL_BIN="${CURL_BIN:-curl}" +export URL="${URL:-https://reqres.in/api/users/2}" + +# FUNCTION: request {{{ +# Will call an external URL to retrieve a user. +function request() { + "${CURL_BIN}" "${URL}" || echo "Failed to retrieve the request" +} # }}} + +request diff --git a/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/bodies/script.sh b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/bodies/script.sh new file mode 100644 index 0000000..2dcd5a1 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/bodies/script.sh @@ -0,0 +1,9 @@ + +set -o errexit +set -o errtrace +set -o pipefail + +export CURL_BIN="${CURL_BIN:-curl}" +export URL="${URL:-https://reqres.in/api/users/2}" + +request diff --git a/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/bodies/script.sh.comments b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/bodies/script.sh.comments new file mode 100644 index 0000000..c350cdb --- /dev/null +++ b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/bodies/script.sh.comments @@ -0,0 +1,16 @@ +#!/bin/bash + +# Exit immediately if a simple command exits with a non-zero status +# When errtrace is enabled, the ERR trap is also triggered when the error (a command returning a nonzero code) occurs inside a function or a subshell. +# If set, the return value of a pipeline is the value of the last (rightmost) command to +# exit with a non-zero status, or zero if all commands in the pipeline exit successfully. +# By default, pipelines only return a failure if the last command errors. +# When used in combination with set -e, pipefail will make a script exit if any command +# in a pipeline errors. + +# synopsis {{{ +# Receives a request from an external system. +# }}} + +# FUNCTION: request {{{ +# Will call an external URL to retrieve a user. # }}} diff --git a/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/call_tree.zsd b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/call_tree.zsd new file mode 100644 index 0000000..0be3f0d --- /dev/null +++ b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/call_tree.zsd @@ -0,0 +1 @@ +script.sh/zsd_script_body: script.sh/request diff --git a/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/descriptions/script.sh/request b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/descriptions/script.sh/request new file mode 100644 index 0000000..d100d89 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/descriptions/script.sh/request @@ -0,0 +1 @@ +# Will call an external URL to retrieve a user. diff --git a/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/env-use/script.sh/request/script.sh/Script_Body_/CURL_BIN b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/env-use/script.sh/request/script.sh/Script_Body_/CURL_BIN new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/env-use/script.sh/request/script.sh/Script_Body_/URL b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/env-use/script.sh/request/script.sh/Script_Body_/URL new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/env-use/script.sh/zsd_script_body/script.sh/Script_Body_/CURL_BIN b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/env-use/script.sh/zsd_script_body/script.sh/Script_Body_/CURL_BIN new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/env-use/script.sh/zsd_script_body/script.sh/Script_Body_/URL b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/env-use/script.sh/zsd_script_body/script.sh/Script_Body_/URL new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/exports/script.sh/Script_Body_/CURL_BIN b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/exports/script.sh/Script_Body_/CURL_BIN new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/exports/script.sh/Script_Body_/CURL_BIN @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/exports/script.sh/Script_Body_/URL b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/exports/script.sh/Script_Body_/URL new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/exports/script.sh/Script_Body_/URL @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/extended/script.sh b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/extended/script.sh new file mode 100644 index 0000000..6d007cb --- /dev/null +++ b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/extended/script.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Exit immediately if a simple command exits with a non-zero status +set -o errexit +# When errtrace is enabled, the ERR trap is also triggered when the error (a command returning a nonzero code) occurs inside a function or a subshell. +set -o errtrace +# If set, the return value of a pipeline is the value of the last (rightmost) command to +# exit with a non-zero status, or zero if all commands in the pipeline exit successfully. +# By default, pipelines only return a failure if the last command errors. +# When used in combination with set -e, pipefail will make a script exit if any command +# in a pipeline errors. +set -o pipefail + +# synopsis {{{ +# Receives a request from an external system. +# }}} + +export CURL_BIN="${CURL_BIN:-curl}" +export URL="${URL:-https://reqres.in/api/users/2}" + +# FUNCTION: request {{{ +# Will call an external URL to retrieve a user. +function request() { + "${CURL_BIN}" "${URL}" || echo "Failed to retrieve the request" +} # }}} + +request diff --git a/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/features/script.sh/Script_Body_/export b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/features/script.sh/Script_Body_/export new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/features/script.sh/Script_Body_/export @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/functions/script.sh/request b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/functions/script.sh/request new file mode 100644 index 0000000..2896120 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/functions/script.sh/request @@ -0,0 +1 @@ +"${CURL_BIN}" "${URL}" || echo "Failed to retrieve the request" diff --git a/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/rev_call_tree.zsd b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/rev_call_tree.zsd new file mode 100644 index 0000000..5882f08 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/rev_call_tree.zsd @@ -0,0 +1 @@ +script.sh/request: script.sh/zsd_script_body diff --git a/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/trees/script.sh/Script_Body_.tree b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/trees/script.sh/Script_Body_.tree new file mode 100644 index 0000000..ad1b912 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/trees/script.sh/Script_Body_.tree @@ -0,0 +1,4 @@ +Script_Body_ +`-- request + +1 directory, 0 files diff --git a/06-automation/06-02-tools-bats/src/main/bash/zsdoc/script.sh.adoc b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/script.sh.adoc new file mode 100644 index 0000000..bfc6e12 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/main/bash/zsdoc/script.sh.adoc @@ -0,0 +1,49 @@ +script.sh(1) +============ +:compat-mode!: + +NAME +---- +script.sh - a shell script + +SYNOPSIS +-------- + +Receives a request from an external system. + + +FUNCTIONS +--------- + + request + +DETAILS +------- + +Script Body +~~~~~~~~~~~ + +Has 9 line(s). Calls functions: + + Script-Body + `-- request + +Uses feature(s): _export_ + +_Exports (environment):_ CURL_BIN [big]*//* URL + +request +~~~~~~~ + +____ + # Will call an external URL to retrieve a user. +____ + +Has 1 line(s). Doesn't call other functions. + +Called by: + + Script-Body + +_Environment variables used:_ CURL_BIN [big]*//* URL + diff --git a/06-automation/06-02-tools-bats/src/test/bats/script.bats b/06-automation/06-02-tools-bats/src/test/bats/script.bats new file mode 100644 index 0000000..3ac695a --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/script.bats @@ -0,0 +1,51 @@ +#!/usr/bin/env bats + +load 'test_helper' +load 'test_helper/bats-support/load' +load 'test_helper/bats-assert/load' + +setup() { + export TEMP_DIR="$( mktemp -d )" + cp -a "${SOURCE_DIR}" "${TEMP_DIR}/my-test" +} + +function curl_verbose { echo "curl $*"; } +function curl_stub { echo "hello"; } +function curl_exception { return 1; } + +export -f curl_verbose +export -f curl_stub +export -f curl_exception + +teardown() { rm -rf "${TEMP_DIR}"; } + +# Whitebox testing +@test "should curl request to an external website" { + export CURL_BIN="curl_verbose" + export URL="https://foo.com/bar" + + run "${SOURCE_DIR}/script.sh" + + assert_success + assert_output "curl https://foo.com/bar" +} + +# Blackbox testing +@test "should return a response from an external website" { + export CURL_BIN="curl_stub" + + run "${SOURCE_DIR}/script.sh" + + assert_success + assert_output "hello" +} + +# Failure testing +@test "should not fail when request retrieval failed" { + export CURL_BIN="curl_exception" + + run "${SOURCE_DIR}/script.sh" + + assert_success + assert_output --partial "Failed" +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper.bash b/06-automation/06-02-tools-bats/src/test/bats/test_helper.bash new file mode 100644 index 0000000..29266d7 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper.bash @@ -0,0 +1,6 @@ +#!/bin/bash + +FIXTURES_DIR="${BATS_TEST_DIRNAME}/fixtures" +SOURCE_DIR="${BATS_TEST_DIRNAME}/../../main/bash" + +export FIXTURES_DIR SOURCE_DIR diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/.travis.yml b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/.travis.yml new file mode 100644 index 0000000..e56169a --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/.travis.yml @@ -0,0 +1,8 @@ +language: bash +before_install: + - ./script/install-bats.sh + - git clone --depth 1 https://github.com/ztombol/bats-support ../bats-support +before_script: + - export PATH="${HOME}/.local/bin:${PATH}" +script: + - bats test diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/CHANGELOG.md b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/CHANGELOG.md new file mode 100644 index 0000000..7e326f4 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/CHANGELOG.md @@ -0,0 +1,39 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + + +## [0.3.0] - 2016-03-22 + +### Removed + +- Move `fail()` to `bats-support` + + +## [0.2.0] - 2016-03-11 + +### Added + +- `refute()` to complement `assert()` +- `npm` support + +### Fixed + +- Not consuming the `--` when stopping option parsing in + `assert_output`, `refute_output`, `assert_line` and `refute_line` + + +## 0.1.0 - 2016-02-16 + +### Added + +- Reporting arbitrary failures with `fail()` +- Generic assertions with `assert()` and `assert_equal()` +- Testing exit status with `assert_success()` and `assert_failure()` +- Testing output with `assert_output()` and `refute_output()` +- Testing individual lines with `assert_line()` and `refute_line()` + + +[0.3.0]: https://github.com/ztombol/bats-assert/compare/v0.2.0...v0.3.0 +[0.2.0]: https://github.com/ztombol/bats-assert/compare/v0.1.0...v0.2.0 diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/LICENSE b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/LICENSE new file mode 100644 index 0000000..670154e --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/LICENSE @@ -0,0 +1,116 @@ +CC0 1.0 Universal + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later +claims of infringement build upon, modify, incorporate in other works, reuse +and redistribute as freely as possible in any form whatsoever and for any +purposes, including without limitation commercial purposes. These owners may +contribute to the Commons to promote the ideal of a free culture and the +further production of creative, cultural and scientific works, or to gain +reputation or greater distribution for their Work in part through the use and +efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with a +Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not limited +to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + + iii. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + + v. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + + vii. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time +extensions), (iii) in any current or future medium and for any number of +copies, and (iv) for any purpose whatsoever, including without limitation +commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes +the Waiver for the benefit of each member of the public at large and to the +detriment of Affirmer's heirs and successors, fully intending that such Waiver +shall not be subject to revocation, rescission, cancellation, termination, or +any other legal or equitable action to disrupt the quiet enjoyment of the Work +by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver +is so judged Affirmer hereby grants to each affected person a royalty-free, +non transferable, non sublicensable, non exclusive, irrevocable and +unconditional license to exercise Affirmer's Copyright and Related Rights in +the Work (i) in all territories worldwide, (ii) for the maximum duration +provided by applicable law or treaty (including future time extensions), (iii) +in any current or future medium and for any number of copies, and (iv) for any +purpose whatsoever, including without limitation commercial, advertising or +promotional purposes (the "License"). The License shall be deemed effective as +of the date CC0 was applied by Affirmer to the Work. Should any part of the +License for any reason be judged legally invalid or ineffective under +applicable law, such partial invalidity or ineffectiveness shall not +invalidate the remainder of the License, and in such case Affirmer hereby +affirms that he or she will not (i) exercise any of his or her remaining +Copyright and Related Rights in the Work or (ii) assert any associated claims +and causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + + b. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, fitness + for a particular purpose, non infringement, or the absence of latent or + other defects, accuracy, or the present or absence of errors, whether or not + discoverable, all to the greatest extent permissible under applicable law. + + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without limitation + any person's Copyright and Related Rights in the Work. Further, Affirmer + disclaims responsibility for obtaining any necessary consents, permissions + or other rights required for any use of the Work. + + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see + diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/README.md b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/README.md new file mode 100644 index 0000000..2e60e92 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/README.md @@ -0,0 +1,656 @@ +# bats-assert + +[![GitHub license](https://img.shields.io/badge/license-CC0-blue.svg)](https://raw.githubusercontent.com/ztombol/bats-assert/master/LICENSE) +[![GitHub release](https://img.shields.io/github/release/ztombol/bats-assert.svg)](https://github.com/ztombol/bats-assert/releases/latest) +[![Build Status](https://travis-ci.org/ztombol/bats-assert.svg?branch=master)](https://travis-ci.org/ztombol/bats-assert) + +`bats-assert` is a helper library providing common assertions for +[Bats][bats]. + +Assertions are functions that perform a test and output relevant +information on failure to help debugging. They return 1 on failure and 0 +otherwise. Output, [formatted][bats-support-output] for readability, is +sent to the standard error to make assertions usable outside of `@test` +blocks too. + +Assertions testing exit code and output operate on the results of the +most recent invocation of `run`. + +Dependencies: +- [`bats-support`][bats-support] (formerly `bats-core`) - output + formatting + +See the [shared documentation][bats-docs] to learn how to install and +load this library. + + +## Usage + +### `assert` + +Fail if the given expression evaluates to false. + +***Note:*** *The expression must be a simple command. [Compound +commands][bash-comp-cmd], such as `[[`, can be used only when executed +with `bash -c`.* + +```bash +@test 'assert()' { + touch '/var/log/test.log' + assert [ -e '/var/log/test.log' ] +} +``` + +On failure, the failed expression is displayed. + +``` +-- assertion failed -- +expression : [ -e /var/log/test.log ] +-- +``` + + +### `refute` + +Fail if the given expression evaluates to true. + +***Note:*** *The expression must be a simple command. [Compound +commands][bash-comp-cmd], such as `[[`, can be used only when executed +with `bash -c`.* + +```bash +@test 'refute()' { + rm -f '/var/log/test.log' + refute [ -e '/var/log/test.log' ] +} +``` + +On failure, the successful expression is displayed. + +``` +-- assertion succeeded, but it was expected to fail -- +expression : [ -e /var/log/test.log ] +-- +``` + + +### `assert_equal` + +Fail if the two parameters, actual and expected value respectively, do +not equal. + +```bash +@test 'assert_equal()' { + assert_equal 'have' 'want' +} +``` + +On failure, the expected and actual values are displayed. + +``` +-- values do not equal -- +expected : want +actual : have +-- +``` + +If either value is longer than one line both are displayed in +*multi-line* format. + + +### `assert_success` + +Fail if `$status` is not 0. + +```bash +@test 'assert_success() status only' { + run bash -c "echo 'Error!'; exit 1" + assert_success +} +``` + +On failure, `$status` and `$output` are displayed. + +``` +-- command failed -- +status : 1 +output : Error! +-- +``` + +If `$output` is longer than one line, it is displayed in *multi-line* +format. + + +### `assert_failure` + +Fail if `$status` is 0. + +```bash +@test 'assert_failure() status only' { + run echo 'Success!' + assert_failure +} +``` + +On failure, `$output` is displayed. + +``` +-- command succeeded, but it was expected to fail -- +output : Success! +-- +``` + +If `$output` is longer than one line, it is displayed in *multi-line* +format. + +#### Expected status + +When one parameter is specified, fail if `$status` does not equal the +expected status specified by the parameter. + +```bash +@test 'assert_failure() with expected status' { + run bash -c "echo 'Error!'; exit 1" + assert_failure 2 +} +``` + +On failure, the expected and actual status, and `$output` are displayed. + +``` +-- command failed as expected, but status differs -- +expected : 2 +actual : 1 +output : Error! +-- +``` + +If `$output` is longer than one line, it is displayed in *multi-line* +format. + + +### `assert_output` + +This function helps to verify that a command or function produces the +correct output by checking that the specified expected output matches +the actual output. Matching can be literal (default), partial or regular +expression. This function is the logical complement of `refute_output`. + +#### Literal matching + +By default, literal matching is performed. The assertion fails if +`$output` does not equal the expected output. + +```bash +@test 'assert_output()' { + run echo 'have' + assert_output 'want' +} +``` + +The expected output can be specified with a heredoc or standard input as well. + +```bash +@test 'assert_output() with pipe' { + run echo 'have' + echo 'want' | assert_output +} +``` + +On failure, the expected and actual output are displayed. + +``` +-- output differs -- +expected : want +actual : have +-- +``` + +If either value is longer than one line both are displayed in +*multi-line* format. + +#### Partial matching + +Partial matching can be enabled with the `--partial` option (`-p` for +short). When used, the assertion fails if the expected *substring* is +not found in `$output`. + +```bash +@test 'assert_output() partial matching' { + run echo 'ERROR: no such file or directory' + assert_output --partial 'SUCCESS' +} +``` + +On failure, the substring and the output are displayed. + +``` +-- output does not contain substring -- +substring : SUCCESS +output : ERROR: no such file or directory +-- +``` + +This option and regular expression matching (`--regexp` or `-e`) are +mutually exclusive. An error is displayed when used simultaneously. + +#### Regular expression matching + +Regular expression matching can be enabled with the `--regexp` option +(`-e` for short). When used, the assertion fails if the *extended +regular expression* does not match `$output`. + +*Note: The anchors `^` and `$` bind to the beginning and the end of the +entire output (not individual lines), respectively.* + +```bash +@test 'assert_output() regular expression matching' { + run echo 'Foobar 0.1.0' + assert_output --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' +} +``` + +On failure, the regular expression and the output are displayed. + +``` +-- regular expression does not match output -- +regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ +output : Foobar 0.1.0 +-- +``` + +An error is displayed if the specified extended regular expression is +invalid. + +This option and partial matching (`--partial` or `-p`) are mutually +exclusive. An error is displayed when used simultaneously. + + +### `refute_output` + +This function helps to verify that a command or function produces the +correct output by checking that the specified unexpected output does not +match the actual output. Matching can be literal (default), partial or +regular expression. This function is the logical complement of +`assert_output`. + +#### Literal matching + +By default, literal matching is performed. The assertion fails if +`$output` equals the unexpected output. + +```bash +@test 'refute_output()' { + run echo 'want' + refute_output 'want' +} +``` + +-The unexpected output can be specified with a heredoc or standard input as well. + +```bash +@test 'refute_output() with pipe' { + run echo 'want' + echo 'want' | refute_output +} +``` + +On failure, the output is displayed. + +``` +-- output equals, but it was expected to differ -- +output : want +-- +``` + +If output is longer than one line it is displayed in *multi-line* +format. + +#### Partial matching + +Partial matching can be enabled with the `--partial` option (`-p` for +short). When used, the assertion fails if the unexpected *substring* is +found in `$output`. + +```bash +@test 'refute_output() partial matching' { + run echo 'ERROR: no such file or directory' + refute_output --partial 'ERROR' +} +``` + +On failure, the substring and the output are displayed. + +``` +-- output should not contain substring -- +substring : ERROR +output : ERROR: no such file or directory +-- +``` + +This option and regular expression matching (`--regexp` or `-e`) are +mutually exclusive. An error is displayed when used simultaneously. + +#### Regular expression matching + +Regular expression matching can be enabled with the `--regexp` option +(`-e` for short). When used, the assertion fails if the *extended +regular expression* matches `$output`. + +*Note: The anchors `^` and `$` bind to the beginning and the end of the +entire output (not individual lines), respectively.* + +```bash +@test 'refute_output() regular expression matching' { + run echo 'Foobar v0.1.0' + refute_output --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' +} +``` + +On failure, the regular expression and the output are displayed. + +``` +-- regular expression should not match output -- +regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ +output : Foobar v0.1.0 +-- +``` + +An error is displayed if the specified extended regular expression is +invalid. + +This option and partial matching (`--partial` or `-p`) are mutually +exclusive. An error is displayed when used simultaneously. + + +### `assert_line` + +Similarly to `assert_output`, this function helps to verify that a +command or function produces the correct output. It checks that the +expected line appears in the output (default) or in a specific line of +it. Matching can be literal (default), partial or regular expression. +This function is the logical complement of `refute_line`. + +***Warning:*** *Due to a [bug in Bats][bats-93], empty lines are +discarded from `${lines[@]}`, causing line indices to change and +preventing testing for empty lines.* + +[bats-93]: https://github.com/sstephenson/bats/pull/93 + +#### Looking for a line in the output + +By default, the entire output is searched for the expected line. The +assertion fails if the expected line is not found in `${lines[@]}`. + +```bash +@test 'assert_line() looking for line' { + run echo $'have-0\nhave-1\nhave-2' + assert_line 'want' +} +``` + +On failure, the expected line and the output are displayed. + +***Warning:*** *The output displayed does not contain empty lines. See +the Warning above for more.* + +``` +-- output does not contain line -- +line : want +output (3 lines): + have-0 + have-1 + have-2 +-- +``` + +If output is not longer than one line, it is displayed in *two-column* +format. + +#### Matching a specific line + +When the `--index ` option is used (`-n ` for short) , the +expected line is matched only against the line identified by the given +index. The assertion fails if the expected line does not equal +`${lines[]}`. + +```bash +@test 'assert_line() specific line' { + run echo $'have-0\nhave-1\nhave-2' + assert_line --index 1 'want-1' +} +``` + +On failure, the index and the compared lines are displayed. + +``` +-- line differs -- +index : 1 +expected : want-1 +actual : have-1 +-- +``` + +#### Partial matching + +Partial matching can be enabled with the `--partial` option (`-p` for +short). When used, a match fails if the expected *substring* is not +found in the matched line. + +```bash +@test 'assert_line() partial matching' { + run echo $'have 1\nhave 2\nhave 3' + assert_line --partial 'want' +} +``` + +On failure, the same details are displayed as for literal matching, +except that the substring replaces the expected line. + +``` +-- no output line contains substring -- +substring : want +output (3 lines): + have 1 + have 2 + have 3 +-- +``` + +This option and regular expression matching (`--regexp` or `-e`) are +mutually exclusive. An error is displayed when used simultaneously. + +#### Regular expression matching + +Regular expression matching can be enabled with the `--regexp` option +(`-e` for short). When used, a match fails if the *extended regular +expression* does not match the line being tested. + +*Note: As expected, the anchors `^` and `$` bind to the beginning and +the end of the matched line, respectively.* + +```bash +@test 'assert_line() regular expression matching' { + run echo $'have-0\nhave-1\nhave-2' + assert_line --index 1 --regexp '^want-[0-9]$' +} +``` + +On failure, the same details are displayed as for literal matching, +except that the regular expression replaces the expected line. + +``` +-- regular expression does not match line -- +index : 1 +regexp : ^want-[0-9]$ +line : have-1 +-- +``` + +An error is displayed if the specified extended regular expression is +invalid. + +This option and partial matching (`--partial` or `-p`) are mutually +exclusive. An error is displayed when used simultaneously. + + +### `refute_line` + +Similarly to `refute_output`, this function helps to verify that a +command or function produces the correct output. It checks that the +unexpected line does not appear in the output (default) or in a specific +line of it. Matching can be literal (default), partial or regular +expression. This function is the logical complement of `assert_line`. + +***Warning:*** *Due to a [bug in Bats][bats-93], empty lines are +discarded from `${lines[@]}`, causing line indices to change and +preventing testing for empty lines.* + +[bats-93]: https://github.com/sstephenson/bats/pull/93 + +#### Looking for a line in the output + +By default, the entire output is searched for the unexpected line. The +assertion fails if the unexpected line is found in `${lines[@]}`. + +```bash +@test 'refute_line() looking for line' { + run echo $'have-0\nwant\nhave-2' + refute_line 'want' +} +``` + +On failure, the unexpected line, the index of its first match and the +output with the matching line highlighted are displayed. + +***Warning:*** *The output displayed does not contain empty lines. See +the Warning above for more.* + +``` +-- line should not be in output -- +line : want +index : 1 +output (3 lines): + have-0 +> want + have-2 +-- +``` + +If output is not longer than one line, it is displayed in *two-column* +format. + +#### Matching a specific line + +When the `--index ` option is used (`-n ` for short) , the +unexpected line is matched only against the line identified by the given +index. The assertion fails if the unexpected line equals +`${lines[]}`. + +```bash +@test 'refute_line() specific line' { + run echo $'have-0\nwant-1\nhave-2' + refute_line --index 1 'want-1' +} +``` + +On failure, the index and the unexpected line are displayed. + +``` +-- line should differ -- +index : 1 +line : want-1 +-- +``` + +#### Partial matching + +Partial matching can be enabled with the `--partial` option (`-p` for +short). When used, a match fails if the unexpected *substring* is found +in the matched line. + +```bash +@test 'refute_line() partial matching' { + run echo $'have 1\nwant 2\nhave 3' + refute_line --partial 'want' +} +``` + +On failure, in addition to the details of literal matching, the +substring is also displayed. When used with `--index ` the +substring replaces the unexpected line. + +``` +-- no line should contain substring -- +substring : want +index : 1 +output (3 lines): + have 1 +> want 2 + have 3 +-- +``` + +This option and regular expression matching (`--regexp` or `-e`) are +mutually exclusive. An error is displayed when used simultaneously. + +#### Regular expression matching + +Regular expression matching can be enabled with the `--regexp` option +(`-e` for short). When used, a match fails if the *extended regular +expression* matches the line being tested. + +*Note: As expected, the anchors `^` and `$` bind to the beginning and +the end of the matched line, respectively.* + +```bash +@test 'refute_line() regular expression matching' { + run echo $'Foobar v0.1.0\nRelease date: 2015-11-29' + refute_line --index 0 --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' +} +``` + +On failure, in addition to the details of literal matching, the regular +expression is also displayed. When used with `--index ` the regular +expression replaces the unexpected line. + +``` +-- regular expression should not match line -- +index : 0 +regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ +line : Foobar v0.1.0 +-- +``` + +An error is displayed if the specified extended regular expression is +invalid. + +This option and partial matching (`--partial` or `-p`) are mutually +exclusive. An error is displayed when used simultaneously. + + +## Options + +For functions that have options, `--` disables option parsing for the +remaining arguments to allow using arguments identical to one of the +allowed options. + +```bash +assert_output -- '-p' +``` + +Specifying `--` as an argument is similarly simple. + +```bash +refute_line -- '--' +``` + + + + +[bats]: https://github.com/sstephenson/bats +[bats-support-output]: https://github.com/ztombol/bats-support#output-formatting +[bats-support]: https://github.com/ztombol/bats-support +[bats-docs]: https://github.com/ztombol/bats-docs +[bash-comp-cmd]: https://www.gnu.org/software/bash/manual/bash.html#Compound-Commands diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/load.bash b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/load.bash new file mode 100644 index 0000000..ac4a875 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/load.bash @@ -0,0 +1 @@ +source "$(dirname "${BASH_SOURCE[0]}")/src/assert.bash" diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/package.json b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/package.json new file mode 100644 index 0000000..d81b21c --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/package.json @@ -0,0 +1,8 @@ +{ + "name": "bats-assert", + "version": "0.3.0", + "private": true, + "peerDependencies": { + "bats-support": "git+https://github.com/ztombol/bats-support.git#v0.2.0" + } +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/script/install-bats.sh b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/script/install-bats.sh new file mode 100755 index 0000000..4c3161a --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/script/install-bats.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -o errexit +set -o xtrace + +git clone --depth 1 https://github.com/sstephenson/bats +cd bats && ./install.sh "${HOME}/.local" && cd .. && rm -rf bats diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/src/assert.bash b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/src/assert.bash new file mode 100644 index 0000000..1194753 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/src/assert.bash @@ -0,0 +1,720 @@ +# +# bats-assert - Common assertions for Bats +# +# Written in 2016 by Zoltan Tombol +# +# To the extent possible under law, the author(s) have dedicated all +# copyright and related and neighboring rights to this software to the +# public domain worldwide. This software is distributed without any +# warranty. +# +# You should have received a copy of the CC0 Public Domain Dedication +# along with this software. If not, see +# . +# + +# +# assert.bash +# ----------- +# +# Assertions are functions that perform a test and output relevant +# information on failure to help debugging. They return 1 on failure +# and 0 otherwise. +# +# All output is formatted for readability using the functions of +# `output.bash' and sent to the standard error. +# + +# Fail and display the expression if it evaluates to false. +# +# NOTE: The expression must be a simple command. Compound commands, such +# as `[[', can be used only when executed with `bash -c'. +# +# Globals: +# none +# Arguments: +# $1 - expression +# Returns: +# 0 - expression evaluates to TRUE +# 1 - otherwise +# Outputs: +# STDERR - details, on failure +assert() { + if ! "$@"; then + batslib_print_kv_single 10 'expression' "$*" \ + | batslib_decorate 'assertion failed' \ + | fail + fi +} + +# Fail and display the expression if it evaluates to true. +# +# NOTE: The expression must be a simple command. Compound commands, such +# as `[[', can be used only when executed with `bash -c'. +# +# Globals: +# none +# Arguments: +# $1 - expression +# Returns: +# 0 - expression evaluates to FALSE +# 1 - otherwise +# Outputs: +# STDERR - details, on failure +refute() { + if "$@"; then + batslib_print_kv_single 10 'expression' "$*" \ + | batslib_decorate 'assertion succeeded, but it was expected to fail' \ + | fail + fi +} + +# Fail and display details if the expected and actual values do not +# equal. Details include both values. +# +# Globals: +# none +# Arguments: +# $1 - actual value +# $2 - expected value +# Returns: +# 0 - values equal +# 1 - otherwise +# Outputs: +# STDERR - details, on failure +assert_equal() { + if [[ $1 != "$2" ]]; then + batslib_print_kv_single_or_multi 8 \ + 'expected' "$2" \ + 'actual' "$1" \ + | batslib_decorate 'values do not equal' \ + | fail + fi +} + +# Fail and display details if `$status' is not 0. Details include +# `$status' and `$output'. +# +# Globals: +# status +# output +# Arguments: +# none +# Returns: +# 0 - `$status' is 0 +# 1 - otherwise +# Outputs: +# STDERR - details, on failure +assert_success() { + if (( status != 0 )); then + { local -ir width=6 + batslib_print_kv_single "$width" 'status' "$status" + batslib_print_kv_single_or_multi "$width" 'output' "$output" + } | batslib_decorate 'command failed' \ + | fail + fi +} + +# Fail and display details if `$status' is 0. Details include `$output'. +# +# Optionally, when the expected status is specified, fail when it does +# not equal `$status'. In this case, details include the expected and +# actual status, and `$output'. +# +# Globals: +# status +# output +# Arguments: +# $1 - [opt] expected status +# Returns: +# 0 - `$status' is not 0, or +# `$status' equals the expected status +# 1 - otherwise +# Outputs: +# STDERR - details, on failure +assert_failure() { + (( $# > 0 )) && local -r expected="$1" + if (( status == 0 )); then + batslib_print_kv_single_or_multi 6 'output' "$output" \ + | batslib_decorate 'command succeeded, but it was expected to fail' \ + | fail + elif (( $# > 0 )) && (( status != expected )); then + { local -ir width=8 + batslib_print_kv_single "$width" \ + 'expected' "$expected" \ + 'actual' "$status" + batslib_print_kv_single_or_multi "$width" \ + 'output' "$output" + } | batslib_decorate 'command failed as expected, but status differs' \ + | fail + fi +} + +# Fail and display details if `$output' does not match the expected +# output. The expected output can be specified either by the first +# parameter or on the standard input. +# +# By default, literal matching is performed. The assertion fails if the +# expected output does not equal `$output'. Details include both values. +# +# Option `--partial' enables partial matching. The assertion fails if +# the expected substring cannot be found in `$output'. +# +# Option `--regexp' enables regular expression matching. The assertion +# fails if the extended regular expression does not match `$output'. An +# invalid regular expression causes an error to be displayed. +# +# It is an error to use partial and regular expression matching +# simultaneously. +# +# Globals: +# output +# Options: +# -p, --partial - partial matching +# -e, --regexp - extended regular expression matching +# Arguments: +# $1 - [=STDIN] expected output +# Returns: +# 0 - expected matches the actual output +# 1 - otherwise +# Inputs: +# STDIN - [=$1] expected output +# Outputs: +# STDERR - details, on failure +# error message, on error +assert_output() { + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + + # Handle options. + while (( $# > 0 )); do + case "$1" in + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: assert_output' \ + | fail + return $? + fi + + # Arguments. + local expected + (( $# == 0 )) && expected="$(cat -)" || expected="$1" + + # Matching. + if (( is_mode_regexp )); then + if [[ '' =~ $expected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$expected'" \ + | batslib_decorate 'ERROR: assert_output' \ + | fail + return $? + fi + if ! [[ $output =~ $expected ]]; then + batslib_print_kv_single_or_multi 6 \ + 'regexp' "$expected" \ + 'output' "$output" \ + | batslib_decorate 'regular expression does not match output' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ $output != *"$expected"* ]]; then + batslib_print_kv_single_or_multi 9 \ + 'substring' "$expected" \ + 'output' "$output" \ + | batslib_decorate 'output does not contain substring' \ + | fail + fi + else + if [[ $output != "$expected" ]]; then + batslib_print_kv_single_or_multi 8 \ + 'expected' "$expected" \ + 'actual' "$output" \ + | batslib_decorate 'output differs' \ + | fail + fi + fi +} + +# Fail and display details if `$output' matches the unexpected output. +# The unexpected output can be specified either by the first parameter +# or on the standard input. +# +# By default, literal matching is performed. The assertion fails if the +# unexpected output equals `$output'. Details include `$output'. +# +# Option `--partial' enables partial matching. The assertion fails if +# the unexpected substring is found in `$output'. The unexpected +# substring is added to details. +# +# Option `--regexp' enables regular expression matching. The assertion +# fails if the extended regular expression does matches `$output'. The +# regular expression is added to details. An invalid regular expression +# causes an error to be displayed. +# +# It is an error to use partial and regular expression matching +# simultaneously. +# +# Globals: +# output +# Options: +# -p, --partial - partial matching +# -e, --regexp - extended regular expression matching +# Arguments: +# $1 - [=STDIN] unexpected output +# Returns: +# 0 - unexpected matches the actual output +# 1 - otherwise +# Inputs: +# STDIN - [=$1] unexpected output +# Outputs: +# STDERR - details, on failure +# error message, on error +refute_output() { + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + + # Handle options. + while (( $# > 0 )); do + case "$1" in + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: refute_output' \ + | fail + return $? + fi + + # Arguments. + local unexpected + (( $# == 0 )) && unexpected="$(cat -)" || unexpected="$1" + + if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$unexpected'" \ + | batslib_decorate 'ERROR: refute_output' \ + | fail + return $? + fi + + # Matching. + if (( is_mode_regexp )); then + if [[ $output =~ $unexpected ]] || (( $? == 0 )); then + batslib_print_kv_single_or_multi 6 \ + 'regexp' "$unexpected" \ + 'output' "$output" \ + | batslib_decorate 'regular expression should not match output' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ $output == *"$unexpected"* ]]; then + batslib_print_kv_single_or_multi 9 \ + 'substring' "$unexpected" \ + 'output' "$output" \ + | batslib_decorate 'output should not contain substring' \ + | fail + fi + else + if [[ $output == "$unexpected" ]]; then + batslib_print_kv_single_or_multi 6 \ + 'output' "$output" \ + | batslib_decorate 'output equals, but it was expected to differ' \ + | fail + fi + fi +} + +# Fail and display details if the expected line is not found in the +# output (default) or in a specific line of it. +# +# By default, the entire output is searched for the expected line. The +# expected line is matched against every element of `${lines[@]}'. If no +# match is found, the assertion fails. Details include the expected line +# and `${lines[@]}'. +# +# When `--index ' is specified, only the -th line is matched. +# If the expected line does not match `${lines[]}', the assertion +# fails. Details include and the compared lines. +# +# By default, literal matching is performed. A literal match fails if +# the expected string does not equal the matched string. +# +# Option `--partial' enables partial matching. A partial match fails if +# the expected substring is not found in the target string. +# +# Option `--regexp' enables regular expression matching. A regular +# expression match fails if the extended regular expression does not +# match the target string. An invalid regular expression causes an error +# to be displayed. +# +# It is an error to use partial and regular expression matching +# simultaneously. +# +# Mandatory arguments to long options are mandatory for short options +# too. +# +# Globals: +# output +# lines +# Options: +# -n, --index - match the -th line +# -p, --partial - partial matching +# -e, --regexp - extended regular expression matching +# Arguments: +# $1 - expected line +# Returns: +# 0 - match found +# 1 - otherwise +# Outputs: +# STDERR - details, on failure +# error message, on error +# FIXME(ztombol): Display `${lines[@]}' instead of `$output'! +assert_line() { + local -i is_match_line=0 + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + + # Handle options. + while (( $# > 0 )); do + case "$1" in + -n|--index) + if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then + echo "\`--index' requires an integer argument: \`$2'" \ + | batslib_decorate 'ERROR: assert_line' \ + | fail + return $? + fi + is_match_line=1 + local -ri idx="$2" + shift 2 + ;; + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: assert_line' \ + | fail + return $? + fi + + # Arguments. + local -r expected="$1" + + if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$expected'" \ + | batslib_decorate 'ERROR: assert_line' \ + | fail + return $? + fi + + # Matching. + if (( is_match_line )); then + # Specific line. + if (( is_mode_regexp )); then + if ! [[ ${lines[$idx]} =~ $expected ]]; then + batslib_print_kv_single 6 \ + 'index' "$idx" \ + 'regexp' "$expected" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'regular expression does not match line' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ ${lines[$idx]} != *"$expected"* ]]; then + batslib_print_kv_single 9 \ + 'index' "$idx" \ + 'substring' "$expected" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'line does not contain substring' \ + | fail + fi + else + if [[ ${lines[$idx]} != "$expected" ]]; then + batslib_print_kv_single 8 \ + 'index' "$idx" \ + 'expected' "$expected" \ + 'actual' "${lines[$idx]}" \ + | batslib_decorate 'line differs' \ + | fail + fi + fi + else + # Contained in output. + if (( is_mode_regexp )); then + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + [[ ${lines[$idx]} =~ $expected ]] && return 0 + done + { local -ar single=( + 'regexp' "$expected" + ) + local -ar may_be_multi=( + 'output' "$output" + ) + local -ir width="$( batslib_get_max_single_line_key_width \ + "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" + } | batslib_decorate 'no output line matches regular expression' \ + | fail + elif (( is_mode_partial )); then + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + [[ ${lines[$idx]} == *"$expected"* ]] && return 0 + done + { local -ar single=( + 'substring' "$expected" + ) + local -ar may_be_multi=( + 'output' "$output" + ) + local -ir width="$( batslib_get_max_single_line_key_width \ + "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" + } | batslib_decorate 'no output line contains substring' \ + | fail + else + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + [[ ${lines[$idx]} == "$expected" ]] && return 0 + done + { local -ar single=( + 'line' "$expected" + ) + local -ar may_be_multi=( + 'output' "$output" + ) + local -ir width="$( batslib_get_max_single_line_key_width \ + "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" + } | batslib_decorate 'output does not contain line' \ + | fail + fi + fi +} + +# Fail and display details if the unexpected line is found in the output +# (default) or in a specific line of it. +# +# By default, the entire output is searched for the unexpected line. The +# unexpected line is matched against every element of `${lines[@]}'. If +# a match is found, the assertion fails. Details include the unexpected +# line, the index of the first match and `${lines[@]}' with the matching +# line highlighted if `${lines[@]}' is longer than one line. +# +# When `--index ' is specified, only the -th line is matched. +# If the unexpected line matches `${lines[]}', the assertion fails. +# Details include and the unexpected line. +# +# By default, literal matching is performed. A literal match fails if +# the unexpected string does not equal the matched string. +# +# Option `--partial' enables partial matching. A partial match fails if +# the unexpected substring is found in the target string. When used with +# `--index ', the unexpected substring is also displayed on +# failure. +# +# Option `--regexp' enables regular expression matching. A regular +# expression match fails if the extended regular expression matches the +# target string. When used with `--index ', the regular expression +# is also displayed on failure. An invalid regular expression causes an +# error to be displayed. +# +# It is an error to use partial and regular expression matching +# simultaneously. +# +# Mandatory arguments to long options are mandatory for short options +# too. +# +# Globals: +# output +# lines +# Options: +# -n, --index - match the -th line +# -p, --partial - partial matching +# -e, --regexp - extended regular expression matching +# Arguments: +# $1 - unexpected line +# Returns: +# 0 - match not found +# 1 - otherwise +# Outputs: +# STDERR - details, on failure +# error message, on error +# FIXME(ztombol): Display `${lines[@]}' instead of `$output'! +refute_line() { + local -i is_match_line=0 + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + + # Handle options. + while (( $# > 0 )); do + case "$1" in + -n|--index) + if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then + echo "\`--index' requires an integer argument: \`$2'" \ + | batslib_decorate 'ERROR: refute_line' \ + | fail + return $? + fi + is_match_line=1 + local -ri idx="$2" + shift 2 + ;; + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: refute_line' \ + | fail + return $? + fi + + # Arguments. + local -r unexpected="$1" + + if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$unexpected'" \ + | batslib_decorate 'ERROR: refute_line' \ + | fail + return $? + fi + + # Matching. + if (( is_match_line )); then + # Specific line. + if (( is_mode_regexp )); then + if [[ ${lines[$idx]} =~ $unexpected ]] || (( $? == 0 )); then + batslib_print_kv_single 6 \ + 'index' "$idx" \ + 'regexp' "$unexpected" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'regular expression should not match line' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ ${lines[$idx]} == *"$unexpected"* ]]; then + batslib_print_kv_single 9 \ + 'index' "$idx" \ + 'substring' "$unexpected" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'line should not contain substring' \ + | fail + fi + else + if [[ ${lines[$idx]} == "$unexpected" ]]; then + batslib_print_kv_single 5 \ + 'index' "$idx" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'line should differ' \ + | fail + fi + fi + else + # Line contained in output. + if (( is_mode_regexp )); then + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + if [[ ${lines[$idx]} =~ $unexpected ]]; then + { local -ar single=( + 'regexp' "$unexpected" + 'index' "$idx" + ) + local -a may_be_multi=( + 'output' "$output" + ) + local -ir width="$( batslib_get_max_single_line_key_width \ + "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + if batslib_is_single_line "${may_be_multi[1]}"; then + batslib_print_kv_single "$width" "${may_be_multi[@]}" + else + may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" \ + | batslib_prefix \ + | batslib_mark '>' "$idx" )" + batslib_print_kv_multi "${may_be_multi[@]}" + fi + } | batslib_decorate 'no line should match the regular expression' \ + | fail + return $? + fi + done + elif (( is_mode_partial )); then + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + if [[ ${lines[$idx]} == *"$unexpected"* ]]; then + { local -ar single=( + 'substring' "$unexpected" + 'index' "$idx" + ) + local -a may_be_multi=( + 'output' "$output" + ) + local -ir width="$( batslib_get_max_single_line_key_width \ + "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + if batslib_is_single_line "${may_be_multi[1]}"; then + batslib_print_kv_single "$width" "${may_be_multi[@]}" + else + may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" \ + | batslib_prefix \ + | batslib_mark '>' "$idx" )" + batslib_print_kv_multi "${may_be_multi[@]}" + fi + } | batslib_decorate 'no line should contain substring' \ + | fail + return $? + fi + done + else + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + if [[ ${lines[$idx]} == "$unexpected" ]]; then + { local -ar single=( + 'line' "$unexpected" + 'index' "$idx" + ) + local -a may_be_multi=( + 'output' "$output" + ) + local -ir width="$( batslib_get_max_single_line_key_width \ + "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + if batslib_is_single_line "${may_be_multi[1]}"; then + batslib_print_kv_single "$width" "${may_be_multi[@]}" + else + may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" \ + | batslib_prefix \ + | batslib_mark '>' "$idx" )" + batslib_print_kv_multi "${may_be_multi[@]}" + fi + } | batslib_decorate 'line should not be in output' \ + | fail + return $? + fi + done + fi + fi +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-11-assert.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-11-assert.bats new file mode 100755 index 0000000..6b7606b --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-11-assert.bats @@ -0,0 +1,18 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'assert() : returns 0 if evaluates to TRUE' { + run assert true + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'assert() : returns 1 and displays if it evaluates to FALSE' { + run assert false + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- assertion failed --' ] + [ "${lines[1]}" == 'expression : false' ] + [ "${lines[2]}" == '--' ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-12-assert_equal.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-12-assert_equal.bats new file mode 100755 index 0000000..b21725a --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-12-assert_equal.bats @@ -0,0 +1,50 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'assert_equal() : returns 0 if equals ' { + run assert_equal 'a' 'a' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'assert_equal() : returns 1 and displays details if does not equal ' { + run assert_equal 'a' 'b' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 4 ] + [ "${lines[0]}" == '-- values do not equal --' ] + [ "${lines[1]}" == 'expected : b' ] + [ "${lines[2]}" == 'actual : a' ] + [ "${lines[3]}" == '--' ] +} + +@test 'assert_equal() : displays details in multi-line format if is longer than one line' { + run assert_equal $'a 0\na 1' 'b' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 7 ] + [ "${lines[0]}" == '-- values do not equal --' ] + [ "${lines[1]}" == 'expected (1 lines):' ] + [ "${lines[2]}" == ' b' ] + [ "${lines[3]}" == 'actual (2 lines):' ] + [ "${lines[4]}" == ' a 0' ] + [ "${lines[5]}" == ' a 1' ] + [ "${lines[6]}" == '--' ] +} + +@test 'assert_equal() : displays details in multi-line format if is longer than one line' { + run assert_equal 'a' $'b 0\nb 1' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 7 ] + [ "${lines[0]}" == '-- values do not equal --' ] + [ "${lines[1]}" == 'expected (2 lines):' ] + [ "${lines[2]}" == ' b 0' ] + [ "${lines[3]}" == ' b 1' ] + [ "${lines[4]}" == 'actual (1 lines):' ] + [ "${lines[5]}" == ' a' ] + [ "${lines[6]}" == '--' ] +} + +@test 'assert_equal() : performs literal matching' { + run assert_equal 'a' '*' + [ "$status" -eq 1 ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-13-assert_success.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-13-assert_success.bats new file mode 100755 index 0000000..6e80caa --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-13-assert_success.bats @@ -0,0 +1,36 @@ +#!/usr/bin/env bats + +load test_helper + +@test "assert_success(): returns 0 if \`\$status' is 0" { + run true + run assert_success + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "assert_success(): returns 1 and displays details if \`\$status' is not 0" { + run bash -c 'echo "a" + exit 1' + run assert_success + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 4 ] + [ "${lines[0]}" == '-- command failed --' ] + [ "${lines[1]}" == 'status : 1' ] + [ "${lines[2]}" == 'output : a' ] + [ "${lines[3]}" == '--' ] +} + +@test "assert_success(): displays \`\$output' in multi-line format if it is longer than one line" { + run bash -c 'printf "a 0\na 1" + exit 1' + run assert_success + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 6 ] + [ "${lines[0]}" == '-- command failed --' ] + [ "${lines[1]}" == 'status : 1' ] + [ "${lines[2]}" == 'output (2 lines):' ] + [ "${lines[3]}" == ' a 0' ] + [ "${lines[4]}" == ' a 1' ] + [ "${lines[5]}" == '--' ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-14-assert_failure.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-14-assert_failure.bats new file mode 100755 index 0000000..fee9685 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-14-assert_failure.bats @@ -0,0 +1,69 @@ +#!/usr/bin/env bats + +load test_helper + +@test "assert_failure(): returns 0 if \`\$status' is not 0" { + run false + run assert_failure + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "assert_failure(): returns 1 and displays details if \`\$status' is 0" { + run bash -c 'echo "a" + exit 0' + run assert_failure + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- command succeeded, but it was expected to fail --' ] + [ "${lines[1]}" == 'output : a' ] + [ "${lines[2]}" == '--' ] +} + +@test "assert_failure(): displays \`\$output' in multi-line format if it is longer then one line" { + run bash -c 'printf "a 0\na 1" + exit 0' + run assert_failure + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 5 ] + [ "${lines[0]}" == '-- command succeeded, but it was expected to fail --' ] + [ "${lines[1]}" == 'output (2 lines):' ] + [ "${lines[2]}" == ' a 0' ] + [ "${lines[3]}" == ' a 1' ] + [ "${lines[4]}" == '--' ] +} + +@test "assert_failure() : returns 0 if \`\$status' equals " { + run bash -c 'exit 1' + run assert_failure 1 + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "assert_failure() : returns 1 and displays details if \`\$status' does not equal " { + run bash -c 'echo "a" + exit 1' + run assert_failure 2 + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 5 ] + [ "${lines[0]}" == '-- command failed as expected, but status differs --' ] + [ "${lines[1]}" == 'expected : 2' ] + [ "${lines[2]}" == 'actual : 1' ] + [ "${lines[3]}" == 'output : a' ] + [ "${lines[4]}" == '--' ] +} + +@test "assert_failure() : displays \`\$output' in multi-line format if it is longer then one line" { + run bash -c 'printf "a 0\na 1" + exit 1' + run assert_failure 2 + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 7 ] + [ "${lines[0]}" == '-- command failed as expected, but status differs --' ] + [ "${lines[1]}" == 'expected : 2' ] + [ "${lines[2]}" == 'actual : 1' ] + [ "${lines[3]}" == 'output (2 lines):' ] + [ "${lines[4]}" == ' a 0' ] + [ "${lines[5]}" == ' a 1' ] + [ "${lines[6]}" == '--' ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-15-assert_output.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-15-assert_output.bats new file mode 100755 index 0000000..cca79cc --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-15-assert_output.bats @@ -0,0 +1,242 @@ +#!/usr/bin/env bats + +load test_helper + + +# +# Literal matching +# + +# Correctness +@test "assert_output() : returns 0 if equals \`\$output'" { + run echo 'a' + run assert_output 'a' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "assert_output() : returns 1 and displays details if does not equal \`\$output'" { + run echo 'b' + run assert_output 'a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 4 ] + [ "${lines[0]}" == '-- output differs --' ] + [ "${lines[1]}" == 'expected : a' ] + [ "${lines[2]}" == 'actual : b' ] + [ "${lines[3]}" == '--' ] +} + +@test 'assert_output(): reads from STDIN' { + run echo 'a' + run assert_output <: displays details in multi-line format if \`\$output' is longer than one line" { + run printf 'b 0\nb 1' + run assert_output 'a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 7 ] + [ "${lines[0]}" == '-- output differs --' ] + [ "${lines[1]}" == 'expected (1 lines):' ] + [ "${lines[2]}" == ' a' ] + [ "${lines[3]}" == 'actual (2 lines):' ] + [ "${lines[4]}" == ' b 0' ] + [ "${lines[5]}" == ' b 1' ] + [ "${lines[6]}" == '--' ] +} + +@test 'assert_output() : displays details in multi-line format if is longer than one line' { + run echo 'b' + run assert_output $'a 0\na 1' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 7 ] + [ "${lines[0]}" == '-- output differs --' ] + [ "${lines[1]}" == 'expected (2 lines):' ] + [ "${lines[2]}" == ' a 0' ] + [ "${lines[3]}" == ' a 1' ] + [ "${lines[4]}" == 'actual (1 lines):' ] + [ "${lines[5]}" == ' b' ] + [ "${lines[6]}" == '--' ] +} + +# Options +@test 'assert_output() : performs literal matching by default' { + run echo 'a' + run assert_output '*' + [ "$status" -eq 1 ] +} + + +# +# Partial matching: `-p' and `--partial' +# + +# Options +test_p_partial () { + run echo 'abc' + run assert_output "$1" 'b' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'assert_output() -p : enables partial matching' { + test_p_partial -p +} + +@test 'assert_output() --partial : enables partial matching' { + test_p_partial --partial +} + +# Correctness +@test "assert_output() --partial : returns 0 if is a substring in \`\$output'" { + run printf 'a\nb\nc' + run assert_output --partial 'b' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "assert_output() --partial : returns 1 and displays details if is not a substring in \`\$output'" { + run echo 'b' + run assert_output --partial 'a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 4 ] + [ "${lines[0]}" == '-- output does not contain substring --' ] + [ "${lines[1]}" == 'substring : a' ] + [ "${lines[2]}" == 'output : b' ] + [ "${lines[3]}" == '--' ] +} + +# Output formatting +@test "assert_output() --partial : displays details in multi-line format if \`\$output' is longer than one line" { + run printf 'b 0\nb 1' + run assert_output --partial 'a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 7 ] + [ "${lines[0]}" == '-- output does not contain substring --' ] + [ "${lines[1]}" == 'substring (1 lines):' ] + [ "${lines[2]}" == ' a' ] + [ "${lines[3]}" == 'output (2 lines):' ] + [ "${lines[4]}" == ' b 0' ] + [ "${lines[5]}" == ' b 1' ] + [ "${lines[6]}" == '--' ] +} + +@test 'assert_output() --partial : displays details in multi-line format if is longer than one line' { + run echo 'b' + run assert_output --partial $'a 0\na 1' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 7 ] + [ "${lines[0]}" == '-- output does not contain substring --' ] + [ "${lines[1]}" == 'substring (2 lines):' ] + [ "${lines[2]}" == ' a 0' ] + [ "${lines[3]}" == ' a 1' ] + [ "${lines[4]}" == 'output (1 lines):' ] + [ "${lines[5]}" == ' b' ] + [ "${lines[6]}" == '--' ] +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +# Options +test_r_regexp () { + run echo 'abc' + run assert_output "$1" '^a' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'assert_output() -e : enables regular expression matching' { + test_r_regexp -e +} + +@test 'assert_output() --regexp : enables regular expression matching' { + test_r_regexp --regexp +} + +# Correctness +@test "assert_output() --regexp : returns 0 if matches \`\$output'" { + run printf 'a\nb\nc' + run assert_output --regexp '.*b.*' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "assert_output() --regexp : returns 1 and displays details if does not match \`\$output'" { + run echo 'b' + run assert_output --regexp '.*a.*' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 4 ] + [ "${lines[0]}" == '-- regular expression does not match output --' ] + [ "${lines[1]}" == 'regexp : .*a.*' ] + [ "${lines[2]}" == 'output : b' ] + [ "${lines[3]}" == '--' ] +} + +# Output formatting +@test "assert_output() --regexp : displays details in multi-line format if \`\$output' is longer than one line" { + run printf 'b 0\nb 1' + run assert_output --regexp '.*a.*' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 7 ] + [ "${lines[0]}" == '-- regular expression does not match output --' ] + [ "${lines[1]}" == 'regexp (1 lines):' ] + [ "${lines[2]}" == ' .*a.*' ] + [ "${lines[3]}" == 'output (2 lines):' ] + [ "${lines[4]}" == ' b 0' ] + [ "${lines[5]}" == ' b 1' ] + [ "${lines[6]}" == '--' ] +} + +@test 'assert_output() --regexp : displays details in multi-line format if is longer than one line' { + run echo 'b' + run assert_output --regexp $'.*a\nb.*' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 7 ] + [ "${lines[0]}" == '-- regular expression does not match output --' ] + [ "${lines[1]}" == 'regexp (2 lines):' ] + [ "${lines[2]}" == ' .*a' ] + [ "${lines[3]}" == ' b.*' ] + [ "${lines[4]}" == 'output (1 lines):' ] + [ "${lines[5]}" == ' b' ] + [ "${lines[6]}" == '--' ] +} + +# Error handling +@test 'assert_output() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { + run assert_output --regexp '[.*' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- ERROR: assert_output --' ] + [ "${lines[1]}" == "Invalid extended regular expression: \`[.*'" ] + [ "${lines[2]}" == '--' ] +} + + +# +# Common +# + +@test "assert_output(): \`--partial' and \`--regexp' are mutually exclusive" { + run assert_output --partial --regexp + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- ERROR: assert_output --' ] + [ "${lines[1]}" == "\`--partial' and \`--regexp' are mutually exclusive" ] + [ "${lines[2]}" == '--' ] +} + +@test "assert_output(): \`--' stops parsing options" { + run echo '-p' + run assert_output -- '-p' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-16-refute_output.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-16-refute_output.bats new file mode 100755 index 0000000..5204301 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-16-refute_output.bats @@ -0,0 +1,196 @@ +#!/usr/bin/env bats + +load test_helper + + +# +# Literal matching +# + +# Correctness +@test "refute_output() : returns 0 if does not equal \`\$output'" { + run echo 'b' + run refute_output 'a' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "refute_output() : returns 1 and displays details if equals \`\$output'" { + run echo 'a' + run refute_output 'a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- output equals, but it was expected to differ --' ] + [ "${lines[1]}" == 'output : a' ] + [ "${lines[2]}" == '--' ] +} + +@test 'refute_output(): reads from STDIN' { + run echo 'a' + run refute_output <: displays details in multi-line format if necessary' { + run printf 'a 0\na 1' + run refute_output $'a 0\na 1' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 5 ] + [ "${lines[0]}" == '-- output equals, but it was expected to differ --' ] + [ "${lines[1]}" == 'output (2 lines):' ] + [ "${lines[2]}" == ' a 0' ] + [ "${lines[3]}" == ' a 1' ] + [ "${lines[4]}" == '--' ] +} + +# Options +@test 'refute_output() : performs literal matching by default' { + run echo 'a' + run refute_output '*' + [ "$status" -eq 0 ] +} + + +# +# Partial matching: `-p' and `--partial' +# + +# Options +test_p_partial () { + run echo 'abc' + run refute_output "$1" 'd' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'refute_output() -p : enables partial matching' { + test_p_partial -p +} + +@test 'refute_output() --partial : enables partial matching' { + test_p_partial --partial +} + +# Correctness +@test "refute_output() --partial : returns 0 if is not a substring in \`\$output'" { + run printf 'a\nb\nc' + run refute_output --partial 'd' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "refute_output() --partial : returns 1 and displays details if is a substring in \`\$output'" { + run echo 'a' + run refute_output --partial 'a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 4 ] + [ "${lines[0]}" == '-- output should not contain substring --' ] + [ "${lines[1]}" == 'substring : a' ] + [ "${lines[2]}" == 'output : a' ] + [ "${lines[3]}" == '--' ] +} + +# Output formatting +@test 'refute_output() --partial : displays details in multi-line format if necessary' { + run printf 'a 0\na 1' + run refute_output --partial 'a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 7 ] + [ "${lines[0]}" == '-- output should not contain substring --' ] + [ "${lines[1]}" == 'substring (1 lines):' ] + [ "${lines[2]}" == ' a' ] + [ "${lines[3]}" == 'output (2 lines):' ] + [ "${lines[4]}" == ' a 0' ] + [ "${lines[5]}" == ' a 1' ] + [ "${lines[6]}" == '--' ] +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +# Options +test_r_regexp () { + run echo 'abc' + run refute_output "$1" '^d' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'refute_output() -e : enables regular expression matching' { + test_r_regexp -e +} + +@test 'refute_output() --regexp : enables regular expression matching' { + test_r_regexp --regexp +} + +# Correctness +@test "refute_output() --regexp : returns 0 if does not match \`\$output'" { + run printf 'a\nb\nc' + run refute_output --regexp '.*d.*' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "refute_output() --regexp : returns 1 and displays details if matches \`\$output'" { + run echo 'a' + run refute_output --regexp '.*a.*' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 4 ] + [ "${lines[0]}" == '-- regular expression should not match output --' ] + [ "${lines[1]}" == 'regexp : .*a.*' ] + [ "${lines[2]}" == 'output : a' ] + [ "${lines[3]}" == '--' ] +} + +# Output formatting +@test 'refute_output() --regexp : displays details in multi-line format if necessary' { + run printf 'a 0\na 1' + run refute_output --regexp '.*a.*' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 7 ] + [ "${lines[0]}" == '-- regular expression should not match output --' ] + [ "${lines[1]}" == 'regexp (1 lines):' ] + [ "${lines[2]}" == ' .*a.*' ] + [ "${lines[3]}" == 'output (2 lines):' ] + [ "${lines[4]}" == ' a 0' ] + [ "${lines[5]}" == ' a 1' ] + [ "${lines[6]}" == '--' ] +} + +# Error handling +@test 'refute_output() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { + run refute_output --regexp '[.*' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- ERROR: refute_output --' ] + [ "${lines[1]}" == "Invalid extended regular expression: \`[.*'" ] + [ "${lines[2]}" == '--' ] +} + + +# +# Common +# + +@test "refute_output(): \`--partial' and \`--regexp' are mutually exclusive" { + run refute_output --partial --regexp + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- ERROR: refute_output --' ] + [ "${lines[1]}" == "\`--partial' and \`--regexp' are mutually exclusive" ] + [ "${lines[2]}" == '--' ] +} + +@test "refute_output(): \`--' stops parsing options" { + run echo '--' + run refute_output -- '-p' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-17-assert_line.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-17-assert_line.bats new file mode 100755 index 0000000..bee3850 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-17-assert_line.bats @@ -0,0 +1,334 @@ +#!/usr/bin/env bats + +load test_helper + + +############################################################################### +# Containing a line +############################################################################### + +# +# Literal matching +# + +# Correctness +@test "assert_line() : returns 0 if is a line in \`\${lines[@]}'" { + run printf 'a\nb\nc' + run assert_line 'b' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "assert_line() : returns 1 and displays details if is not a line in \`\${lines[@]}'" { + run echo 'b' + run assert_line 'a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 4 ] + [ "${lines[0]}" == '-- output does not contain line --' ] + [ "${lines[1]}" == 'line : a' ] + [ "${lines[2]}" == 'output : b' ] + [ "${lines[3]}" == '--' ] +} + +# Output formatting +@test "assert_line() : displays \`\$output' in multi-line format if it is longer than one line" { + run printf 'b 0\nb 1' + run assert_line 'a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 6 ] + [ "${lines[0]}" == '-- output does not contain line --' ] + [ "${lines[1]}" == 'line : a' ] + [ "${lines[2]}" == 'output (2 lines):' ] + [ "${lines[3]}" == ' b 0' ] + [ "${lines[4]}" == ' b 1' ] + [ "${lines[5]}" == '--' ] +} + +# Options +@test 'assert_line() : performs literal matching by default' { + run echo 'a' + run assert_line '*' + [ "$status" -eq 1 ] +} + + +# +# Partial matching: `-p' and `--partial' +# + +# Options +test_p_partial () { + run printf 'a\n_b_\nc' + run assert_line "$1" 'b' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'assert_line() -p : enables partial matching' { + test_p_partial -p +} + +@test 'assert_line() --partial : enables partial matching' { + test_p_partial --partial +} + +# Correctness +@test "assert_line() --partial : returns 0 if is a substring in any line in \`\${lines[@]}'" { + run printf 'a\n_b_\nc' + run assert_line --partial 'b' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "assert_line() --partial : returns 1 and displays details if is not a substring in any lines in \`\${lines[@]}'" { + run echo 'b' + run assert_line --partial 'a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 4 ] + [ "${lines[0]}" == '-- no output line contains substring --' ] + [ "${lines[1]}" == 'substring : a' ] + [ "${lines[2]}" == 'output : b' ] + [ "${lines[3]}" == '--' ] +} + +# Output formatting +@test "assert_line() --partial : displays \`\$output' in multi-line format if it is longer than one line" { + run printf 'b 0\nb 1' + run assert_line --partial 'a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 6 ] + [ "${lines[0]}" == '-- no output line contains substring --' ] + [ "${lines[1]}" == 'substring : a' ] + [ "${lines[2]}" == 'output (2 lines):' ] + [ "${lines[3]}" == ' b 0' ] + [ "${lines[4]}" == ' b 1' ] + [ "${lines[5]}" == '--' ] +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +# Options +test_r_regexp () { + run printf 'a\n_b_\nc' + run assert_line "$1" '^.b' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'assert_line() -e : enables regular expression matching' { + test_r_regexp -e +} + +@test 'assert_line() --regexp : enables regular expression matching' { + test_r_regexp --regexp +} + +# Correctness +@test "assert_line() --regexp : returns 0 if matches any line in \`\${lines[@]}'" { + run printf 'a\n_b_\nc' + run assert_line --regexp '^.b' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "assert_line() --regexp : returns 1 and displays details if does not match any lines in \`\${lines[@]}'" { + run echo 'b' + run assert_line --regexp '^.a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 4 ] + [ "${lines[0]}" == '-- no output line matches regular expression --' ] + [ "${lines[1]}" == 'regexp : ^.a' ] + [ "${lines[2]}" == 'output : b' ] + [ "${lines[3]}" == '--' ] +} + +# Output formatting +@test "assert_line() --regexp : displays \`\$output' in multi-line format if longer than one line" { + run printf 'b 0\nb 1' + run assert_line --regexp '^.a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 6 ] + [ "${lines[0]}" == '-- no output line matches regular expression --' ] + [ "${lines[1]}" == 'regexp : ^.a' ] + [ "${lines[2]}" == 'output (2 lines):' ] + [ "${lines[3]}" == ' b 0' ] + [ "${lines[4]}" == ' b 1' ] + [ "${lines[5]}" == '--' ] +} + + +############################################################################### +# Matching single line: `-n' and `--index' +############################################################################### + +# Options +test_n_index () { + run printf 'a\nb\nc' + run assert_line "$1" 1 'b' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'assert_line() -n : matches against the -th line only' { + test_n_index -n +} + +@test 'assert_line() --index : matches against the -th line only' { + test_n_index --index +} + +@test 'assert_line() --index : returns 1 and displays an error message if is not an integer' { + run assert_line --index 1a + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- ERROR: assert_line --' ] + [ "${lines[1]}" == "\`--index' requires an integer argument: \`1a'" ] + [ "${lines[2]}" == '--' ] +} + + +# +# Literal matching +# + +# Correctness +@test "assert_line() --index : returns 0 if equals \`\${lines[]}'" { + run printf 'a\nb\nc' + run assert_line --index 1 'b' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "assert_line() --index : returns 1 and displays details if does not equal \`\${lines[]}'" { + run printf 'a\nb\nc' + run assert_line --index 1 'a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 5 ] + [ "${lines[0]}" == '-- line differs --' ] + [ "${lines[1]}" == 'index : 1' ] + [ "${lines[2]}" == 'expected : a' ] + [ "${lines[3]}" == 'actual : b' ] + [ "${lines[4]}" == '--' ] +} + +# Options +@test 'assert_line() --index : performs literal matching by default' { + run printf 'a\nb\nc' + run assert_line --index 1 '*' + [ "$status" -eq 1 ] +} + + +# +# Partial matching: `-p' and `--partial' +# + +# Options +test_index_p_partial () { + run printf 'a\n_b_\nc' + run assert_line --index 1 "$1" 'b' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'assert_line() --index -p : enables partial matching' { + test_index_p_partial -p +} + +@test 'assert_line() --index --partial : enables partial matching' { + test_index_p_partial --partial +} + +# Correctness +@test "assert_line() --index --partial : returns 0 if is a substring in \`\${lines[]}'" { + run printf 'a\n_b_\nc' + run assert_line --index 1 --partial 'b' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "assert_line() --index --partial : returns 1 and displays details if is not a substring in \`\${lines[]}'" { + run printf 'b 0\nb 1' + run assert_line --index 1 --partial 'a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 5 ] + [ "${lines[0]}" == '-- line does not contain substring --' ] + [ "${lines[1]}" == 'index : 1' ] + [ "${lines[2]}" == 'substring : a' ] + [ "${lines[3]}" == 'line : b 1' ] + [ "${lines[4]}" == '--' ] +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +# Options +test_index_r_regexp () { + run printf 'a\n_b_\nc' + run assert_line --index 1 "$1" '^.b' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'assert_line() --index -e : enables regular expression matching' { + test_index_r_regexp -e +} + +@test 'assert_line() --index --regexp : enables regular expression matching' { + test_index_r_regexp --regexp +} + +# Correctness +@test "assert_line() --index --regexp : returns 0 if matches \`\${lines[]}'" { + run printf 'a\n_b_\nc' + run assert_line --index 1 --regexp '^.b' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "assert_line() --index --regexp : returns 1 and displays details if does not match \`\${lines[]}'" { + run printf 'a\nb\nc' + run assert_line --index 1 --regexp '^.a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 5 ] + [ "${lines[0]}" == '-- regular expression does not match line --' ] + [ "${lines[1]}" == 'index : 1' ] + [ "${lines[2]}" == 'regexp : ^.a' ] + [ "${lines[3]}" == 'line : b' ] + [ "${lines[4]}" == '--' ] +} + + +############################################################################### +# Common +############################################################################### + +@test "assert_line(): \`--partial' and \`--regexp' are mutually exclusive" { + run assert_line --partial --regexp + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- ERROR: assert_line --' ] + [ "${lines[1]}" == "\`--partial' and \`--regexp' are mutually exclusive" ] + [ "${lines[2]}" == '--' ] +} + +@test 'assert_line() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { + run assert_line --regexp '[.*' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- ERROR: assert_line --' ] + [ "${lines[1]}" == "Invalid extended regular expression: \`[.*'" ] + [ "${lines[2]}" == '--' ] +} + +@test "assert_line(): \`--' stops parsing options" { + run printf 'a\n-p\nc' + run assert_line -- '-p' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-18-refute_line.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-18-refute_line.bats new file mode 100755 index 0000000..9cc8185 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-18-refute_line.bats @@ -0,0 +1,342 @@ +#!/usr/bin/env bats + +load test_helper + + +############################################################################### +# Containing a line +############################################################################### + +# +# Literal matching +# + +# Correctness +@test "refute_line() : returns 0 if is not a line in \`\${lines[@]}'" { + run printf 'a\nb\nc' + run refute_line 'd' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "refute_line() : returns 1 and displays details if is not a line in \`\${lines[@]}'" { + run echo 'a' + run refute_line 'a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 5 ] + [ "${lines[0]}" == '-- line should not be in output --' ] + [ "${lines[1]}" == 'line : a' ] + [ "${lines[2]}" == 'index : 0' ] + [ "${lines[3]}" == 'output : a' ] + [ "${lines[4]}" == '--' ] +} + +# Output formatting +@test "refute_line() : displays \`\$output' in multi-line format if it is longer than one line" { + run printf 'a 0\na 1\na 2' + run refute_line 'a 1' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 8 ] + [ "${lines[0]}" == '-- line should not be in output --' ] + [ "${lines[1]}" == 'line : a 1' ] + [ "${lines[2]}" == 'index : 1' ] + [ "${lines[3]}" == 'output (3 lines):' ] + [ "${lines[4]}" == ' a 0' ] + [ "${lines[5]}" == '> a 1' ] + [ "${lines[6]}" == ' a 2' ] + [ "${lines[7]}" == '--' ] +} + +# Options +@test 'refute_line() : performs literal matching by default' { + run echo 'a' + run refute_line '*' + [ "$status" -eq 0 ] +} + + +# +# Partial matching: `-p' and `--partial' +# + +# Options +test_p_partial () { + run printf 'a\nb\nc' + run refute_line "$1" 'd' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'refute_line() -p : enables partial matching' { + test_p_partial -p +} + +@test 'refute_line() --partial : enables partial matching' { + test_p_partial --partial +} + +# Correctness +@test "refute_line() --partial : returns 0 if is not a substring in any line in \`\${lines[@]}'" { + run printf 'a\nb\nc' + run refute_line --partial 'd' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "refute_line() --partial : returns 1 and displays details if is a substring in any line in \`\${lines[@]}'" { + run echo 'a' + run refute_line --partial 'a' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 5 ] + [ "${lines[0]}" == '-- no line should contain substring --' ] + [ "${lines[1]}" == 'substring : a' ] + [ "${lines[2]}" == 'index : 0' ] + [ "${lines[3]}" == 'output : a' ] + [ "${lines[4]}" == '--' ] +} + +# Output formatting +@test "refute_line() --partial : displays \`\$output' in multi-line format if it is longer than one line" { + run printf 'a\nabc\nc' + run refute_line --partial 'b' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 8 ] + [ "${lines[0]}" == '-- no line should contain substring --' ] + [ "${lines[1]}" == 'substring : b' ] + [ "${lines[2]}" == 'index : 1' ] + [ "${lines[3]}" == 'output (3 lines):' ] + [ "${lines[4]}" == ' a' ] + [ "${lines[5]}" == '> abc' ] + [ "${lines[6]}" == ' c' ] + [ "${lines[7]}" == '--' ] +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +# Options +test_r_regexp () { + run printf 'a\nb\nc' + run refute_line "$1" '^.d' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'refute_line() -e : enables regular expression matching' { + test_r_regexp -e +} + +@test 'refute_line() --regexp : enables regular expression matching' { + test_r_regexp --regexp +} + +# Correctness +@test "refute_line() --regexp : returns 0 if does not match any line in \`\${lines[@]}'" { + run printf 'a\nb\nc' + run refute_line --regexp '.*d.*' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "refute_line() --regexp : returns 1 and displays details if matches any lines in \`\${lines[@]}'" { + run echo 'a' + run refute_line --regexp '.*a.*' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 5 ] + [ "${lines[0]}" == '-- no line should match the regular expression --' ] + [ "${lines[1]}" == 'regexp : .*a.*' ] + [ "${lines[2]}" == 'index : 0' ] + [ "${lines[3]}" == 'output : a' ] + [ "${lines[4]}" == '--' ] +} + +# Output formatting +@test "refute_line() --regexp : displays \`\$output' in multi-line format if longer than one line" { + run printf 'a\nabc\nc' + run refute_line --regexp '.*b.*' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 8 ] + [ "${lines[0]}" == '-- no line should match the regular expression --' ] + [ "${lines[1]}" == 'regexp : .*b.*' ] + [ "${lines[2]}" == 'index : 1' ] + [ "${lines[3]}" == 'output (3 lines):' ] + [ "${lines[4]}" == ' a' ] + [ "${lines[5]}" == '> abc' ] + [ "${lines[6]}" == ' c' ] + [ "${lines[7]}" == '--' ] +} + + +############################################################################### +# Matching single line: `-n' and `--index' +############################################################################### + +# Options +test_n_index () { + run printf 'a\nb\nc' + run refute_line "$1" 1 'd' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'refute_line() -n : matches against the -th line only' { + test_n_index -n +} + +@test 'refute_line() --index : matches against the -th line only' { + test_n_index --index +} + +@test 'refute_line() --index : returns 1 and displays an error message if is not an integer' { + run refute_line --index 1a + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- ERROR: refute_line --' ] + [ "${lines[1]}" == "\`--index' requires an integer argument: \`1a'" ] + [ "${lines[2]}" == '--' ] +} + + +# +# Literal matching +# + +# Correctness +@test "refute_line() --index : returns 0 if does not equal \`\${lines[]}'" { + run printf 'a\nb\nc' + run refute_line --index 1 'd' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "refute_line() --index : returns 1 and displays details if equals \`\${lines[]}'" { + run printf 'a\nb\nc' + run refute_line --index 1 'b' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 4 ] + [ "${lines[0]}" == '-- line should differ --' ] + [ "${lines[1]}" == 'index : 1' ] + [ "${lines[2]}" == 'line : b' ] + [ "${lines[3]}" == '--' ] +} + +# Options +@test 'refute_line() --index : performs literal matching by default' { + run printf 'a\nb\nc' + run refute_line --index 1 '*' + [ "$status" -eq 0 ] +} + + +# +# Partial matching: `-p' and `--partial' +# + +# Options +test_index_p_partial () { + run printf 'a\nb\nc' + run refute_line --index 1 "$1" 'd' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'refute_line() --index -p : enables partial matching' { + test_index_p_partial -p +} + +@test 'refute_line() --index --partial : enables partial matching' { + test_index_p_partial --partial +} + +# Correctness +@test "refute_line() --index --partial : returns 0 if is not a substring in \`\${lines[]}'" { + run printf 'a\nabc\nc' + run refute_line --index 1 --partial 'd' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "refute_line() --index --partial : returns 1 and displays details if is a substring in \`\${lines[]}'" { + run printf 'a\nabc\nc' + run refute_line --index 1 --partial 'b' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 5 ] + [ "${lines[0]}" == '-- line should not contain substring --' ] + [ "${lines[1]}" == 'index : 1' ] + [ "${lines[2]}" == 'substring : b' ] + [ "${lines[3]}" == 'line : abc' ] + [ "${lines[4]}" == '--' ] +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +# Options +test_index_r_regexp () { + run printf 'a\nb\nc' + run refute_line --index 1 "$1" '^.b' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'refute_line() --index -e : enables regular expression matching' { + test_index_r_regexp -e +} + +@test 'refute_line() --index --regexp : enables regular expression matching' { + test_index_r_regexp --regexp +} + +# Correctness +@test "refute_line() --index --regexp : returns 0 if does not match \`\${lines[]}'" { + run printf 'a\nabc\nc' + run refute_line --index 1 --regexp '.*d.*' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "refute_line() --index --regexp : returns 1 and displays details if matches \`\${lines[]}'" { + run printf 'a\nabc\nc' + run refute_line --index 1 --regexp '.*b.*' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 5 ] + [ "${lines[0]}" == '-- regular expression should not match line --' ] + [ "${lines[1]}" == 'index : 1' ] + [ "${lines[2]}" == 'regexp : .*b.*' ] + [ "${lines[3]}" == 'line : abc' ] + [ "${lines[4]}" == '--' ] +} + + +############################################################################### +# Common +############################################################################### + +@test "refute_line(): \`--partial' and \`--regexp' are mutually exclusive" { + run refute_line --partial --regexp + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- ERROR: refute_line --' ] + [ "${lines[1]}" == "\`--partial' and \`--regexp' are mutually exclusive" ] + [ "${lines[2]}" == '--' ] +} + +@test 'refute_line() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { + run refute_line --regexp '[.*' + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- ERROR: refute_line --' ] + [ "${lines[1]}" == "Invalid extended regular expression: \`[.*'" ] + [ "${lines[2]}" == '--' ] +} + +@test "refute_line(): \`--' stops parsing options" { + run printf 'a\n--\nc' + run refute_line -- '-p' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-19-refute.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-19-refute.bats new file mode 100755 index 0000000..191dc73 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-19-refute.bats @@ -0,0 +1,18 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'refute() : returns 0 if evaluates to FALSE' { + run refute false + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'refute() : returns 1 and displays if it evaluates to TRUE' { + run refute true + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- assertion succeeded, but it was expected to fail --' ] + [ "${lines[1]}" == 'expression : true' ] + [ "${lines[2]}" == '--' ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/test_helper.bash b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/test_helper.bash new file mode 100644 index 0000000..32ad846 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/test_helper.bash @@ -0,0 +1,10 @@ +setup() { + export TEST_MAIN_DIR="${BATS_TEST_DIRNAME}/.." + export TEST_DEPS_DIR="${TEST_DEPS_DIR-${TEST_MAIN_DIR}/..}" + + # Load dependencies. + load "${TEST_DEPS_DIR}/bats-support/load.bash" + + # Load library. + load '../load' +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/.travis.yml b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/.travis.yml new file mode 100644 index 0000000..75721f2 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/.travis.yml @@ -0,0 +1,7 @@ +language: bash +before_install: + - ./script/install-bats.sh +before_script: + - export PATH="${HOME}/.local/bin:${PATH}" +script: + - bats test diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/CHANGELOG.md b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/CHANGELOG.md new file mode 100644 index 0000000..324d247 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/CHANGELOG.md @@ -0,0 +1,46 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + + +## [0.3.0] - 2016-11-29 + +### Added + +- Restricting invocation to specific locations with + `batslib_is_caller()` + + +## [0.2.0] - 2016-03-22 + +### Added + +- `npm` support +- Reporting arbitrary failures with `fail()` (moved from `bats-assert`) + +### Changed + +- Library renamed to `bats-support` + + +## 0.1.0 - 2016-02-16 + +### Added + +- Two-column key-value formatting with `batslib_print_kv_single()` +- Multi-line key-value formatting with `batslib_print_kv_multi()` +- Mixed formatting with `batslib_print_kv_single_or_multi()` +- Header and footer decoration with `batslib_decorate()` +- Prefixing lines with `batslib_prefix()` +- Marking lines with `batslib_mark()` +- Common output function `batslib_err()` +- Line counting with `batslib_count_lines()` +- Checking whether a text is one line long with + `batslib_is_single_line()` +- Determining key width for two-column and mixed formatting with + `batslib_get_max_single_line_key_width()` + + +[0.3.0]: https://github.com/ztombol/bats-support/compare/v0.2.0...v0.3.0 +[0.2.0]: https://github.com/ztombol/bats-support/compare/v0.1.0...v0.2.0 diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/LICENSE b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/LICENSE new file mode 100644 index 0000000..670154e --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/LICENSE @@ -0,0 +1,116 @@ +CC0 1.0 Universal + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later +claims of infringement build upon, modify, incorporate in other works, reuse +and redistribute as freely as possible in any form whatsoever and for any +purposes, including without limitation commercial purposes. These owners may +contribute to the Commons to promote the ideal of a free culture and the +further production of creative, cultural and scientific works, or to gain +reputation or greater distribution for their Work in part through the use and +efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with a +Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not limited +to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + + iii. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + + v. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + + vii. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time +extensions), (iii) in any current or future medium and for any number of +copies, and (iv) for any purpose whatsoever, including without limitation +commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes +the Waiver for the benefit of each member of the public at large and to the +detriment of Affirmer's heirs and successors, fully intending that such Waiver +shall not be subject to revocation, rescission, cancellation, termination, or +any other legal or equitable action to disrupt the quiet enjoyment of the Work +by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver +is so judged Affirmer hereby grants to each affected person a royalty-free, +non transferable, non sublicensable, non exclusive, irrevocable and +unconditional license to exercise Affirmer's Copyright and Related Rights in +the Work (i) in all territories worldwide, (ii) for the maximum duration +provided by applicable law or treaty (including future time extensions), (iii) +in any current or future medium and for any number of copies, and (iv) for any +purpose whatsoever, including without limitation commercial, advertising or +promotional purposes (the "License"). The License shall be deemed effective as +of the date CC0 was applied by Affirmer to the Work. Should any part of the +License for any reason be judged legally invalid or ineffective under +applicable law, such partial invalidity or ineffectiveness shall not +invalidate the remainder of the License, and in such case Affirmer hereby +affirms that he or she will not (i) exercise any of his or her remaining +Copyright and Related Rights in the Work or (ii) assert any associated claims +and causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + + b. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, fitness + for a particular purpose, non infringement, or the absence of latent or + other defects, accuracy, or the present or absence of errors, whether or not + discoverable, all to the greatest extent permissible under applicable law. + + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without limitation + any person's Copyright and Related Rights in the Work. Further, Affirmer + disclaims responsibility for obtaining any necessary consents, permissions + or other rights required for any use of the Work. + + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see + diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/README.md b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/README.md new file mode 100644 index 0000000..71c02ba --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/README.md @@ -0,0 +1,189 @@ +*__Important:__ `bats-core` has been renamed to `bats-support`. GitHub +automatically redirects all references, e.g. submodules and clones will +continue to work, but you are encouraged to [update][github-rename] +them. Version numbering continues where `bats-core` left off.* + +[github-rename]: https://help.github.com/articles/renaming-a-repository/ + +- - - - - + +# bats-support + +[![GitHub license](https://img.shields.io/badge/license-CC0-blue.svg)](https://raw.githubusercontent.com/ztombol/bats-support/master/LICENSE) +[![GitHub release](https://img.shields.io/github/release/ztombol/bats-support.svg)](https://github.com/ztombol/bats-support/releases/latest) +[![Build Status](https://travis-ci.org/ztombol/bats-support.svg?branch=master)](https://travis-ci.org/ztombol/bats-support) + +`bats-support` is a supporting library providing common functions to +test helper libraries written for [Bats][bats]. + +Features: +- [error reporting](#error-reporting) +- [output formatting](#output-formatting) +- [language tools](#language-and-execution) + +See the [shared documentation][bats-docs] to learn how to install and +load this library. + +If you want to use this library in your own helpers or just want to +learn about its internals see the developer documentation in the [source +files](src). + + +## Error reporting + +### `fail` + +Display an error message and fail. This function provides a convenient +way to report failure in arbitrary situations. You can use it to +implement your own helpers when the ones available do not meet your +needs. Other functions use it internally as well. + +```bash +@test 'fail()' { + fail 'this test always fails' +} +``` + +The message can also be specified on the standard input. + +```bash +@test 'fail() with pipe' { + echo 'this test always fails' | fail +} +``` + +This function always fails and simply outputs the given message. + +``` +this test always fails +``` + + +## Output formatting + +Many test helpers need to produce human readable output. This library +provides a simple way to format simple messages and key value pairs, and +display them on the standard error. + + +### Simple message + +Simple messages without structure, e.g. one-line error messages, are +simply wrapped in a header and a footer to help them stand out. + +``` +-- ERROR: assert_output -- +`--partial' and `--regexp' are mutually exclusive +-- +``` + + +### Key-Value pairs + +Some helpers, e.g. [assertions][bats-assert], structure output as +key-value pairs. This library provides two ways to format them. + +When the value is one line long, a pair can be displayed in a columnar +fashion called ***two-column*** format. + +``` +-- output differs -- +expected : want +actual : have +-- +``` + +When the value is longer than one line, the key and value must be +displayed on separate lines. First, the key is displayed along with the +number of lines in the value. Then, the value, indented by two spaces +for added readability, starting on the next line. This is called +***multi-line*** format. + +``` +-- command failed -- +status : 1 +output (2 lines): + Error! Something went terribly wrong! + Our engineers are panicing... \`>`;/ +-- +``` + +Sometimes, for clarity, it is a good idea to display related values also +in this format, even if they are just one line long. + +``` +-- output differs -- +expected (1 lines): + want +actual (3 lines): + have 1 + have 2 + have 3 +-- +``` + +## Language and Execution + +### Restricting invocation to specific locations + +Sometimes a helper may work properly only when called from a certain +location. Because it depends on variables to be set or some other side +effect. + +A good example is cleaning up temporary files only if the test has +succeeded. The outcome of a test is only available in `teardown`. Thus, +to avoid programming mistakes, it makes sense to restrict such a +clean-up helper to that function. + +`batslib_is_caller` checks the call stack and returns `0` if the caller +was invoked from a given function, and `1` otherwise. This function +becomes really useful with the `--indirect` option, which allows calls +through intermediate functions, e.g. the calling function may be called +from a function that was called from the given function. + +Staying with the example above, the following code snippet implements a +helper that is restricted to `teardown` or any function called +indirectly from it. + +```shell +clean_up() { + # Check caller. + if batslib_is_caller --indirect 'teardown'; then + echo "Must be called from \`teardown'" \ + | batslib_decorate 'ERROR: clean_up' \ + | fail + return $? + fi + + # Body goes here... +} +``` + +In some cases a helper may be called from multiple locations. For +example, a logging function that uses the test name, description or +number, information only available in `setup`, `@test` or `teardown`, to +distinguish entries. The following snippet implements this restriction. + +```shell +log_test() { + # Check caller. + if ! ( batslib_is_caller --indirect 'setup' \ + || batslib_is_caller --indirect "$BATS_TEST_NAME" \ + || batslib_is_caller --indirect 'teardown' ) + then + echo "Must be called from \`setup', \`@test' or \`teardown'" \ + | batslib_decorate 'ERROR: log_test' \ + | fail + return $? + fi + + # Body goes here... +} +``` + + + + +[bats]: https://github.com/sstephenson/bats +[bats-docs]: https://github.com/ztombol/bats-docs +[bats-assert]: https://github.com/ztombol/bats-assert diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/load.bash b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/load.bash new file mode 100644 index 0000000..0727aeb --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/load.bash @@ -0,0 +1,3 @@ +source "$(dirname "${BASH_SOURCE[0]}")/src/output.bash" +source "$(dirname "${BASH_SOURCE[0]}")/src/error.bash" +source "$(dirname "${BASH_SOURCE[0]}")/src/lang.bash" diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/package.json b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/package.json new file mode 100644 index 0000000..192d16a --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/package.json @@ -0,0 +1,5 @@ +{ + "name": "bats-support", + "version": "0.3.0", + "private": true +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/script/install-bats.sh b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/script/install-bats.sh new file mode 100755 index 0000000..4c3161a --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/script/install-bats.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -o errexit +set -o xtrace + +git clone --depth 1 https://github.com/sstephenson/bats +cd bats && ./install.sh "${HOME}/.local" && cd .. && rm -rf bats diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/src/error.bash b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/src/error.bash new file mode 100644 index 0000000..e5d9791 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/src/error.bash @@ -0,0 +1,41 @@ +# +# bats-support - Supporting library for Bats test helpers +# +# Written in 2016 by Zoltan Tombol +# +# To the extent possible under law, the author(s) have dedicated all +# copyright and related and neighboring rights to this software to the +# public domain worldwide. This software is distributed without any +# warranty. +# +# You should have received a copy of the CC0 Public Domain Dedication +# along with this software. If not, see +# . +# + +# +# error.bash +# ---------- +# +# Functions implementing error reporting. Used by public helper +# functions or test suits directly. +# + +# Fail and display a message. When no parameters are specified, the +# message is read from the standard input. Other functions use this to +# report failure. +# +# Globals: +# none +# Arguments: +# $@ - [=STDIN] message +# Returns: +# 1 - always +# Inputs: +# STDIN - [=$@] message +# Outputs: +# STDERR - message +fail() { + (( $# == 0 )) && batslib_err || batslib_err "$@" + return 1 +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/src/lang.bash b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/src/lang.bash new file mode 100644 index 0000000..c57e299 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/src/lang.bash @@ -0,0 +1,73 @@ +# +# bats-util - Various auxiliary functions for Bats +# +# Written in 2016 by Zoltan Tombol +# +# To the extent possible under law, the author(s) have dedicated all +# copyright and related and neighboring rights to this software to the +# public domain worldwide. This software is distributed without any +# warranty. +# +# You should have received a copy of the CC0 Public Domain Dedication +# along with this software. If not, see +# . +# + +# +# lang.bash +# --------- +# +# Bash language and execution related functions. Used by public helper +# functions. +# + +# Check whether the calling function was called from a given function. +# +# By default, direct invocation is checked. The function succeeds if the +# calling function was called directly from the given function. In other +# words, if the given function is the next element on the call stack. +# +# When `--indirect' is specified, indirect invocation is checked. The +# function succeeds if the calling function was called from the given +# function with any number of intermediate calls. In other words, if the +# given function can be found somewhere on the call stack. +# +# Direct invocation is a form of indirect invocation with zero +# intermediate calls. +# +# Globals: +# FUNCNAME +# Options: +# -i, --indirect - check indirect invocation +# Arguments: +# $1 - calling function's name +# Returns: +# 0 - current function was called from the given function +# 1 - otherwise +batslib_is_caller() { + local -i is_mode_direct=1 + + # Handle options. + while (( $# > 0 )); do + case "$1" in + -i|--indirect) is_mode_direct=0; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + # Arguments. + local -r func="$1" + + # Check call stack. + if (( is_mode_direct )); then + [[ $func == "${FUNCNAME[2]}" ]] && return 0 + else + local -i depth + for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do + [[ $func == "${FUNCNAME[$depth]}" ]] && return 0 + done + fi + + return 1 +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/src/output.bash b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/src/output.bash new file mode 100644 index 0000000..c6cf6a6 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/src/output.bash @@ -0,0 +1,279 @@ +# +# bats-support - Supporting library for Bats test helpers +# +# Written in 2016 by Zoltan Tombol +# +# To the extent possible under law, the author(s) have dedicated all +# copyright and related and neighboring rights to this software to the +# public domain worldwide. This software is distributed without any +# warranty. +# +# You should have received a copy of the CC0 Public Domain Dedication +# along with this software. If not, see +# . +# + +# +# output.bash +# ----------- +# +# Private functions implementing output formatting. Used by public +# helper functions. +# + +# Print a message to the standard error. When no parameters are +# specified, the message is read from the standard input. +# +# Globals: +# none +# Arguments: +# $@ - [=STDIN] message +# Returns: +# none +# Inputs: +# STDIN - [=$@] message +# Outputs: +# STDERR - message +batslib_err() { + { if (( $# > 0 )); then + echo "$@" + else + cat - + fi + } >&2 +} + +# Count the number of lines in the given string. +# +# TODO(ztombol): Fix tests and remove this note after #93 is resolved! +# NOTE: Due to a bug in Bats, `batslib_count_lines "$output"' does not +# give the same result as `${#lines[@]}' when the output contains +# empty lines. +# See PR #93 (https://github.com/sstephenson/bats/pull/93). +# +# Globals: +# none +# Arguments: +# $1 - string +# Returns: +# none +# Outputs: +# STDOUT - number of lines +batslib_count_lines() { + local -i n_lines=0 + local line + while IFS='' read -r line || [[ -n $line ]]; do + (( ++n_lines )) + done < <(printf '%s' "$1") + echo "$n_lines" +} + +# Determine whether all strings are single-line. +# +# Globals: +# none +# Arguments: +# $@ - strings +# Returns: +# 0 - all strings are single-line +# 1 - otherwise +batslib_is_single_line() { + for string in "$@"; do + (( $(batslib_count_lines "$string") > 1 )) && return 1 + done + return 0 +} + +# Determine the length of the longest key that has a single-line value. +# +# This function is useful in determining the correct width of the key +# column in two-column format when some keys may have multi-line values +# and thus should be excluded. +# +# Globals: +# none +# Arguments: +# $odd - key +# $even - value of the previous key +# Returns: +# none +# Outputs: +# STDOUT - length of longest key +batslib_get_max_single_line_key_width() { + local -i max_len=-1 + while (( $# != 0 )); do + local -i key_len="${#1}" + batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len" + shift 2 + done + echo "$max_len" +} + +# Print key-value pairs in two-column format. +# +# Keys are displayed in the first column, and their corresponding values +# in the second. To evenly line up values, the key column is fixed-width +# and its width is specified with the first parameter (possibly computed +# using `batslib_get_max_single_line_key_width'). +# +# Globals: +# none +# Arguments: +# $1 - width of key column +# $even - key +# $odd - value of the previous key +# Returns: +# none +# Outputs: +# STDOUT - formatted key-value pairs +batslib_print_kv_single() { + local -ir col_width="$1"; shift + while (( $# != 0 )); do + printf '%-*s : %s\n' "$col_width" "$1" "$2" + shift 2 + done +} + +# Print key-value pairs in multi-line format. +# +# The key is displayed first with the number of lines of its +# corresponding value in parenthesis. Next, starting on the next line, +# the value is displayed. For better readability, it is recommended to +# indent values using `batslib_prefix'. +# +# Globals: +# none +# Arguments: +# $odd - key +# $even - value of the previous key +# Returns: +# none +# Outputs: +# STDOUT - formatted key-value pairs +batslib_print_kv_multi() { + while (( $# != 0 )); do + printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )" + printf '%s\n' "$2" + shift 2 + done +} + +# Print all key-value pairs in either two-column or multi-line format +# depending on whether all values are single-line. +# +# If all values are single-line, print all pairs in two-column format +# with the specified key column width (identical to using +# `batslib_print_kv_single'). +# +# Otherwise, print all pairs in multi-line format after indenting values +# with two spaces for readability (identical to using `batslib_prefix' +# and `batslib_print_kv_multi') +# +# Globals: +# none +# Arguments: +# $1 - width of key column (for two-column format) +# $even - key +# $odd - value of the previous key +# Returns: +# none +# Outputs: +# STDOUT - formatted key-value pairs +batslib_print_kv_single_or_multi() { + local -ir width="$1"; shift + local -a pairs=( "$@" ) + + local -a values=() + local -i i + for (( i=1; i < ${#pairs[@]}; i+=2 )); do + values+=( "${pairs[$i]}" ) + done + + if batslib_is_single_line "${values[@]}"; then + batslib_print_kv_single "$width" "${pairs[@]}" + else + local -i i + for (( i=1; i < ${#pairs[@]}; i+=2 )); do + pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )" + done + batslib_print_kv_multi "${pairs[@]}" + fi +} + +# Prefix each line read from the standard input with the given string. +# +# Globals: +# none +# Arguments: +# $1 - [= ] prefix string +# Returns: +# none +# Inputs: +# STDIN - lines +# Outputs: +# STDOUT - prefixed lines +batslib_prefix() { + local -r prefix="${1:- }" + local line + while IFS='' read -r line || [[ -n $line ]]; do + printf '%s%s\n' "$prefix" "$line" + done +} + +# Mark select lines of the text read from the standard input by +# overwriting their beginning with the given string. +# +# Usually the input is indented by a few spaces using `batslib_prefix' +# first. +# +# Globals: +# none +# Arguments: +# $1 - marking string +# $@ - indices (zero-based) of lines to mark +# Returns: +# none +# Inputs: +# STDIN - lines +# Outputs: +# STDOUT - lines after marking +batslib_mark() { + local -r symbol="$1"; shift + # Sort line numbers. + set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" ) + + local line + local -i idx=0 + while IFS='' read -r line || [[ -n $line ]]; do + if (( ${1:--1} == idx )); then + printf '%s\n' "${symbol}${line:${#symbol}}" + shift + else + printf '%s\n' "$line" + fi + (( ++idx )) + done +} + +# Enclose the input text in header and footer lines. +# +# The header contains the given string as title. The output is preceded +# and followed by an additional newline to make it stand out more. +# +# Globals: +# none +# Arguments: +# $1 - title +# Returns: +# none +# Inputs: +# STDIN - text +# Outputs: +# STDOUT - decorated text +batslib_decorate() { + echo + echo "-- $1 --" + cat - + echo '--' + echo +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-10-batslib_err.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-10-batslib_err.bats new file mode 100755 index 0000000..8c27fd1 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-10-batslib_err.bats @@ -0,0 +1,16 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_err() : displays ' { + run batslib_err 'm1' 'm2' + [ "$status" -eq 0 ] + [ "$output" == 'm1 m2' ] +} + +@test 'batslib_err(): reads from STDIN' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + echo 'm1' 'm2' | batslib_err" + [ "$status" -eq 0 ] + [ "$output" == 'm1 m2' ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-11-batslib_count_lines.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-11-batslib_count_lines.bats new file mode 100755 index 0000000..ea172c3 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-11-batslib_count_lines.bats @@ -0,0 +1,21 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_count_lines() : displays the number of lines in ' { + run batslib_count_lines $'a\nb\nc\n' + [ "$status" -eq 0 ] + [ "$output" == '3' ] +} + +@test 'batslib_count_lines() : counts the last line when it is not terminated by a newline' { + run batslib_count_lines $'a\nb\nc' + [ "$status" -eq 0 ] + [ "$output" == '3' ] +} + +@test 'batslib_count_lines() : counts empty lines' { + run batslib_count_lines $'\n\n\n' + [ "$status" -eq 0 ] + [ "$output" == '3' ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-12-batslib_is_single_line.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-12-batslib_is_single_line.bats new file mode 100755 index 0000000..484b64d --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-12-batslib_is_single_line.bats @@ -0,0 +1,13 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_is_single_line() : returns 0 if all are single-line' { + run batslib_is_single_line 'a' $'b\n' 'c' + [ "$status" -eq 0 ] +} + +@test 'batslib_is_single_line() : returns 1 if at least one of is longer than one line' { + run batslib_is_single_line 'a' $'b\nb' 'c' + [ "$status" -eq 1 ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-13-batslib_get_max_single_line_key_width.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-13-batslib_get_max_single_line_key_width.bats new file mode 100755 index 0000000..e6af161 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-13-batslib_get_max_single_line_key_width.bats @@ -0,0 +1,21 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_get_max_single_line_key_width() : displays the length of the longest key' { + local -ar pairs=( 'k _1' 'v 1' + 'k 2' 'v 2' + 'k __3' 'v 3' ) + run batslib_get_max_single_line_key_width "${pairs[@]}" + [ "$status" -eq 0 ] + [ "$output" == '5' ] +} + +@test 'batslib_get_max_single_line_key_width() : only considers keys with single-line values' { + local -ar pairs=( 'k _1' 'v 1' + 'k 2' 'v 2' + 'k __3' $'v\n3' ) + run batslib_get_max_single_line_key_width "${pairs[@]}" + [ "$status" -eq 0 ] + [ "$output" == '4' ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-14-batslib_print_kv_single.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-14-batslib_print_kv_single.bats new file mode 100755 index 0000000..7637897 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-14-batslib_print_kv_single.bats @@ -0,0 +1,27 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_print_kv_single() : displays in two-column format with wide key column' { + local -ar pairs=( 'k _1' 'v 1' + 'k 2 ' 'v 2' + 'k __3' 'v 3' ) + run batslib_print_kv_single 5 "${pairs[@]}" + [ "$status" -eq 0 ] + [ "${#lines[@]}" == '3' ] + [ "${lines[0]}" == 'k _1 : v 1' ] + [ "${lines[1]}" == 'k 2 : v 2' ] + [ "${lines[2]}" == 'k __3 : v 3' ] +} + +@test 'batslib_print_kv_single() : does not truncate keys when the column is too narrow' { + local -ar pairs=( 'k _1' 'v 1' + 'k 2' 'v 2' + 'k __3' 'v 3' ) + run batslib_print_kv_single 0 "${pairs[@]}" + [ "$status" -eq 0 ] + [ "${#lines[@]}" == '3' ] + [ "${lines[0]}" == 'k _1 : v 1' ] + [ "${lines[1]}" == 'k 2 : v 2' ] + [ "${lines[2]}" == 'k __3 : v 3' ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-15-batslib_print_kv_multi.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-15-batslib_print_kv_multi.bats new file mode 100755 index 0000000..6ad4b3d --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-15-batslib_print_kv_multi.bats @@ -0,0 +1,19 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_print_kv_multi() : displays in multi-line format' { + local -ar pairs=( 'k _1' 'v 1' + 'k 2' $'v 2-1\nv 2-2' + 'k __3' 'v 3' ) + run batslib_print_kv_multi "${pairs[@]}" + [ "$status" -eq 0 ] + [ "${#lines[@]}" == '7' ] + [ "${lines[0]}" == 'k _1 (1 lines):' ] + [ "${lines[1]}" == 'v 1' ] + [ "${lines[2]}" == 'k 2 (2 lines):' ] + [ "${lines[3]}" == 'v 2-1' ] + [ "${lines[4]}" == 'v 2-2' ] + [ "${lines[5]}" == 'k __3 (1 lines):' ] + [ "${lines[6]}" == 'v 3' ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-16-batslib_print_kv_single_or_multi.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-16-batslib_print_kv_single_or_multi.bats new file mode 100755 index 0000000..c20d101 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-16-batslib_print_kv_single_or_multi.bats @@ -0,0 +1,31 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_print_kv_single_or_multi() : displays in two-column format if all values are one line long' { + local -ar pairs=( 'k _1' 'v 1' + 'k 2 ' 'v 2' + 'k __3' 'v 3' ) + run batslib_print_kv_single_or_multi 5 "${pairs[@]}" + [ "$status" -eq 0 ] + [ "${#lines[@]}" == '3' ] + [ "${lines[0]}" == 'k _1 : v 1' ] + [ "${lines[1]}" == 'k 2 : v 2' ] + [ "${lines[2]}" == 'k __3 : v 3' ] +} + +@test 'batslib_print_kv_single_or_multi() : displays in multi-line format if at least one value is longer than one line' { + local -ar pairs=( 'k _1' 'v 1' + 'k 2' $'v 2-1\nv 2-2' + 'k __3' 'v 3' ) + run batslib_print_kv_single_or_multi 5 "${pairs[@]}" + [ "$status" -eq 0 ] + [ "${#lines[@]}" == '7' ] + [ "${lines[0]}" == 'k _1 (1 lines):' ] + [ "${lines[1]}" == ' v 1' ] + [ "${lines[2]}" == 'k 2 (2 lines):' ] + [ "${lines[3]}" == ' v 2-1' ] + [ "${lines[4]}" == ' v 2-2' ] + [ "${lines[5]}" == 'k __3 (1 lines):' ] + [ "${lines[6]}" == ' v 3' ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-17-batslib_prefix.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-17-batslib_prefix.bats new file mode 100755 index 0000000..817fd33 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-17-batslib_prefix.bats @@ -0,0 +1,43 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_prefix() : prefixes each line of the input with ' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf 'a\nb\nc\n' | batslib_prefix '_'" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '_a' ] + [ "${lines[1]}" == '_b' ] + [ "${lines[2]}" == '_c' ] +} + +@test 'batslib_prefix() : prefixes the last line when it is not terminated by a newline' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf 'a\nb\nc' | batslib_prefix '_'" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '_a' ] + [ "${lines[1]}" == '_b' ] + [ "${lines[2]}" == '_c' ] +} + +@test 'batslib_prefix() : prefixes empty lines' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf '\n\n\n' | batslib_prefix '_'" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '_' ] + [ "${lines[1]}" == '_' ] + [ "${lines[2]}" == '_' ] +} + +@test 'batslib_prefix(): default to two spaces' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf 'a\nb\nc\n' | batslib_prefix" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == ' a' ] + [ "${lines[1]}" == ' b' ] + [ "${lines[2]}" == ' c' ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-18-batslib_mark.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-18-batslib_mark.bats new file mode 100755 index 0000000..c5d0975 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-18-batslib_mark.bats @@ -0,0 +1,72 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_mark() : marks the -th line of the input with ' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf ' a\n b\n c\n' | batslib_mark '>' 0" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '>a' ] + [ "${lines[1]}" == ' b' ] + [ "${lines[2]}" == ' c' ] +} + +@test 'batslib_mark() : marks multiple lines when is in ascending order' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf ' a\n b\n c\n' | batslib_mark '>' 1 2" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == ' a' ] + [ "${lines[1]}" == '>b' ] + [ "${lines[2]}" == '>c' ] +} + +@test 'batslib_mark() : marks multiple lines when is in random order' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf ' a\n b\n c\n d\n' | batslib_mark '>' 2 1 3" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 4 ] + [ "${lines[0]}" == ' a' ] + [ "${lines[1]}" == '>b' ] + [ "${lines[2]}" == '>c' ] + [ "${lines[3]}" == '>d' ] +} + +@test 'batslib_mark() : ignores duplicate indices' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf ' a\n b\n c\n' | batslib_mark '>' 1 2 1" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == ' a' ] + [ "${lines[1]}" == '>b' ] + [ "${lines[2]}" == '>c' ] +} + +@test 'batslib_mark() : outputs the input untouched if is the empty string' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf ' a\n b\n c\n' | batslib_mark '' 1" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == ' a' ] + [ "${lines[1]}" == ' b' ] + [ "${lines[2]}" == ' c' ] +} + +@test 'batslib_mark() : marks the last line when it is not terminated by a newline' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf ' a\n b\n c' | batslib_mark '>' 2" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == ' a' ] + [ "${lines[1]}" == ' b' ] + [ "${lines[2]}" == '>c' ] +} + +@test 'batslib_mark() : does not truncate if it is longer than the marked line' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf '\n' | batslib_mark '>' 0" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 1 ] + [ "${lines[0]}" == '>' ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-19-batslib_decorate.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-19-batslib_decorate.bats new file mode 100755 index 0000000..02d55ad --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-19-batslib_decorate.bats @@ -0,0 +1,13 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_decorate() : encloses the input in a footer line and a header line containing <title>' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + echo 'body' | batslib_decorate 'title'" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- title --' ] + [ "${lines[1]}" == 'body' ] + [ "${lines[2]}" == '--' ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/51-error-10-fail.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/51-error-10-fail.bats new file mode 100755 index 0000000..1d8691a --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/51-error-10-fail.bats @@ -0,0 +1,16 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'fail() <message>: returns 1 and displays <message>' { + run fail 'message' + [ "$status" -eq 1 ] + [ "$output" == 'message' ] +} + +@test 'fail(): reads <message> from STDIN' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + echo 'message' | fail" + [ "$status" -eq 1 ] + [ "$output" == 'message' ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/52-lang-10-batslib_is_caller.bats b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/52-lang-10-batslib_is_caller.bats new file mode 100755 index 0000000..68fd59b --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/52-lang-10-batslib_is_caller.bats @@ -0,0 +1,88 @@ +#!/usr/bin/env bats + +load 'test_helper' + + +# Test functions +test_func_lvl_2() { + test_func_lvl_1 "$@" +} + +test_func_lvl_1() { + test_func_lvl_0 "$@" +} + +test_func_lvl_0() { + batslib_is_caller "$@" +} + + +# +# Direct invocation +# + +# Interface +@test 'batslib_is_caller() <function>: returns 0 if the current function was called directly from <function>' { + run test_func_lvl_1 test_func_lvl_1 + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'batslib_is_caller() <function>: returns 1 if the current function was not called directly from <function>' { + run test_func_lvl_0 test_func_lvl_1 + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 0 ] +} + +# Correctness +@test 'batslib_is_caller() <function>: the current function does not appear on the call stack' { + run test_func_lvl_0 test_func_lvl_0 + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 0 ] +} + + +# +# Indirect invocation +# + +# Options +test_i_indirect() { + run test_func_lvl_2 "$@" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'batslib_is_caller() -i <function>: enables indirect checking' { + test_i_indirect -i test_func_lvl_2 +} + +@test 'batslib_is_caller() --indirect <function>: enables indirect checking' { + test_i_indirect --indirect test_func_lvl_2 +} + +# Interface +@test 'batslib_is_caller() --indirect <function>: returns 0 if the current function was called indirectly from <function>' { + run test_func_lvl_2 --indirect test_func_lvl_2 + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'batslib_is_caller() --indirect <function>: returns 1 if the current function was not called indirectly from <function>' { + run test_func_lvl_1 --indirect test_func_lvl_2 + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 0 ] +} + +# Correctness +@test 'batslib_is_caller() --indirect <function>: direct invocation is a special case of indirect invocation with zero intermediate calls' { + run test_func_lvl_1 --indirect test_func_lvl_1 + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'batslib_is_caller() --indirect <function>: the current function does not appear on the call stack' { + run test_func_lvl_0 --indirect test_func_lvl_0 + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 0 ] +} diff --git a/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/test_helper.bash b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/test_helper.bash new file mode 100644 index 0000000..ca16775 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/test_helper.bash @@ -0,0 +1,6 @@ +setup() { + export TEST_MAIN_DIR="${BATS_TEST_DIRNAME}/.." + + # Load library. + load '../load' +} diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/DONATIONS.md b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/DONATIONS.md new file mode 100644 index 0000000..fc22058 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/DONATIONS.md @@ -0,0 +1,441 @@ +<!-- START doctoc generated TOC please keep comment here to allow auto update --> +<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [2018-08-14, received $30](#2018-08-14-received-30) +- [2018-08-03, received $8](#2018-08-03-received-8) +- [2018-08-02, received $3 from Patreon](#2018-08-02-received-3-from-patreon) +- [2018-07-31, received $7](#2018-07-31-received-7) +- [2018-07-28, received $2](#2018-07-28-received-2) +- [2018-07-25, received $3](#2018-07-25-received-3) +- [2018-07-20, received $3](#2018-07-20-received-3) +- [2018-06-17, received ~$155 (200 CAD)](#2018-06-17-received-155-200-cad) +- [2018-06-10, received $10](#2018-06-10-received-10) +- [2018-05-25, received $50](#2018-05-25-received-50) + +<!-- END doctoc generated TOC please keep comment here to allow auto update --> + +Below are reports about what is being done with donations, i.e. which commits +are created thanks to them, which new features are added, etc. From the money +I receive I buy myself coffee and organize the time to work on the requested +features, sometimes many days in a row. + +## 2018-08-14, received $30 + + * **Project**: **[Zplugin](https://github.com/zdharma/zplugin)** + * **Goal**: Create a binary Zsh module with one Zplugin optimization and optionally some + other features. + * **Status**: The job is done. + +Thanks to this donation I have finally started to code **[binary Zplugin module]( +https://github.com/zdharma/zplugin#quick-start-module-only)**, which is a big step onward +in evolution of Zplugin. I've implemented and published the module with 3 complete +features: 1) `load` optimization, 2) autocompilation of scripts, 3) profiling of script +load times. + +Commit list: +``` +2018-08-22 7b96fad doc: mod-install.sh +2018-08-22 ba1ba64 module: Update zpmod usage text +2018-08-22 b0d72e8 zplugin,*autoload: `module' command, manages new zdharma/zplugin module +2018-08-22 706bbb3 Update Zsh source files to latest +2018-08-20 b77426f module: source-study builds report with milliseconds without fractions +2018-08-20 c3cc09b module: Updated zpmod_usage, i.a. with `source-study' sub-command +2018-08-20 6190295 module: Go back to subcommand-interface to `zpmod'; simple option parser +2018-08-20 881005f module: Report on sourcing times is shown on `zpmod -S`. Done generation +2018-08-19 e5d046a module: Correct conditions on zwc file vs. script file (after stats) +2018-08-19 1282c21 module: Duration of sourcing a file is measured and stored into a hash +2018-08-18 e080153 module: Overload both `source' and `.' builtins +2018-08-18 580efb8 module: Invoke bin_zcompile with -U option (i.e. no alias expansion) +2018-08-18 b7d9836 module: Custom `source' ensures script is compiled, compiles if not +2018-08-18 1e75a47 module: Code cleanup, vim folding +2018-08-18 a4a02f3 module: Finally working `source'/`.' overload (used options translating) +2018-08-16 99bba56 module: zpmod_usage gained content +2018-08-16 04703cd module: Add the main builtin zpmod with report-append which is working +2018-08-16 cd6dc19 module: my_ztrdup_glen, zp_unmetafy_zalloc +2018-08-16 6d44e36 module: Cleanup, `source' overload after patron leoj3n restarted module +``` + +## 2018-08-03, received $8 + + * **Project**: **[zdharma/history-search-multi-word](https://github.com/zdharma/history-search-multi-word)** + * **Goal**: Allow calling `zle reset-prompt` (Zshell feature). + * **Status**: The job is done. + +A user wanted to be able to call `reset-prompt` Zshell widget without disturbing my project +`history-search-multi-word`. I've implemented the necessary changes to HSMW. + +Commit list: + +``` +2018-08-04 9745d3d hsmw: reset-prompt-protect zstyle – allow users to run zle reset-prompt +2018-08-04 ce48a53 hsmw: More typo-like lackings of % substitution +2018-08-04 7e2d79b hsmw: A somewhat typo, missing % substitution +``` + +## 2018-08-02, received $3 from Patreon + + * **Project**: **[zdharma/fast-syntax-highlighting](https://github.com/zdharma/fast-syntax-highlighting)** + * **Goal**: No goal set up. + * **Status**: Bug-fixing work. + +I did bug-fixing run on `fast-syntax-highlighting`, spotted many small and sometimes important things to +improve. Did one bigger thing – added global-aliases functionality. + +Commit list: + +``` +2018-08-02 1e854f5 -autoload.ch: Don't check existence for arguments that are variables +2018-08-02 14cdc5e *-string-*: Support highlighter cooperation in presence of $PREBUFFER +2018-08-02 2d8f0e4 *-highlight: Correctly highlight $VAR, $~VAR, ${+VAR}, etc. in strings +2018-08-02 e3032d9 *-highlight: ${#PREBUFFER} -> __PBUFLEN, equal performance +2018-08-02 f0a7121 *-highlight: Make case conditions and brackets highlighter compatible +2018-08-02 781f68e *-highlight: Recognize more case-item-end tokens +2018-08-02 206c122 *-highlight: Remove unused 4th __arg_type +2018-08-02 c6da477 *-string-*: Handle 'abc\' – no slash-quoting here. Full quoting support +2018-08-02 52e0176 *-string-*: Fix bug, third level was getting wrong style +2018-08-02 5edbfae -git.ch: Support "--message=..." syntax (commit) +2018-08-02 669d4b7 -git.ch: Handle "--" argument (stops options) +2018-08-02 4fae1f2 -make.ch: Handle make's -f option +2018-08-02 3fd32fe -make.ch: Handle make's -C option +2018-08-02 31751f5 -make.ch: Recognize options that obtain argument +2018-08-02 e480f18 -make.ch: Fix reply-var clash, gained consistency +2018-08-02 0e8bc1e Updated README.md +2018-08-02 eee0034 images: global-alias.png +2018-08-02 00b41ef *-highlight,themes,fast-theme: Support for global aliases #41 +``` + +## 2018-07-31, received $7 + + * **Project**: **[zdharma/fast-syntax-highlighting](https://github.com/zdharma/fast-syntax-highlighting)** + * **Goal**: Implement ideal brackets highlighting. + * **Status**: The job is done. + +When a source code is edited e.g. in `Notepad++` or some IDE, then most often brackets are somehow matched to +each other, so that the programmer can detect mistakes. `Fast-syntax-highlighting` too gained that feature. It +was done in such a way that FSH cannot make any mistake, colors will perfectly match brackets to each other. + +Commit list: + +``` +2018-07-31 2889860 *-highlight: Correct place to initialize $_FAST_COMPLEX_BRACKETS +2018-07-31 2bde2a9 Performance status -15/8/8 +2018-07-31 5078261 *-highlight,README: Brackets highlighter active by default +2018-07-31 2ee3073 *-highlight,*string-*: Brackets in [[..]], ((..)), etc. handled normally +2018-07-31 776b12d plugin.zsh: $_ZSH_HIGHLIGHT_MAIN_CACHE -> $_FAST_MAIN_CACHE +2018-07-30 2867712 plugin.zsh: Fix array parameter created without declaring #43 +2018-07-30 cbe5fc8 Updated README.md +2018-07-30 2bd3291 images: brackets.gif +2018-07-30 ef23a96 *-string-*: Bug-fix, correctly use theme styles +2018-07-30 9046f82 plugin.zsh: Attach the new brackets highlighter; F_H[use_brackets]=1 +2018-07-30 b33a5fd fast-theme: Support 4 new styles (for brackets) +2018-07-30 a03f004 themes: Add 4 new styles (brackets) +2018-07-30 2448cdc *-string-*: Additional highlight of bracket under cursor; more styles +2018-07-30 5e1795e *-string-*: Highlighter for brackets, handles all quotings; detached +``` + +## 2018-07-28, received $2 + + * **Project**: **[zdharma/fast-syntax-highlighting](https://github.com/zdharma/fast-syntax-highlighting)** + * **Goal**: Distinguish file and directory when highlighting + * **Status**: The job is done. + +A user requested that when `fast-syntax-highlighting` colorizes the command line it should use different +styles (e.g. colors) for token that's a *file* and that's a *directory*. It was a reasonable idea and I've +implemented it. + +Commit list: +``` +2018-07-28 7f48e04 themes: Extend all themes with new style `path-to-dir' +2018-07-28 c7c6a91 fast-theme: Support for new style `path-to-dir' +2018-07-28 264676c *-highlight: Differentiate path and to-dir path. New style: path-to-dir +``` + +## 2018-07-25, received $3 + + * **Project**: **[zdharma/zshelldoc](https://github.com/zdharma/zshelldoc)** + * **Goal**: Implement documenting of used environment variables. + * **Status**: The job is done. + +Zshelldoc generates code-documentation like Doxygen or Javadoc, etc. User requested a +new feature: the generated docs should enumerate environment variables used and/or +exported by every function. Everything went fine and this feature has been implemented. + +Commit list: + +``` +2018-07-26 f63ea25 Updated README.md +2018-07-26 3af0cf7 *detect: Get `var' from ${var:-...} and ${...:+${var}} and other subst +2018-07-25 2932510 *adoc: Better language in output document (about exported vars) #5 +2018-07-25 f858dd8 *adoc: Include (in the output document) data on env-vars used #5 +2018-07-25 80e3763 *adoc: Include data on exports (environment) in the output document #5 +2018-07-25 ca576e2 *detect: Detect which env-vars are used, store meta-data in data/ #5 +2018-07-25 f369dcc *detect: Function `find-variables' reported "$" as a variable, fixed #5 +2018-07-25 e243dab *detect: Function `find-variables' #5 +2018-07-25 5b34bb1 *transform: Detect exports done by function/script-body, store #5 +``` + +## 2018-07-20, received $3 + + * **Project**: **[zdharma/zshelldoc](https://github.com/zdharma/zshelldoc)** + * **Goal**: Implement stripping of leading `#` char from functions' descriptions. + * **Status**: The job is done. + +A user didn't like that functions' descriptions in the JavaDoc-like document (generated with Zshelldoc) all +contain a leading `#` character. I've added stripping of this character (it is there in the processed source +code) controlled by a new Zshelldoc option. + +Commit list: +``` +2018-07-20 172c220 zsd,*adoc,README: Option --scomm to strip "#" from function descriptions +``` + +## 2018-06-17, received ~$155 (200 CAD) + + * **Project**: **[zdharma/fast-syntax-highlighting](https://github.com/zdharma/fast-syntax-highlighting)** + * **Goal**: No goal set up. + * **Status**: Done intense research. + +I've created 2 new branches: `Hue-optimization` (33 commits) and `Tidbits-feature` (22 commits). Those were +branches with architectural changes and extraordinary features. The changes yielded to be too slow, and I had +to withdraw the merge. Below are fixing and optimizing commits (i.e. the valuable ones) that I've restored +from the two branches into master. + +Commit list: +``` +2018-07-21 dab6576 *-highlight: Merge-restore: remove old comments +2018-07-21 637521f *-highlight: Merge-restore: a threshold on # of zle .redisplay calls +2018-07-21 4163d4d *-highlight: Merge-restore: optimize four $__arg[1] = ... cases +2018-07-21 0f01195 *-highlight: Merge-restore: can remove one (Q) dequoting +2018-07-21 39a4ec6 *-highlight: Merge-restore: $v = A* is faster than $v[1] = A, tests: +2018-07-21 99d6b33 *-highlight: Merge-restore: optimize-out ${var:1} Bash syntax +2018-07-21 719c092 *-highlight: Merge-restore: allow $V/cmd, "$V/cmd, "$V/cmd", "${V}/cmd" +2018-07-21 026941d *-highlight: Merge-restore: stack pop in single instruction, not two +2018-07-21 3467e3d *-highlight: Merge-restore: more reasonable redirection-detecting code +2018-07-21 00d25ee *-highlight: Merge-restore: one active_command="$__arg" not needed (?) +2018-07-21 1daa6b3 *-highlight: Merge-restore: simplify ; and \n code short-paths +2018-07-21 55d65be *-highlight: Merge-restore: proc_buf advancement via patterns (not (i)) +2018-07-21 cc55546 *-highlight: Merge-restore: pattern matching to replace (i) flag +``` + +## 2018-06-10, received $10 + + * **Project**: **[zdharma/fast-syntax-highlighting](https://github.com/zdharma/fast-syntax-highlighting)** + * **Goal**: No goal set up. + * **Status**: Done intense experimenting. + +I was working on *chromas* – command-specific colorization. I've added `which` and +`printf` colorization, then added asynchronous path checking (needed on slow network +drives), then coded experimental `ZPath` feature for chromas, but it couldn't be optimized +so I had to resign of it. + +Commit list: +``` +2018-06-12 c4ed1c6 Optimization – the same idea as in previous patch, better method +2018-06-12 c36feef Optimization – a) don't index large buffer, b) with negative index +2018-06-12 2f03829 Performance status 2298 / 1850 +2018-06-12 14f5159 New working feature – ZPath. It requires optimization +2018-06-12 e027c40 -which.ch: One of commands can apparently return via stderr (#27) +2018-06-11 5b8004f New chroma `ruby', works like chroma `perl', checks syntax via -ce opts +2018-06-10 ca2e18b *-highlight: Async path checking has now 8-second cache +2018-06-10 e071469 *-highlight: Remove path-exists queue clearing +2018-06-10 5a6684c *-highlight: Support for asynchronous path checking +2018-06-10 1d7d6f5 New chroma: `printf', highlights special sequences like %s, %20s, etc. +2018-06-10 8f59868 -which.ch: Update main comment on purpose of this chroma +2018-06-10 5f4ece2 -which.ch: Added `whatis', it has only 1st line if output used +2018-06-10 e2d173e -which.ch: Uplift: handle `which' called on a function, /usr/bin/which +``` + +## 2018-05-25, received $50 + + * **Project**: **[zdharma/fast-syntax-highlighting](https://github.com/zdharma/fast-syntax-highlighting)** + * **Goal**: No goal set up. + * **Status**: New ideas and features. + +I was working from May, 25 to June, 9 and came up with key ideas and implemented them. First were *themes* +that were very special because they were using `INI` files instead of some Zsh-script format. Creating themes +for `fast-syntax-highlighting` is thus easy and fun. Then I came up with *chromas*, command-specific +highlighting, which redefine how syntax-highlighting for Zshell works – detailed highlighting for e.g. Git +became possible, the user is informed about e.g. a mistake even before running a command. Overall 178 commits +in 16 days. + +``` +2018-06-09 3f72e6c -git.ch: `revert' works almost like `checkout', attach `revert' there +2018-06-09 b892743 Updated CHROMA_GUIDE.adoc +2018-06-09 f05643d Revert "Revert "Updated CHROMA_GUIDE.md"" +2018-06-09 729bf7f Revert "Revert "CHROMA_GUIDE: Remove redundant comments, uplift"" +2018-06-09 48a4e0c Revert "CHROMA_GUIDE: Remove redundant comments, uplift" +2018-06-09 55ede0a Revert "Updated CHROMA_GUIDE.md" +2018-06-09 17a28ba New chroma `-docker.ch' that verifies image ID passed to `image rm' +2018-06-09 868812a -make.ch,*-make-targets: Check Makefile exists, use 7 second cache, #24 +2018-06-09 73df278 -sh.ch: Attach fish, has -c option, though different syntax, let's try +2018-06-09 3a73b8e Updated CHROMA_GUIDE.md +2018-06-09 29d04c8 CHROMA_GUIDE: Remove redundant comments, uplift +2018-06-09 22ce1d8 -sh.ch,*-highlight: Attach to 2 other shells, Zsh and Bash +2018-06-09 f54e44f New chroma `-sh.ch', colorizes code passed to `sh' with -c option +2018-06-09 f5d2375 CHROMA_GUIDE: Add example code block (rendered broken in mdown) +2018-06-09 08f4b28 CHROMA_GUIDE: Switch to asciidoc (rename) +2018-06-09 4e03609 CHROMA_GUIDE.md +2018-06-09 bbcf2d6 -source.ch: Word "source" should be highlighted as builtin +2018-06-09 6739b8b New chroma – `source' to handle . and source builtins +2018-06-09 b961211 gitignore: ignore more paths +2018-06-09 59d5d09 Updated README.md +2018-06-09 f6d4d19 Updated README.md +2018-06-09 eb31324 Update README.md (figlet logo) +2018-06-09 71dcc5f Performance status 298 / 479 +2018-06-09 00c5f8f *-highlight: Add comments +2018-06-09 232903c -awk.ch: Highlight `sub' function, not working {, } highlighting +2018-06-09 b5241ba *-highlight: Much better $( ) recursion, would say problems-free, maybe +2018-06-08 6c69437 *-highlight: Larger buffer (110 -> 250) for $( ) matching +2018-06-08 f2b7a96 -awk.ch: Syntax check code passed to awk. Awk is very forgiving, though +2018-06-08 c53d8ba -vim.ch: Pass almost everything to big-loop, check if vim exists +2018-06-08 7fbf7cd chroma: New chroma `vim', shows last opened files under prompt +2018-06-08 06e4570 gitignore: Extend .gitignore +2018-06-08 3184ba1 chroma: All chroma functions end chroma mode on e.g. | and similar +2018-06-08 070077d *-highlight,-example.ch: Rename arg_type -> __arg_type, use it to end +2018-06-08 6c2411e -awk.ch: Use the new theme style `subtle-bg' +2018-06-08 9ec8d63 themes: All themes (remaining 4) to support `subtle-bg' style +2018-06-08 66e848b fast-theme: New theme key `subtle-bg', default & clean.ini support it +2018-06-08 1e794f9 -awk.ch: More keywords highlighted +2018-06-08 f3bbaca -awk.ch: Don't highlight keywords when they only contain proper keyword +2018-06-08 e4d5283 -awk.ch: Fix mistake (indices), was highlighting 1 extra trailing letter +2018-06-08 eebbb19 -awk.ch: Initialize FSH_LIST +2018-06-08 8ec24ca *-highlight: Missing math function for awk +2018-06-08 d8e423a -awk.ch: Highlight more keywords, via more general code +2018-06-07 ee26e66 Commit missing -fast-make-targets +2018-06-07 9d4f2b5 New chroma `-awk.ch', colorizes regex characters and a keyword (print) +2018-06-07 def5133 -example.ch: Add comments +2018-06-07 f31a2d0 New chroma -make.ch, verifies if target is correct +2018-06-07 623b8ce -perl.ch: Use correct keys in FAST_HIGHLIGHT hash +2018-06-07 090f420 themes: Make all themes provide {in,}correct-subtle styles +2018-06-07 2201fb6 New -perl.ch chroma, syntax-checks perl code; 2 new theme entries +2018-06-06 4b9598e *-highlight: Fix bug in math highlight – allow variables starting with _ +2018-06-06 708afec *-highlight: Fix FAST_BLIST_PATTERNS not expanding path to absolute one +2018-06-06 caef05a -example.ch: Update, fix typos, remove unused code +2018-06-06 3fb192a Updated README.md +2018-06-06 6de0c82 images: git_chroma.png +2018-06-06 2852fdd -grep.ch (new): Special highlighting for grep – -grep.ch chroma function +2018-06-06 f216785 -example.ch: Added comments +2018-06-06 4ab5b36 -example.ch: Add comments +2018-06-06 380cd12 -example.ch: Added comments +2018-06-06 c8947cc -example.ch: Add comments +2018-06-06 f2e273e -example.ch: Add comments +2018-06-06 2f3565b plugin.zsh: Fix parse error +2018-06-06 4f1a9bd plugin.zsh: Added $fpath handling, to match what README contains +2018-06-06 cc9adb5 -example.ch: Change and extend comments +2018-06-06 3128fff -git.ch: More intelligent `checkout' highlighting – ref is first +2018-06-06 4b6f54b -git.ch: Support for `checkout' subcommand +2018-06-06 1930d37 -example.ch: Added example chroma function +2018-06-05 d79cd85 -git.ch: Add comments +2018-06-05 1473c9e -git.ch: Add comments +2018-06-05 0cb1419 -git.ch: Message passed after -m is checked for the 72 chars boundary +2018-06-05 3f99944 -git.ch: Architectural uplift of git chroma +2018-06-05 e044d13 -git.ch: Single place to add entry to $reply (target: region_highlight) +2018-06-05 3a84364 -git.ch: Handle quoted non-option arguments, also partly quoted: "abc +2018-06-05 d635bf4 -fast-run-git-command, it handles cache automatically, decimates source +2018-06-05 102ea78 -git.ch: Smart handling of `git push', remotes and branches are verified +2018-06-04 be88850 Performance status [+] 39+77=116 / -26+24=-2 +2018-06-04 0e033f8 Experimental chroma support, currently active only on command `git' +2018-06-04 43ae221 *-highlight: Emacs mode-line +2018-06-04 938ad29 test: New "-git" parsing option, test results, -git.ch included +2018-06-04 e433fbc fast-theme: Explicitly return 0; added Emacs mode-line +2018-06-04 66e9b3c *-highlight: Detection of $( ) now doesn't go for $(( )) as a candidate +2018-06-04 488a580 chroma: Empty chroma function for `git' +2018-06-04 f54d770 *-highlight: Rename $cur_cmd to $active_command +2018-06-04 3f24e68 *-highlight: Make sudo and always-block compatible with `case' handling +2018-06-02 cd82637 themes: forest.ini to support 3 new `case' styles +2018-06-02 e1e993e themes: safari.ini & zdharma.ini to support 3 new `case' styles +2018-06-02 2e78a02 themes: clean.ini & default.ini to support 3 new `case' styles +2018-06-02 c1c3aab themes: free.ini to support 3 new `case' styles +2018-06-02 70a7e18 fast-theme,*-highlight: 3 new styles for `case' higlighting +2018-06-02 8d90dc2 *-highlight: Support for `case' highlighting +2018-06-02 10d291c *-highlight: Softer state manipulation, less rigid =1 etc. assignments +2018-06-02 6159507 *-highlight: Don't highlight closing ) with style `assign' +2018-06-02 1fc2450 *-highlight: One complex math command optimization, top of the loop +2018-06-02 cc49247 *-highlight: Fix improper state after assignment (command | regular) +2018-06-02 02942b8 Updated README.md +2018-06-02 5e28259 images: eval_cmp.png +2018-06-02 df92fed *-highlight: Fix removal of trailing "/' when recursing in eval +2018-06-02 4f61938 Performance status 46 / 44 +2018-06-02 a5ade0e *-highlight: Recursive highlighting of eval string argument +2018-06-02 e91847b *-highlight: Don't recurse when not at main *-process call +2018-06-02 fca8603 *-highlight: Support assignments of arrays when key is taken from array +2018-06-02 5d70f01 *-highlight: Math highlighting recognizes ${+VAR} +2018-06-02 c48eb0d *-highlight: Math colorizing recognizes variables in braces ${HISTISZE} +2018-06-02 53dd85a *-highlight: Allow -- for precommand modifiers command & exec +2018-06-02 d9fe110 *-highlight: Detect globbing also when `##' occurs +2018-06-02 55c923d Performance status 132 / 66 +2018-06-02 3bd8f07 themes: safari.ini to have globbing color specifically selected +2018-06-02 2b55260 themes: free.ini to have globbing color specifically selected +2018-06-02 494868e themes: clean.ini to have globbing color specifically selected +2018-06-01 fca6b3d images: herestring.png #9 +2018-06-01 f9842c1 themes: forest.ini to use underline instead of bg color #9 +2018-06-01 c25c539 themes: Small tune-up of forest & zdharma themes for here-string #9 +2018-06-01 988d504 themes: Rudimentary (all same) configuration of here-string tokens #9 +2018-06-01 99842d2 fast-theme,*-highlight: Support for here-string, can use bg color #9 +2018-06-01 f739c30 Updated README.md +2018-06-01 7fa8451 images: execfd.png execfd_cmp.png +2018-06-01 d7384f1 themes: All themes gained `exec-descriptor=` key, now supported by code +2018-06-01 d66d140 themes: Fix improper effect of s/red/.../ substitution in clean,forest +2018-06-01 f7ee5e2 fast-theme,*-highlight: Support highlighting of {FD} etc. passed to exec +2018-06-01 e5c5534 *-highlight: Proper states for precmd (command,exec) option handling +2018-06-01 647b198 images: New cmdsubst.png +2018-06-01 74bdc4c Updated README.md +2018-06-01 86eb15e images: theme.png +2018-06-01 5169e82 Updated README.md +2018-06-01 1c462b7 Updated README.md +2018-06-01 4c21da4 images: cmdsubst.png +2018-06-01 b39996e *-highlight: Switch theme to secondary when descending into $() #15 +2018-06-01 bf96045 themes: Equip all themes with key `secondary' (an alternate theme) #15 +2018-06-01 aa1b112 fast-theme: Generate secondary theme (from key `secondary' in theme) #15 +2018-06-01 6dd3bd3 *-highlight: Support for multiple active themes #15 +2018-06-01 8a32944 *-highlight: Fix "$() found?" comparison +2018-06-01 3651605 *-highlight: Significant change: the parser is called recursively on $() +2018-05-31 882d88b test,*-highlight: New -ooo performance test; highlighter takes arguments +2018-05-31 5ba1178 *-highlight: Optimization - compute __arg length once +2018-05-30 b2a0126 *-highlight: Allow multiple separate options for `command', `exec' (#10) +2018-05-30 5804e9a *-highlight: Correct state after option for precommand (#10) +2018-05-30 1247b64 *-highlight: Simpler and more accurate option-testing for exec, command (#10) +2018-05-30 d87fed4 *-highlight: Correctly highlight options for `command' and `exec' (#10) +2018-05-30 8c3e75e *-highlight: Double-hyphen (--) stops option recognition and colorizing +2018-05-30 1c5a56c *-highlight: Support ${VAR} at command position (not only $VAR) +2018-05-30 f19d761 Updated README.md +2018-05-30 4a27351 images: for-loop +2018-05-30 4d650de themes: zdharma.ini to support for-loop +2018-05-30 45cafbc themes: safari.ini to support for-loop +2018-05-30 8bb9ee0 themes: free.ini to support for-loop +2018-05-30 f25a059 themes: forest.ini to support for-loop +2018-05-29 093d79e themes: default.ini to support for-loop +2018-05-29 446cb7b clean.ini,fast-theme: Clean-theme & theme subsystem to support for-loop +2018-05-29 1bb701f *-highlight: Move $variable highlighting from case to if-block +2018-05-29 b8413e9 *-highlight: For-loop highlighting, working, needs few upgrades +2018-05-28 7bec6e5 *-highlight: Three more simple vs. complex math operation optimizations +2018-05-27 baae683 *-highlight: Optimise complex math command into single one with & and ~ +2018-05-27 2dc3103 *-highlight: Optimise complex math command into single one with & and ~ +2018-05-27 291f905 _fast-theme: Update -t/--test description +2018-05-27 ec305f6 fast-theme: Help message treats about -t/--test +2018-05-27 0e1d19a Updated README.md +2018-05-27 5c3c911 Updated README.md +2018-05-26 76af248 themes: A fix for zdharma theme, 61 -> 63, a lighter color for builtins +2018-05-26 8eca0f2 *fast-theme: Ability to test theme after setting it (-t/--test) +2018-05-26 d3a7922 *-highlight: Fix in_array_assignment setting when closing ) found +2018-05-26 796c482 *-highlight: Make parameters' names exotic blank-var detection to work +2018-05-26 ae3913f _fast-theme: Complete theme names +2018-05-26 d212945 *-highlight,plugin.zsh,default.ini: Uplift of fg=112-fix code +2018-05-26 ee56f65 *-highlight,plugin.zsh: Final fix for fg=112 assignment – use zstyle +2018-05-26 391f5a4 fast-theme: Set `theme' zstyle in `:plugin:fast...' to given theme +2018-05-26 e0dc086 plugin.zsh: Fix the fg=112 assignment done for `variable' style +2018-05-26 17c9286 Updated README.md +2018-05-26 4774c1c fast-theme: Add completion for this function +2018-05-26 d971f39 fast-theme: Detect lack of theme name in arguments +2018-05-26 74f0d4d fast-theme: Use standard option parsing (zparseopts) and typical options +2018-05-26 d9c6180 New theme: `forest' +2018-05-26 419c156 New theme: `zdharma' +2018-05-26 a7735df gitignore +2018-05-26 99db69a New theme: `free' +2018-05-26 73619ff New theme: `clean' +2018-05-25 52307fb Theme support, 1 extra theme – `safari' +2018-05-25 41df55b *-highlight: (k) subscript flag is sufficient, no need for (K) +2018-05-25 cb21c05 Updated README.md +2018-05-25 a580cff *-highlight: FAST_BLIST_PATTERNS +``` diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/LICENSE b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/LICENSE new file mode 100644 index 0000000..2be3628 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/LICENSE @@ -0,0 +1,700 @@ +This software is dual-licensed under MIT, GPLv3. + +MIT 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: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +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. + +GPLv3 License +-------------- + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/Makefile b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/Makefile new file mode 100644 index 0000000..f673ed2 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/Makefile @@ -0,0 +1,85 @@ +NAME=zshelldoc + +INSTALL?=install -c +PREFIX?=/usr/local +BIN_DIR?=$(DESTDIR)$(PREFIX)/bin +SHARE_DIR?=$(DESTDIR)$(PREFIX)/share/$(NAME) +DOC_DIR?=$(DESTDIR)$(PREFIX)/share/doc/$(NAME) + +all: build/zsd build/zsd-transform build/zsd-detect build/zsd-to-adoc + +build/zsd: src/zsd.preamble src/zsd.main + mkdir -p build + rm -f build/zsd + cat src/zsd.preamble > build/zsd + echo "" >> build/zsd + cat src/zsd.main >> build/zsd + chmod +x build/zsd + +build/zsd-transform: src/zsd-transform.preamble src/zsd-transform.main src/zsd-process-buffer src/zsd-trim-indent + mkdir -p build + rm -f build/zsd-transform + cat src/zsd-transform.preamble > build/zsd-transform + echo "" >> build/zsd-transform + echo "zsd-process-buffer() {" >> build/zsd-transform + cat src/zsd-process-buffer >> build/zsd-transform + echo "}" >> build/zsd-transform + echo "" >> build/zsd-transform + echo "zsd-trim-indent() {" >> build/zsd-transform + cat src/zsd-trim-indent >> build/zsd-transform + echo "}" >> build/zsd-transform + echo "" >> build/zsd-transform + cat src/token-types.mod >> build/zsd-transform + echo "" >> build/zsd-transform + cat src/zsd-transform.main >> build/zsd-transform + chmod +x build/zsd-transform + +build/zsd-detect: src/zsd-detect.preamble src/zsd-detect.main src/zsd-process-buffer src/run-tree-convert.mod src/token-types.mod + mkdir -p build + rm -f build/zsd-detect + cat src/zsd-detect.preamble > build/zsd-detect + echo "" >> build/zsd-detect + echo "zsd-process-buffer() {" >> build/zsd-detect + cat src/zsd-process-buffer >> build/zsd-detect + echo "}" >> build/zsd-detect + echo "" >> build/zsd-detect + cat src/run-tree-convert.mod >> build/zsd-detect + echo "" >> build/zsd-detect + cat src/token-types.mod >> build/zsd-detect + echo "" >> build/zsd-detect + cat src/zsd-detect.main >> build/zsd-detect + chmod +x build/zsd-detect + +build/zsd-to-adoc: src/zsd-to-adoc.preamble src/zsd-to-adoc.main src/zsd-trim-indent + mkdir -p build + rm -f build/zsd-to-adoc + cat src/zsd-to-adoc.preamble > build/zsd-to-adoc + echo "" >> build/zsd-to-adoc + echo "zsd-trim-indent() {" >> build/zsd-to-adoc + cat src/zsd-trim-indent >> build/zsd-to-adoc + echo "}" >> build/zsd-to-adoc + echo "" >> build/zsd-to-adoc + cat src/zsd-to-adoc.main >> build/zsd-to-adoc + chmod +x build/zsd-to-adoc + +install: build/zsd build/zsd-detect build/zsd-transform build/zsd-to-adoc + $(INSTALL) -d $(SHARE_DIR) + $(INSTALL) -d $(DOC_DIR) + $(INSTALL) -d $(BIN_DIR) + cp build/zsd build/zsd-transform build/zsd-detect build/zsd-to-adoc $(BIN_DIR) + cp README.md NEWS LICENSE $(DOC_DIR) + cp zsd.config $(SHARE_DIR) + +uninstall: + rm -f $(BIN_DIR)/zsd $(BIN_DIR)/zsd-transform $(BIN_DIR)/zsd-detect $(BIN_DIR)/zsd-to-adoc + rm -f $(SHARE_DIR)/zsd.config $(DOC_DIR)/README.md $(DOC_DIR)/NEWS $(DOC_DIR)/LICENSE + [ -d $(DOC_DIR) ] && rmdir $(DOC_DIR) || true + [ -d $(SHARE_DIR) ] && rmdir $(SHARE_DIR) || true + +clean: + rm -rf build/* + +test: + make -C test test + +.PHONY: all install uninstall test clean diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/NEWS b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/README.md b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/README.md new file mode 100644 index 0000000..b947fc9 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/README.md @@ -0,0 +1,122 @@ +[![paypal](https://img.shields.io/badge/-Donate-yellow.svg?longCache=true&style=for-the-badge)](https://www.paypal.me/ZdharmaInitiative) +[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=D54B3S7C6HGME) +[![patreon](https://img.shields.io/badge/-Patreon-orange.svg?longCache=true&style=for-the-badge)](https://www.patreon.com/psprint) +<br/>New: You can request a feature when donating, even fancy or advanced ones get implemented this way. [There are +reports](DONATIONS.md) about what is being done with the money received. Patreon members get rewards (currently: +syntax-highlighting themes). + +# Zshelldoc - Doxygen For Shell Scripts + +Parses `Zsh` and `Bash` scripts, outputs `Asciidoc` document with: +- list of functions, including autoload functions, +- call trees of functions and script body, +- comments for functions, +- features used for each function and for script body (features like: `eval`, `read`, `vared`, `shopt`, etc.), +- distinct marks for hooks registered with `add-zsh-hook` (Zsh), +- list of exported variables, +- list of used exported variables, together with the variable's origin (i.e. possibly other script). + +Call trees support cross-file invocations, i.e. when a script calls functiion defined in other file. + +Written in `Zshell` language. + +![image](https://raw.githubusercontent.com/zdharma/zshelldoc/images/env_feat_demo.png) + +# Usage + +``` +zsd [-h/--help] [-v/--verbose] [-q/--quiet] [-n/--noansi] [--cignore <pattern>] {file1} [file2] ... +The files will be processed and their documentation will be generated +in subdirectory `zsdoc' (with meta-data in subdirectory `data'). + +Options: +-h/--help Usage information +-v/--verbose More verbose operation-status output +-q/--quiet No status messages +-n/--noansi No colors in terminal output +--cignore Specify which comment lines should be ignored +-f/--fpath Paths separated by : pointing to directories with functions +--synopsis Text to be used in SYNOPSIS section. Line break "... +\n", paragraph "...\n\n" +--scomm Strip comment char "#" from function comments +--bash Output slightly tailored to Bash specifics (instead of Zsh specifics) + +Example --cignore options: +--cignore '\#*FUNCTION:*{{{*' - ignore comments like: # FUNCTION: usage {{{ +--cignore '(\#*FUNCTION:*{{{*|\#*FUN:*{{{*)' - also ignore comments like: # FUN: usage {{{ + +File is parsed for synopsis block, which can be e.g.: +# synopsis {{{my synopsis, can be multi-line}}} + +Other block that is parsed is commenting on environment variables. It consists of multiple +"VAR_NAME -> var description" lines and results in a table in the output AsciiDoc document, e.g.: +# env-vars {{{ +# PATH -> paths to executables +# MANPATH -> paths to manuals }}} + +Change the default brace block-delimeters with --blocka, --blockb. Block body should be AsciiDoc. +``` + +# Installation + +Clone and issue `make && make install`. Default install path-prefix is `/usr/local`, you +can change it by setting `PREFIX` variable in `make` invocation: + +```SystemVerilog +% make install PREFIX=~/opt/local +install -c -d ~/opt/local/share/zshelldoc +install -c -d ~/opt/local/share/doc/zshelldoc +cp build/zsd build/zsd-transform build/zsd-detect build/zsd-to-adoc ~/opt/local/bin +cp README.md NEWS LICENSE ~/opt/local/share/doc/zshelldoc +cp zsd.config ~/opt/local/share/zshelldoc + +% tree ~/opt +/Users/sgniazdowski/opt +└── local + ├── bin + │   ├── zsd + │   ├── zsd-detect + │   ├── zsd-to-adoc + │   └── zsd-transform + └── share + ├── doc + │   └── zshelldoc + │   ├── LICENSE + │   ├── NEWS + │   └── README.md + └── zshelldoc + └── zsd.config +``` + +Other available `make` variables are: `INSTALL` (to customize install command), +`BIN_DIR`, `SHARE_DIR`, `DOC_DIR`. + +# Examples + +`Zshelldoc` highly motivates to document code, my other project `Zplugin` gained from this. +Also, `Zplugin` documentation demonstrates rich cross-file invocations. +[Check out Zplugin's code documentation](https://github.com/zdharma/zplugin/tree/master/zsdoc). + +For other, in-place examples see: +[example 1](https://github.com/zdharma/zshelldoc/blob/master/examples/zsh-syntax-highlighting.zsh.adoc), +[example 2](https://github.com/zdharma/zshelldoc/blob/master/examples/zsh-autosuggestions.zsh.adoc) +(also in **PDF**: +[example 1](https://raw.githubusercontent.com/zdharma/zshelldoc/master/examples/zsh-syntax-highlighting.zsh.pdf), +[example 2](https://raw.githubusercontent.com/zdharma/zshelldoc/master/examples/zsh-autosuggestions.zsh.pdf)). + +# Few Rules + +Few rules helping to use `Zshelldoc` in your project: + + 1. Write function comments before function. Empty lines between comment and function are allowed. + 1. If you use special comments, e.g. `vim` (or `emacs-origami`) **folds**, you can ignore these lines with `--cignore` (see [Usage](https://github.com/zdharma/zshelldoc#usage)). + 1. If it's possible to avoid `eval`, then do that – `Zshelldoc` will analyze more code. + 1. Currently, functions defined in functions are ignored, but this will change shortly. + 1. I've greatly optimized new `Zsh` version (`5.4.2`) for data processing – `Zshelldoc` parses long sources very fast starting from that `Zsh` version. + 1. If you have multiple `Zsh` versions installed, then (for example) set `zsh_control_bin="/usr/local/bin/zsh-5.4.2"` in `/usr/local/share/zshelldoc/zsd.config`. + 1. Be aware that to convert a group of scripts, you simply need `zsd file1.zsh file2.zsh ...` – cross-file function invocations will work automatically, and multiple `*.adoc` files will be created. + 1. Create `Makefile` with `doc` target, that does `rm -rf zsdoc/data; zsd -v file1.zsh ...`. Documentation will land in `zsdoc` directory. + 1. Directory `zsdoc/data` holds meta-data used to create `asciidoc` documents (`*.adoc` files). You can remove it or analyze it yourself. + 1. Obtain **PDFs** with [Asciidoctor](http://asciidoctor.org/) tool via: `asciidoctor -b pdf -r asciidoctor-pdf file1.zsh.adoc`. Install `Asciidoctor` with: `gem install asciidoctor-pdf --pre`. (Check out [Zplugin's Makefile](https://github.com/zdharma/zplugin/blob/master/zsdoc/Makefile).) + 1. HTML: `asciidoctor script.adoc`. + 1. Obtain manual pages with `Asciidoc` package via: `a2x -L --doctype manpage --format manpage file1.zsh.adoc` (`asciidoc` is a common package; its `a2x` command is little slow). + 1. Github supports `Asciidoc` documents and renders them automatically. diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/.inputs/zsh-autosuggestions.zsh b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/.inputs/zsh-autosuggestions.zsh new file mode 100644 index 0000000..d205b46 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/.inputs/zsh-autosuggestions.zsh @@ -0,0 +1,693 @@ +# Fish-like fast/unobtrusive autosuggestions for zsh. +# https://github.com/zsh-users/zsh-autosuggestions +# v0.4.0 +# Copyright (c) 2013 Thiago de Arruda +# Copyright (c) 2016-2017 Eric Freese +# +# The only licensing for this file follows. +# +# 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: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# 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. + +#--------------------------------------------------------------------# +# Setup # +#--------------------------------------------------------------------# + +# Precmd hooks for initializing the library and starting pty's +autoload -Uz add-zsh-hook + +# Asynchronous suggestions are generated in a pty +zmodload zsh/zpty + +#--------------------------------------------------------------------# +# Global Configuration Variables # +#--------------------------------------------------------------------# + +# Color to use when highlighting suggestion +# Uses format of `region_highlight` +# More info: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets +ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' + +# Prefix to use when saving original versions of bound widgets +ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig- + +ZSH_AUTOSUGGEST_STRATEGY=default + +# Widgets that clear the suggestion +ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( + history-search-forward + history-search-backward + history-beginning-search-forward + history-beginning-search-backward + history-substring-search-up + history-substring-search-down + up-line-or-history + down-line-or-history + accept-line +) + +# Widgets that accept the entire suggestion +ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( + forward-char + end-of-line + vi-forward-char + vi-end-of-line + vi-add-eol +) + +# Widgets that accept the entire suggestion and execute it +ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=( +) + +# Widgets that accept the suggestion as far as the cursor moves +ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( + forward-word + vi-forward-word + vi-forward-word-end + vi-forward-blank-word + vi-forward-blank-word-end +) + +# Widgets that should be ignored (globbing supported but must be escaped) +ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( + orig-\* + beep + run-help + set-local-history + which-command + yank +) + +# Max size of buffer to trigger autosuggestion. Leave undefined for no upper bound. +ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= + +# Pty name for calculating autosuggestions asynchronously +ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty + +#--------------------------------------------------------------------# +# Utility Functions # +#--------------------------------------------------------------------# + +_zsh_autosuggest_escape_command() { + setopt localoptions EXTENDED_GLOB + + # Escape special chars in the string (requires EXTENDED_GLOB) + echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}" +} + +#--------------------------------------------------------------------# +# Feature Detection # +#--------------------------------------------------------------------# + +_zsh_autosuggest_feature_detect_zpty_returns_fd() { + typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD + typeset -h REPLY + + zpty zsh_autosuggest_feature_detect '{ zshexit() { kill -KILL $$; sleep 1 } }' + + if (( REPLY )); then + _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1 + else + _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=0 + fi + + zpty -d zsh_autosuggest_feature_detect +} + +#--------------------------------------------------------------------# +# Widget Helpers # +#--------------------------------------------------------------------# + +_zsh_autosuggest_incr_bind_count() { + if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then + ((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]++)) + else + _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=1 + fi + + bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1] +} + +_zsh_autosuggest_get_bind_count() { + if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then + bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1] + else + bind_count=0 + fi +} + +# Bind a single widget to an autosuggest widget, saving a reference to the original widget +_zsh_autosuggest_bind_widget() { + typeset -gA _ZSH_AUTOSUGGEST_BIND_COUNTS + + local widget=$1 + local autosuggest_action=$2 + local prefix=$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX + + local -i bind_count + + # Save a reference to the original widget + case $widgets[$widget] in + # Already bound + user:_zsh_autosuggest_(bound|orig)_*);; + + # User-defined widget + user:*) + _zsh_autosuggest_incr_bind_count $widget + zle -N $prefix${bind_count}-$widget ${widgets[$widget]#*:} + ;; + + # Built-in widget + builtin) + _zsh_autosuggest_incr_bind_count $widget + eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }" + zle -N $prefix${bind_count}-$widget _zsh_autosuggest_orig_$widget + ;; + + # Completion widget + completion:*) + _zsh_autosuggest_incr_bind_count $widget + eval "zle -C $prefix${bind_count}-${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}" + ;; + esac + + _zsh_autosuggest_get_bind_count $widget + + # Pass the original widget's name explicitly into the autosuggest + # function. Use this passed in widget name to call the original + # widget instead of relying on the $WIDGET variable being set + # correctly. $WIDGET cannot be trusted because other plugins call + # zle without the `-w` flag (e.g. `zle self-insert` instead of + # `zle self-insert -w`). + eval "_zsh_autosuggest_bound_${bind_count}_${(q)widget}() { + _zsh_autosuggest_widget_$autosuggest_action $prefix$bind_count-${(q)widget} \$@ + }" + + # Create the bound widget + zle -N $widget _zsh_autosuggest_bound_${bind_count}_$widget +} + +# Map all configured widgets to the right autosuggest widgets +_zsh_autosuggest_bind_widgets() { + local widget + local ignore_widgets + + ignore_widgets=( + .\* + _\* + zle-\* + autosuggest-\* + $ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX\* + $ZSH_AUTOSUGGEST_IGNORE_WIDGETS + ) + + # Find every widget we might want to bind and bind it appropriately + for widget in ${${(f)"$(builtin zle -la)"}:#${(j:|:)~ignore_widgets}}; do + if [ ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]; then + _zsh_autosuggest_bind_widget $widget clear + elif [ ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]; then + _zsh_autosuggest_bind_widget $widget accept + elif [ ${ZSH_AUTOSUGGEST_EXECUTE_WIDGETS[(r)$widget]} ]; then + _zsh_autosuggest_bind_widget $widget execute + elif [ ${ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS[(r)$widget]} ]; then + _zsh_autosuggest_bind_widget $widget partial_accept + else + # Assume any unspecified widget might modify the buffer + _zsh_autosuggest_bind_widget $widget modify + fi + done +} + +# Given the name of an original widget and args, invoke it, if it exists +_zsh_autosuggest_invoke_original_widget() { + # Do nothing unless called with at least one arg + [ $# -gt 0 ] || return + + local original_widget_name="$1" + + shift + + if [ $widgets[$original_widget_name] ]; then + zle $original_widget_name -- $@ + fi +} + +#--------------------------------------------------------------------# +# Highlighting # +#--------------------------------------------------------------------# + +# If there was a highlight, remove it +_zsh_autosuggest_highlight_reset() { + typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT + + if [ -n "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" ]; then + region_highlight=("${(@)region_highlight:#$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT}") + unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT + fi +} + +# If there's a suggestion, highlight it +_zsh_autosuggest_highlight_apply() { + typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT + + if [ $#POSTDISPLAY -gt 0 ]; then + _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE" + region_highlight+=("$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT") + else + unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT + fi +} + +#--------------------------------------------------------------------# +# Autosuggest Widget Implementations # +#--------------------------------------------------------------------# + +# Disable suggestions +_zsh_autosuggest_disable() { + typeset -g _ZSH_AUTOSUGGEST_DISABLED + _zsh_autosuggest_clear +} + +# Enable suggestions +_zsh_autosuggest_enable() { + unset _ZSH_AUTOSUGGEST_DISABLED + + if [ $#BUFFER -gt 0 ]; then + _zsh_autosuggest_fetch + fi +} + +# Toggle suggestions (enable/disable) +_zsh_autosuggest_toggle() { + if [ -n "${_ZSH_AUTOSUGGEST_DISABLED+x}" ]; then + _zsh_autosuggest_enable + else + _zsh_autosuggest_disable + fi +} + +# Clear the suggestion +_zsh_autosuggest_clear() { + # Remove the suggestion + unset POSTDISPLAY + + _zsh_autosuggest_invoke_original_widget $@ +} + +# Modify the buffer and get a new suggestion +_zsh_autosuggest_modify() { + local -i retval + + # Only added to zsh very recently + local -i KEYS_QUEUED_COUNT + + # Save the contents of the buffer/postdisplay + local orig_buffer="$BUFFER" + local orig_postdisplay="$POSTDISPLAY" + + # Clear suggestion while waiting for next one + unset POSTDISPLAY + + # Original widget may modify the buffer + _zsh_autosuggest_invoke_original_widget $@ + retval=$? + + # Don't fetch a new suggestion if there's more input to be read immediately + if [[ $PENDING > 0 ]] || [[ $KEYS_QUEUED_COUNT > 0 ]]; then + return $retval + fi + + # Optimize if manually typing in the suggestion + if [ $#BUFFER -gt $#orig_buffer ]; then + local added=${BUFFER#$orig_buffer} + + # If the string added matches the beginning of the postdisplay + if [ "$added" = "${orig_postdisplay:0:$#added}" ]; then + POSTDISPLAY="${orig_postdisplay:$#added}" + return $retval + fi + fi + + # Don't fetch a new suggestion if the buffer hasn't changed + if [ "$BUFFER" = "$orig_buffer" ]; then + POSTDISPLAY="$orig_postdisplay" + return $retval + fi + + # Bail out if suggestions are disabled + if [ -n "${_ZSH_AUTOSUGGEST_DISABLED+x}" ]; then + return $? + fi + + # Get a new suggestion if the buffer is not empty after modification + if [ $#BUFFER -gt 0 ]; then + if [ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" -o $#BUFFER -le "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]; then + _zsh_autosuggest_fetch + fi + fi + + return $retval +} + +# Fetch a new suggestion based on what's currently in the buffer +_zsh_autosuggest_fetch() { + if zpty -t "$ZSH_AUTOSUGGEST_ASYNC_PTY_NAME" &>/dev/null; then + _zsh_autosuggest_async_request "$BUFFER" + else + local suggestion + _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY "$BUFFER" + _zsh_autosuggest_suggest "$suggestion" + fi +} + +# Offer a suggestion +_zsh_autosuggest_suggest() { + local suggestion="$1" + + if [ -n "$suggestion" ] && [ $#BUFFER -gt 0 ]; then + POSTDISPLAY="${suggestion#$BUFFER}" + else + unset POSTDISPLAY + fi +} + +# Accept the entire suggestion +_zsh_autosuggest_accept() { + local -i max_cursor_pos=$#BUFFER + + # When vicmd keymap is active, the cursor can't move all the way + # to the end of the buffer + if [ "$KEYMAP" = "vicmd" ]; then + max_cursor_pos=$((max_cursor_pos - 1)) + fi + + # Only accept if the cursor is at the end of the buffer + if [ $CURSOR -eq $max_cursor_pos ]; then + # Add the suggestion to the buffer + BUFFER="$BUFFER$POSTDISPLAY" + + # Remove the suggestion + unset POSTDISPLAY + + # Move the cursor to the end of the buffer + CURSOR=${#BUFFER} + fi + + _zsh_autosuggest_invoke_original_widget $@ +} + +# Accept the entire suggestion and execute it +_zsh_autosuggest_execute() { + # Add the suggestion to the buffer + BUFFER="$BUFFER$POSTDISPLAY" + + # Remove the suggestion + unset POSTDISPLAY + + # Call the original `accept-line` to handle syntax highlighting or + # other potential custom behavior + _zsh_autosuggest_invoke_original_widget "accept-line" +} + +# Partially accept the suggestion +_zsh_autosuggest_partial_accept() { + local -i retval + + # Save the contents of the buffer so we can restore later if needed + local original_buffer="$BUFFER" + + # Temporarily accept the suggestion. + BUFFER="$BUFFER$POSTDISPLAY" + + # Original widget moves the cursor + _zsh_autosuggest_invoke_original_widget $@ + retval=$? + + # If we've moved past the end of the original buffer + if [ $CURSOR -gt $#original_buffer ]; then + # Set POSTDISPLAY to text right of the cursor + POSTDISPLAY="$RBUFFER" + + # Clip the buffer at the cursor + BUFFER="$LBUFFER" + else + # Restore the original buffer + BUFFER="$original_buffer" + fi + + return $retval +} + +for action in clear modify fetch suggest accept partial_accept execute enable disable toggle; do + eval "_zsh_autosuggest_widget_$action() { + local -i retval + + _zsh_autosuggest_highlight_reset + + _zsh_autosuggest_$action \$@ + retval=\$? + + _zsh_autosuggest_highlight_apply + + zle -R + + return \$retval + }" +done + +zle -N autosuggest-fetch _zsh_autosuggest_widget_fetch +zle -N autosuggest-suggest _zsh_autosuggest_widget_suggest +zle -N autosuggest-accept _zsh_autosuggest_widget_accept +zle -N autosuggest-clear _zsh_autosuggest_widget_clear +zle -N autosuggest-execute _zsh_autosuggest_widget_execute +zle -N autosuggest-enable _zsh_autosuggest_widget_enable +zle -N autosuggest-disable _zsh_autosuggest_widget_disable +zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle + +#--------------------------------------------------------------------# +# Default Suggestion Strategy # +#--------------------------------------------------------------------# +# Suggests the most recent history item that matches the given +# prefix. +# + +_zsh_autosuggest_strategy_default() { + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh + + # Enable globbing flags so that we can use (#m) + setopt EXTENDED_GLOB + + # Escape backslashes and all of the glob operators so we can use + # this string as a pattern to search the $history associative array. + # - (#m) globbing flag enables setting references for match data + local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" + + # Get the history items that match + # - (r) subscript flag makes the pattern match on values + suggestion="${history[(r)$prefix*]}" + +} + +#--------------------------------------------------------------------# +# Match Previous Command Suggestion Strategy # +#--------------------------------------------------------------------# +# Suggests the most recent history item that matches the given +# prefix and whose preceding history item also matches the most +# recently executed command. +# +# For example, suppose your history has the following entries: +# - pwd +# - ls foo +# - ls bar +# - pwd +# +# Given the history list above, when you type 'ls', the suggestion +# will be 'ls foo' rather than 'ls bar' because your most recently +# executed command (pwd) was previously followed by 'ls foo'. +# +# Note that this strategy won't work as expected with ZSH options that don't +# preserve the history order such as `HIST_IGNORE_ALL_DUPS` or +# `HIST_EXPIRE_DUPS_FIRST`. + +_zsh_autosuggest_strategy_match_prev_cmd() { + local prefix="${1//(#m)[\\()\[\]|*?~]/\\$MATCH}" + + # Get all history event numbers that correspond to history + # entries that match pattern $prefix* + local history_match_keys + history_match_keys=(${(k)history[(R)$prefix*]}) + + # By default we use the first history number (most recent history entry) + local histkey="${history_match_keys[1]}" + + # Get the previously executed command + local prev_cmd="$(_zsh_autosuggest_escape_command "${history[$((HISTCMD-1))]}")" + + # Iterate up to the first 200 history event numbers that match $prefix + for key in "${(@)history_match_keys[1,200]}"; do + # Stop if we ran out of history + [[ $key -gt 1 ]] || break + + # See if the history entry preceding the suggestion matches the + # previous command, and use it if it does + if [[ "${history[$((key - 1))]}" == "$prev_cmd" ]]; then + histkey="$key" + break + fi + done + + # Give back the matched history entry + suggestion="$history[$histkey]" +} + +#--------------------------------------------------------------------# +# Async # +#--------------------------------------------------------------------# + +# Zpty process is spawned running this function +_zsh_autosuggest_async_server() { + emulate -R zsh + + # There is a bug in zpty module (fixed in zsh/master) by which a + # zpty that exits will kill all zpty processes that were forked + # before it. Here we set up a zsh exit hook to SIGKILL the zpty + # process immediately, before it has a chance to kill any other + # zpty processes. + zshexit() { + kill -KILL $$ + sleep 1 # Block for long enough for the signal to come through + } + + # Output only newlines (not carriage return + newline) + stty -onlcr + + # Silence any error messages + exec 2>/dev/null + + local strategy=$1 + local last_pid + + while IFS='' read -r -d $'\0' query; do + # Kill last bg process + kill -KILL $last_pid &>/dev/null + + # Run suggestion search in the background + ( + local suggestion + _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY "$query" + echo -n -E "$suggestion"$'\0' + ) & + + last_pid=$! + done +} + +_zsh_autosuggest_async_request() { + # Write the query to the zpty process to fetch a suggestion + zpty -w -n $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "${1}"$'\0' +} + +# Called when new data is ready to be read from the pty +# First arg will be fd ready for reading +# Second arg will be passed in case of error +_zsh_autosuggest_async_response() { + setopt LOCAL_OPTIONS EXTENDED_GLOB + + local suggestion + + zpty -rt $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME suggestion '*'$'\0' 2>/dev/null + zle autosuggest-suggest -- "${suggestion%%$'\0'##}" +} + +_zsh_autosuggest_async_pty_create() { + # With newer versions of zsh, REPLY stores the fd to read from + typeset -h REPLY + + # If we won't get a fd back from zpty, try to guess it + if [ $_ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD -eq 0 ]; then + integer -l zptyfd + exec {zptyfd}>&1 # Open a new file descriptor (above 10). + exec {zptyfd}>&- # Close it so it's free to be used by zpty. + fi + + # Fork a zpty process running the server function + zpty -b $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "_zsh_autosuggest_async_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" + + # Store the fd so we can remove the handler later + if (( REPLY )); then + _ZSH_AUTOSUGGEST_PTY_FD=$REPLY + else + _ZSH_AUTOSUGGEST_PTY_FD=$zptyfd + fi + + # Set up input handler from the zpty + zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_response +} + +_zsh_autosuggest_async_pty_destroy() { + if zpty -t $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null; then + # Remove the input handler + zle -F $_ZSH_AUTOSUGGEST_PTY_FD &>/dev/null + + # Destroy the zpty + zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null + fi +} + +_zsh_autosuggest_async_pty_recreate() { + _zsh_autosuggest_async_pty_destroy + _zsh_autosuggest_async_pty_create +} + +_zsh_autosuggest_async_start() { + typeset -g _ZSH_AUTOSUGGEST_PTY_FD + + _zsh_autosuggest_feature_detect_zpty_returns_fd + _zsh_autosuggest_async_pty_recreate + + # We recreate the pty to get a fresh list of history events + add-zsh-hook precmd _zsh_autosuggest_async_pty_recreate +} + +#--------------------------------------------------------------------# +# Start # +#--------------------------------------------------------------------# + +# Start the autosuggestion widgets +_zsh_autosuggest_start() { + add-zsh-hook -d precmd _zsh_autosuggest_start + + _zsh_autosuggest_bind_widgets + + # Re-bind widgets on every precmd to ensure we wrap other wrappers. + # Specifically, highlighting breaks if our widgets are wrapped by + # zsh-syntax-highlighting widgets. This also allows modifications + # to the widget list variables to take effect on the next precmd. + add-zsh-hook precmd _zsh_autosuggest_bind_widgets + + if [ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]; then + _zsh_autosuggest_async_start + fi +} + +# Start the autosuggestion widgets on the next precmd +add-zsh-hook precmd _zsh_autosuggest_start diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/.inputs/zsh-syntax-highlighting.zsh b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/.inputs/zsh-syntax-highlighting.zsh new file mode 100644 index 0000000..60f9113 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/.inputs/zsh-syntax-highlighting.zsh @@ -0,0 +1,419 @@ +# ------------------------------------------------------------------------------------------------- +# Copyright (c) 2010-2016 zsh-syntax-highlighting contributors +# All rights reserved. +# +# The only licensing for this file follows. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted +# provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this list of conditions +# and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, this list of +# conditions and the following disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the zsh-syntax-highlighting contributors nor the names of its contributors +# may be used to endorse or promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ------------------------------------------------------------------------------------------------- +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et +# ------------------------------------------------------------------------------------------------- + +# First of all, ensure predictable parsing. +zsh_highlight__aliases=`builtin alias -Lm '[^+]*'` +# In zsh <= 5.2, `alias -L` emits aliases that begin with a plus sign ('alias -- +foo=42') +# them without a '--' guard, so they don't round trip. +# +# Hence, we exclude them from unaliasing: +builtin unalias -m '[^+]*' + +# Set $0 to the expected value, regardless of functionargzero. +0=${(%):-%N} +if true; then + # $0 is reliable + typeset -g ZSH_HIGHLIGHT_VERSION=$(<"${0:A:h}"/.version) + typeset -g ZSH_HIGHLIGHT_REVISION=$(<"${0:A:h}"/.revision-hash) + if [[ $ZSH_HIGHLIGHT_REVISION == \$Format:* ]]; then + # When running from a source tree without 'make install', $ZSH_HIGHLIGHT_REVISION + # would be set to '$Format:%H$' literally. That's an invalid value, and obtaining + # the valid value (via `git rev-parse HEAD`, as Makefile does) might be costly, so: + ZSH_HIGHLIGHT_REVISION=HEAD + fi +fi + +# ------------------------------------------------------------------------------------------------- +# Core highlighting update system +# ------------------------------------------------------------------------------------------------- + +# Use workaround for bug in ZSH? +# zsh-users/zsh@48cadf4 http://www.zsh.org/mla/workers//2017/msg00034.html +autoload -U is-at-least +if is-at-least 5.3.2; then + zsh_highlight__pat_static_bug=false +else + zsh_highlight__pat_static_bug=true +fi + +# Array declaring active highlighters names. +typeset -ga ZSH_HIGHLIGHT_HIGHLIGHTERS + +# Update ZLE buffer syntax highlighting. +# +# Invokes each highlighter that needs updating. +# This function is supposed to be called whenever the ZLE state changes. +_zsh_highlight() +{ + # Store the previous command return code to restore it whatever happens. + local ret=$? + + # Remove all highlighting in isearch, so that only the underlining done by zsh itself remains. + # For details see FAQ entry 'Why does syntax highlighting not work while searching history?'. + # This disables highlighting during isearch (for reasons explained in README.md) unless zsh is new enough + # and doesn't have the 5.3.1 bug + if [[ $WIDGET == zle-isearch-update ]] && { $zsh_highlight__pat_static_bug || ! (( $+ISEARCHMATCH_ACTIVE )) }; then + region_highlight=() + return $ret + fi + + setopt localoptions warncreateglobal + setopt localoptions noksharrays + local REPLY # don't leak $REPLY into global scope + + # Do not highlight if there are more than 300 chars in the buffer. It's most + # likely a pasted command or a huge list of files in that case.. + [[ -n ${ZSH_HIGHLIGHT_MAXLENGTH:-} ]] && [[ $#BUFFER -gt $ZSH_HIGHLIGHT_MAXLENGTH ]] && return $ret + + # Do not highlight if there are pending inputs (copy/paste). + [[ $PENDING -gt 0 ]] && return $ret + + # Reset region highlight to build it from scratch + typeset -ga region_highlight + region_highlight=(); + + { + local cache_place + local -a region_highlight_copy + + # Select which highlighters in ZSH_HIGHLIGHT_HIGHLIGHTERS need to be invoked. + local highlighter; for highlighter in $ZSH_HIGHLIGHT_HIGHLIGHTERS; do + + # eval cache place for current highlighter and prepare it + cache_place="_zsh_highlight__highlighter_${highlighter}_cache" + typeset -ga ${cache_place} + + # If highlighter needs to be invoked + if ! type "_zsh_highlight_highlighter_${highlighter}_predicate" >&/dev/null; then + echo "zsh-syntax-highlighting: warning: disabling the ${(qq)highlighter} highlighter as it has not been loaded" >&2 + # TODO: use ${(b)} rather than ${(q)} if supported + ZSH_HIGHLIGHT_HIGHLIGHTERS=( ${ZSH_HIGHLIGHT_HIGHLIGHTERS:#${highlighter}} ) + elif "_zsh_highlight_highlighter_${highlighter}_predicate"; then + + # save a copy, and cleanup region_highlight + region_highlight_copy=("${region_highlight[@]}") + region_highlight=() + + # Execute highlighter and save result + { + "_zsh_highlight_highlighter_${highlighter}_paint" + } always { + eval "${cache_place}=(\"\${region_highlight[@]}\")" + } + + # Restore saved region_highlight + region_highlight=("${region_highlight_copy[@]}") + + fi + + # Use value form cache if any cached + eval "region_highlight+=(\"\${${cache_place}[@]}\")" + + done + + # Re-apply zle_highlight settings + + # region + if (( REGION_ACTIVE == 1 )); then + _zsh_highlight_apply_zle_highlight region standout "$MARK" "$CURSOR" + elif (( REGION_ACTIVE == 2 )); then + () { + local needle=$'\n' + integer min max + if (( MARK > CURSOR )) ; then + min=$CURSOR max=$MARK + else + min=$MARK max=$CURSOR + fi + (( min = ${${BUFFER[1,$min]}[(I)$needle]} )) + (( max += ${${BUFFER:($max-1)}[(i)$needle]} - 1 )) + _zsh_highlight_apply_zle_highlight region standout "$min" "$max" + } + fi + + # yank / paste (zsh-5.1.1 and newer) + (( $+YANK_ACTIVE )) && (( YANK_ACTIVE )) && _zsh_highlight_apply_zle_highlight paste standout "$YANK_START" "$YANK_END" + + # isearch + (( $+ISEARCHMATCH_ACTIVE )) && (( ISEARCHMATCH_ACTIVE )) && _zsh_highlight_apply_zle_highlight isearch underline "$ISEARCHMATCH_START" "$ISEARCHMATCH_END" + + # suffix + (( $+SUFFIX_ACTIVE )) && (( SUFFIX_ACTIVE )) && _zsh_highlight_apply_zle_highlight suffix bold "$SUFFIX_START" "$SUFFIX_END" + + + return $ret + + + } always { + typeset -g _ZSH_HIGHLIGHT_PRIOR_BUFFER="$BUFFER" + typeset -gi _ZSH_HIGHLIGHT_PRIOR_CURSOR=$CURSOR + } +} + +# Apply highlighting based on entries in the zle_highlight array. +# This function takes four arguments: +# 1. The exact entry (no patterns) in the zle_highlight array: +# region, paste, isearch, or suffix +# 2. The default highlighting that should be applied if the entry is unset +# 3. and 4. Two integer values describing the beginning and end of the +# range. The order does not matter. +_zsh_highlight_apply_zle_highlight() { + local entry="$1" default="$2" + integer first="$3" second="$4" + + # read the relevant entry from zle_highlight + local region="${zle_highlight[(r)${entry}:*]}" + + if [[ -z "$region" ]]; then + # entry not specified at all, use default value + region=$default + else + # strip prefix + region="${region#${entry}:}" + + # no highlighting when set to the empty string or to 'none' + if [[ -z "$region" ]] || [[ "$region" == none ]]; then + return + fi + fi + + integer start end + if (( first < second )); then + start=$first end=$second + else + start=$second end=$first + fi + region_highlight+=("$start $end $region") +} + + +# ------------------------------------------------------------------------------------------------- +# API/utility functions for highlighters +# ------------------------------------------------------------------------------------------------- + +# Array used by highlighters to declare user overridable styles. +typeset -gA ZSH_HIGHLIGHT_STYLES + +# Whether the command line buffer has been modified or not. +# +# Returns 0 if the buffer has changed since _zsh_highlight was last called. +_zsh_highlight_buffer_modified() +{ + [[ "${_ZSH_HIGHLIGHT_PRIOR_BUFFER:-}" != "$BUFFER" ]] +} + +# Whether the cursor has moved or not. +# +# Returns 0 if the cursor has moved since _zsh_highlight was last called. +_zsh_highlight_cursor_moved() +{ + [[ -n $CURSOR ]] && [[ -n ${_ZSH_HIGHLIGHT_PRIOR_CURSOR-} ]] && (($_ZSH_HIGHLIGHT_PRIOR_CURSOR != $CURSOR)) +} + +# Add a highlight defined by ZSH_HIGHLIGHT_STYLES. +# +# Should be used by all highlighters aside from 'pattern' (cf. ZSH_HIGHLIGHT_PATTERN). +# Overwritten in tests/test-highlighting.zsh when testing. +_zsh_highlight_add_highlight() +{ + local -i start end + local highlight + start=$1 + end=$2 + shift 2 + for highlight; do + if (( $+ZSH_HIGHLIGHT_STYLES[$highlight] )); then + region_highlight+=("$start $end $ZSH_HIGHLIGHT_STYLES[$highlight]") + break + fi + done +} + +# ------------------------------------------------------------------------------------------------- +# Setup functions +# ------------------------------------------------------------------------------------------------- + +# Helper for _zsh_highlight_bind_widgets +# $1 is name of widget to call +_zsh_highlight_call_widget() +{ + builtin zle "$@" && + _zsh_highlight +} + +# Rebind all ZLE widgets to make them invoke _zsh_highlights. +_zsh_highlight_bind_widgets() +{ + setopt localoptions noksharrays + typeset -F SECONDS + local prefix=orig-s$SECONDS-r$RANDOM # unique each time, in case we're sourced more than once + + # Load ZSH module zsh/zleparameter, needed to override user defined widgets. + zmodload zsh/zleparameter 2>/dev/null || { + print -r -- >&2 'zsh-syntax-highlighting: failed loading zsh/zleparameter.' + return 1 + } + + # Override ZLE widgets to make them invoke _zsh_highlight. + local -U widgets_to_bind + widgets_to_bind=(${${(k)widgets}:#(.*|run-help|which-command|beep|set-local-history|yank)}) + + # Always wrap special zle-line-finish widget. This is needed to decide if the + # current line ends and special highlighting logic needs to be applied. + # E.g. remove cursor imprint, don't highlight partial paths, ... + widgets_to_bind+=(zle-line-finish) + + # Always wrap special zle-isearch-update widget to be notified of updates in isearch. + # This is needed because we need to disable highlighting in that case. + widgets_to_bind+=(zle-isearch-update) + + local cur_widget + for cur_widget in $widgets_to_bind; do + case $widgets[$cur_widget] in + + # Already rebound event: do nothing. + user:_zsh_highlight_widget_*);; + + # The "eval"'s are required to make $cur_widget a closure: the value of the parameter at function + # definition time is used. + # + # We can't use ${0/_zsh_highlight_widget_} because these widgets are always invoked with + # NO_function_argzero, regardless of the option's setting here. + + # User defined widget: override and rebind old one with prefix "orig-". + user:*) zle -N $prefix-$cur_widget ${widgets[$cur_widget]#*:} + eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget ${(q)prefix}-${(q)cur_widget} -- \"\$@\" }" + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; + + # Completion widget: override and rebind old one with prefix "orig-". + completion:*) zle -C $prefix-$cur_widget ${${(s.:.)widgets[$cur_widget]}[2,3]} + eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget ${(q)prefix}-${(q)cur_widget} -- \"\$@\" }" + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; + + # Builtin widget: override and make it call the builtin ".widget". + builtin) eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget .${(q)cur_widget} -- \"\$@\" }" + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; + + # Incomplete or nonexistent widget: Bind to z-sy-h directly. + *) + if [[ $cur_widget == zle-* ]] && [[ -z $widgets[$cur_widget] ]]; then + _zsh_highlight_widget_${cur_widget}() { :; _zsh_highlight } + zle -N $cur_widget _zsh_highlight_widget_$cur_widget + else + # Default: unhandled case. + print -r -- >&2 "zsh-syntax-highlighting: unhandled ZLE widget ${(qq)cur_widget}" + print -r -- >&2 "zsh-syntax-highlighting: (This is sometimes caused by doing \`bindkey <keys> ${(q-)cur_widget}\` without creating the ${(qq)cur_widget} widget with \`zle -N\` or \`zle -C\`.)" + fi + esac + done +} + +# Load highlighters from directory. +# +# Arguments: +# 1) Path to the highlighters directory. +_zsh_highlight_load_highlighters() +{ + setopt localoptions noksharrays + + # Check the directory exists. + [[ -d "$1" ]] || { + print -r -- >&2 "zsh-syntax-highlighting: highlighters directory ${(qq)1} not found." + return 1 + } + + # Load highlighters from highlighters directory and check they define required functions. + local highlighter highlighter_dir + for highlighter_dir ($1/*/); do + highlighter="${highlighter_dir:t}" + [[ -f "$highlighter_dir${highlighter}-highlighter.zsh" ]] && + . "$highlighter_dir${highlighter}-highlighter.zsh" + if type "_zsh_highlight_highlighter_${highlighter}_paint" &> /dev/null && + type "_zsh_highlight_highlighter_${highlighter}_predicate" &> /dev/null; + then + # New (0.5.0) function names + elif type "_zsh_highlight_${highlighter}_highlighter" &> /dev/null && + type "_zsh_highlight_${highlighter}_highlighter_predicate" &> /dev/null; + then + # Old (0.4.x) function names + if false; then + # TODO: only show this warning for plugin authors/maintainers, not for end users + print -r -- >&2 "zsh-syntax-highlighting: warning: ${(qq)highlighter} highlighter uses deprecated entry point names; please ask its maintainer to update it: https://github.com/zsh-users/zsh-syntax-highlighting/issues/329" + fi + # Make it work. + eval "_zsh_highlight_highlighter_${(q)highlighter}_paint() { _zsh_highlight_${(q)highlighter}_highlighter \"\$@\" }" + eval "_zsh_highlight_highlighter_${(q)highlighter}_predicate() { _zsh_highlight_${(q)highlighter}_highlighter_predicate \"\$@\" }" + else + print -r -- >&2 "zsh-syntax-highlighting: ${(qq)highlighter} highlighter should define both required functions '_zsh_highlight_highlighter_${highlighter}_paint' and '_zsh_highlight_highlighter_${highlighter}_predicate' in ${(qq):-"$highlighter_dir${highlighter}-highlighter.zsh"}." + fi + done +} + + +# ------------------------------------------------------------------------------------------------- +# Setup +# ------------------------------------------------------------------------------------------------- + +# Try binding widgets. +_zsh_highlight_bind_widgets || { + print -r -- >&2 'zsh-syntax-highlighting: failed binding ZLE widgets, exiting.' + return 1 +} + +# Resolve highlighters directory location. +_zsh_highlight_load_highlighters "${ZSH_HIGHLIGHT_HIGHLIGHTERS_DIR:-${${0:A}:h}/highlighters}" || { + print -r -- >&2 'zsh-syntax-highlighting: failed loading highlighters, exiting.' + return 1 +} + +# Reset scratch variables when commandline is done. +_zsh_highlight_preexec_hook() +{ + typeset -g _ZSH_HIGHLIGHT_PRIOR_BUFFER= + typeset -gi _ZSH_HIGHLIGHT_PRIOR_CURSOR= +} +autoload -U add-zsh-hook +add-zsh-hook preexec _zsh_highlight_preexec_hook 2>/dev/null || { + print -r -- >&2 'zsh-syntax-highlighting: failed loading add-zsh-hook.' + } + +# Load zsh/parameter module if available +zmodload zsh/parameter 2>/dev/null || true + +# Initialize the array of active highlighters if needed. +[[ $#ZSH_HIGHLIGHT_HIGHLIGHTERS -eq 0 ]] && ZSH_HIGHLIGHT_HIGHLIGHTERS=(main) + +# Restore the aliases we unned +eval "$zsh_highlight__aliases" +builtin unset zsh_highlight__aliases + +# Set $?. +true diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/zsh-autosuggestions.zsh.adoc b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/zsh-autosuggestions.zsh.adoc new file mode 100644 index 0000000..8fc5ae6 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/zsh-autosuggestions.zsh.adoc @@ -0,0 +1,524 @@ +zsh-autosuggestions.zsh(1) +========================== +:compat-mode!: + +NAME +---- +zsh-autosuggestions.zsh - a shell script + +SYNOPSIS +-------- +Documentation automatically generated with `zshelldoc' + +FUNCTIONS +--------- + + _zsh_autosuggest_accept + _zsh_autosuggest_async_pty_create + _zsh_autosuggest_async_pty_destroy + _zsh_autosuggest_async_request + _zsh_autosuggest_async_response + _zsh_autosuggest_async_server + _zsh_autosuggest_async_start + _zsh_autosuggest_bind_widget + _zsh_autosuggest_clear + _zsh_autosuggest_disable + _zsh_autosuggest_enable + _zsh_autosuggest_escape_command + _zsh_autosuggest_execute + _zsh_autosuggest_feature_detect_zpty_returns_fd + _zsh_autosuggest_fetch + _zsh_autosuggest_get_bind_count + _zsh_autosuggest_highlight_apply + _zsh_autosuggest_highlight_reset + _zsh_autosuggest_incr_bind_count + _zsh_autosuggest_invoke_original_widget + _zsh_autosuggest_modify + _zsh_autosuggest_partial_accept + _zsh_autosuggest_strategy_default + _zsh_autosuggest_strategy_match_prev_cmd + _zsh_autosuggest_suggest + _zsh_autosuggest_toggle +AUTOLOAD add-zsh-hook +PRECMD-HOOK _zsh_autosuggest_async_pty_recreate +PRECMD-HOOK _zsh_autosuggest_bind_widgets +PRECMD-HOOK _zsh_autosuggest_start + +DETAILS +------- + +Script Body +~~~~~~~~~~~ + +Has 71 line(s). Calls functions: + + Script-Body + `-- add-zsh-hook + +_zsh_autosuggest_accept +~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Accept the entire suggestion +____ + +Has 11 line(s). Calls functions: + + _zsh_autosuggest_accept + `-- _zsh_autosuggest_invoke_original_widget + +Not called by script or any function, may be a hook or Zle widget, etc. + +_zsh_autosuggest_async_pty_create +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Has 13 line(s). Doesn't call other functions. + +Uses feature(s): _zle_, _zpty_ + +Called by: + + _zsh_autosuggest_async_pty_recreate + +_zsh_autosuggest_async_pty_destroy +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Has 4 line(s). Doesn't call other functions. + +Uses feature(s): _zle_, _zpty_ + +Called by: + + _zsh_autosuggest_async_pty_recreate + +_zsh_autosuggest_async_pty_recreate +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Has 2 line(s). *Is a precmd hook*. Calls functions: + + _zsh_autosuggest_async_pty_recreate + |-- _zsh_autosuggest_async_pty_create + `-- _zsh_autosuggest_async_pty_destroy + +Called by: + + _zsh_autosuggest_async_start + +_zsh_autosuggest_async_request +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Has 1 line(s). Doesn't call other functions. + +Uses feature(s): _zpty_ + +Called by: + + _zsh_autosuggest_fetch + +_zsh_autosuggest_async_response +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Called when new data is ready to be read from the pty + # First arg will be fd ready for reading + # Second arg will be passed in case of error +____ + +Has 6 line(s). Doesn't call other functions. + +Uses feature(s): _zle_, _zpty_ + +Not called by script or any function, may be a hook or Zle widget, etc. + +_zsh_autosuggest_async_server +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Zpty process is spawned running this function +____ + +Has 21 line(s). Doesn't call other functions. + +Uses feature(s): _kill_ + +Not called by script or any function, may be a hook or Zle widget, etc. + +_zsh_autosuggest_async_start +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Has 5 line(s). Calls functions: + + _zsh_autosuggest_async_start + |-- _zsh_autosuggest_async_pty_recreate + |   |-- _zsh_autosuggest_async_pty_create + |   `-- _zsh_autosuggest_async_pty_destroy + |-- _zsh_autosuggest_feature_detect_zpty_returns_fd + `-- add-zsh-hook + +Called by: + + _zsh_autosuggest_start + +_zsh_autosuggest_bind_widget +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Bind a single widget to an autosuggest widget, saving a reference to the original widget +____ + +Has 29 line(s). Calls functions: + + _zsh_autosuggest_bind_widget + |-- _zsh_autosuggest_get_bind_count + `-- _zsh_autosuggest_incr_bind_count + +Uses feature(s): _eval_, _zle_ + +Called by: + + _zsh_autosuggest_bind_widgets + +_zsh_autosuggest_bind_widgets +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Map all configured widgets to the right autosuggest widgets +____ + +Has 24 line(s). *Is a precmd hook*. Calls functions: + + _zsh_autosuggest_bind_widgets + `-- _zsh_autosuggest_bind_widget + |-- _zsh_autosuggest_get_bind_count + `-- _zsh_autosuggest_incr_bind_count + +Called by: + + _zsh_autosuggest_start + +_zsh_autosuggest_clear +~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Clear the suggestion +____ + +Has 3 line(s). Calls functions: + + _zsh_autosuggest_clear + `-- _zsh_autosuggest_invoke_original_widget + +Called by: + + _zsh_autosuggest_disable + +_zsh_autosuggest_disable +~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Disable suggestions +____ + +Has 2 line(s). Calls functions: + + _zsh_autosuggest_disable + `-- _zsh_autosuggest_clear + `-- _zsh_autosuggest_invoke_original_widget + +Called by: + + _zsh_autosuggest_toggle + +_zsh_autosuggest_enable +~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Enable suggestions +____ + +Has 5 line(s). Calls functions: + + _zsh_autosuggest_enable + `-- _zsh_autosuggest_fetch + |-- _zsh_autosuggest_async_request + `-- _zsh_autosuggest_suggest + +Called by: + + _zsh_autosuggest_toggle + +_zsh_autosuggest_escape_command +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + #--------------------------------------------------------------------# + # Utility Functions # + #--------------------------------------------------------------------# +____ + +Has 2 line(s). Doesn't call other functions. + +Not called by script or any function, may be a hook or Zle widget, etc. + +_zsh_autosuggest_execute +~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Accept the entire suggestion and execute it +____ + +Has 3 line(s). Calls functions: + + _zsh_autosuggest_execute + `-- _zsh_autosuggest_invoke_original_widget + +Not called by script or any function, may be a hook or Zle widget, etc. + +_zsh_autosuggest_feature_detect_zpty_returns_fd +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + #--------------------------------------------------------------------# + # Feature Detection # + #--------------------------------------------------------------------# +____ + +Has 12 line(s). Doesn't call other functions. + +Uses feature(s): _zpty_ + +Called by: + + _zsh_autosuggest_async_start + +_zsh_autosuggest_fetch +~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Fetch a new suggestion based on what's currently in the buffer +____ + +Has 7 line(s). Calls functions: + + _zsh_autosuggest_fetch + |-- _zsh_autosuggest_async_request + `-- _zsh_autosuggest_suggest + +Uses feature(s): _zpty_ + +Called by: + + _zsh_autosuggest_enable + _zsh_autosuggest_modify + +_zsh_autosuggest_get_bind_count +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Has 5 line(s). Doesn't call other functions. + +Called by: + + _zsh_autosuggest_bind_widget + +_zsh_autosuggest_highlight_apply +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # If there's a suggestion, highlight it +____ + +Has 8 line(s). Doesn't call other functions. + +Not called by script or any function, may be a hook or Zle widget, etc. + +_zsh_autosuggest_highlight_reset +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # If there was a highlight, remove it +____ + +Has 6 line(s). Doesn't call other functions. + +Not called by script or any function, may be a hook or Zle widget, etc. + +_zsh_autosuggest_incr_bind_count +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + #--------------------------------------------------------------------# + # Widget Helpers # + #--------------------------------------------------------------------# +____ + +Has 7 line(s). Doesn't call other functions. + +Called by: + + _zsh_autosuggest_bind_widget + +_zsh_autosuggest_invoke_original_widget +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Given the name of an original widget and args, invoke it, if it exists +____ + +Has 9 line(s). Doesn't call other functions. + +Uses feature(s): _zle_ + +Called by: + + _zsh_autosuggest_accept + _zsh_autosuggest_clear + _zsh_autosuggest_execute + _zsh_autosuggest_modify + _zsh_autosuggest_partial_accept + +_zsh_autosuggest_modify +~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Modify the buffer and get a new suggestion +____ + +Has 31 line(s). Calls functions: + + _zsh_autosuggest_modify + |-- _zsh_autosuggest_fetch + |   |-- _zsh_autosuggest_async_request + |   `-- _zsh_autosuggest_suggest + `-- _zsh_autosuggest_invoke_original_widget + +Not called by script or any function, may be a hook or Zle widget, etc. + +_zsh_autosuggest_partial_accept +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Partially accept the suggestion +____ + +Has 13 line(s). Calls functions: + + _zsh_autosuggest_partial_accept + `-- _zsh_autosuggest_invoke_original_widget + +Not called by script or any function, may be a hook or Zle widget, etc. + +_zsh_autosuggest_start +~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Start the autosuggestion widgets +____ + +Has 8 line(s). *Is a precmd hook*. Calls functions: + + _zsh_autosuggest_start + |-- _zsh_autosuggest_async_start + |   |-- _zsh_autosuggest_async_pty_recreate + |   |   |-- _zsh_autosuggest_async_pty_create + |   |   `-- _zsh_autosuggest_async_pty_destroy + |   |-- _zsh_autosuggest_feature_detect_zpty_returns_fd + |   `-- add-zsh-hook + |-- _zsh_autosuggest_bind_widgets + |   `-- _zsh_autosuggest_bind_widget + |   |-- _zsh_autosuggest_get_bind_count + |   `-- _zsh_autosuggest_incr_bind_count + `-- add-zsh-hook + +Not called by script or any function, may be a hook or Zle widget, etc. + +_zsh_autosuggest_strategy_default +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + #--------------------------------------------------------------------# + # Default Suggestion Strategy # + #--------------------------------------------------------------------# + # Suggests the most recent history item that matches the given + # prefix. +____ + +Has 4 line(s). Doesn't call other functions. + +Not called by script or any function, may be a hook or Zle widget, etc. + +_zsh_autosuggest_strategy_match_prev_cmd +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + #--------------------------------------------------------------------# + # Match Previous Command Suggestion Strategy # + #--------------------------------------------------------------------# + # Suggests the most recent history item that matches the given + # prefix and whose preceding history item also matches the most + # recently executed command. + # + # For example, suppose your history has the following entries: + # - pwd + # - ls foo + # - ls bar + # - pwd + # + # Given the history list above, when you type 'ls', the suggestion + # will be 'ls foo' rather than 'ls bar' because your most recently + # executed command (pwd) was previously followed by 'ls foo'. + # + # Note that this strategy won't work as expected with ZSH options that don't + # preserve the history order such as `HIST_IGNORE_ALL_DUPS` or + # `HIST_EXPIRE_DUPS_FIRST`. +____ + +Has 13 line(s). Doesn't call other functions. + +Not called by script or any function, may be a hook or Zle widget, etc. + +_zsh_autosuggest_suggest +~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Offer a suggestion +____ + +Has 7 line(s). Doesn't call other functions. + +Called by: + + _zsh_autosuggest_fetch + +_zsh_autosuggest_toggle +~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Toggle suggestions (enable/disable) +____ + +Has 5 line(s). Calls functions: + + _zsh_autosuggest_toggle + |-- _zsh_autosuggest_disable + |   `-- _zsh_autosuggest_clear + |   `-- _zsh_autosuggest_invoke_original_widget + `-- _zsh_autosuggest_enable + `-- _zsh_autosuggest_fetch + |-- _zsh_autosuggest_async_request + `-- _zsh_autosuggest_suggest + +Not called by script or any function, may be a hook or Zle widget, etc. + +add-zsh-hook +~~~~~~~~~~~~ + +Has 93 line(s). Doesn't call other functions. + +Uses feature(s): _autoload_ + +Called by: + + Script-Body + _zsh_autosuggest_async_start + _zsh_autosuggest_start + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/zsh-autosuggestions.zsh.pdf b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/zsh-autosuggestions.zsh.pdf new file mode 100644 index 0000000..e0132f4 Binary files /dev/null and b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/zsh-autosuggestions.zsh.pdf differ diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/zsh-syntax-highlighting.zsh.adoc b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/zsh-syntax-highlighting.zsh.adoc new file mode 100644 index 0000000..47f1045 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/zsh-syntax-highlighting.zsh.adoc @@ -0,0 +1,203 @@ +zsh-syntax-highlighting.zsh(1) +============================== +:compat-mode!: + +NAME +---- +zsh-syntax-highlighting.zsh - a shell script + +SYNOPSIS +-------- +Documentation automatically generated with `zshelldoc' + +FUNCTIONS +--------- + + _zsh_highlight + _zsh_highlight_add_highlight + _zsh_highlight_apply_zle_highlight + _zsh_highlight_bind_widgets + _zsh_highlight_buffer_modified + _zsh_highlight_call_widget + _zsh_highlight_cursor_moved + _zsh_highlight_load_highlighters +AUTOLOAD add-zsh-hook +AUTOLOAD is-at-least +PREEXEC-HOOK _zsh_highlight_preexec_hook + +DETAILS +------- + +Script Body +~~~~~~~~~~~ + +Has 36 line(s). Calls functions: + + Script-Body + |-- _zsh_highlight_bind_widgets + |-- _zsh_highlight_load_highlighters + |-- add-zsh-hook + `-- is-at-least + +_zsh_highlight +~~~~~~~~~~~~~~ + +____ + # Update ZLE buffer syntax highlighting. + # + # Invokes each highlighter that needs updating. + # This function is supposed to be called whenever the ZLE state changes. +____ + +Has 65 line(s). Calls functions: + + _zsh_highlight + `-- _zsh_highlight_apply_zle_highlight + +Uses feature(s): _eval_, _type_ + +Called by: + + _zsh_highlight_call_widget + +_zsh_highlight_add_highlight +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Add a highlight defined by ZSH_HIGHLIGHT_STYLES. + # + # Should be used by all highlighters aside from 'pattern' (cf. ZSH_HIGHLIGHT_PATTERN). + # Overwritten in tests/test-highlighting.zsh when testing. +____ + +Has 11 line(s). Doesn't call other functions. + +Not called by script or any function, may be a hook or Zle widget, etc. + +_zsh_highlight_apply_zle_highlight +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Apply highlighting based on entries in the zle_highlight array. + # This function takes four arguments: + # 1. The exact entry (no patterns) in the zle_highlight array: + # region, paste, isearch, or suffix + # 2. The default highlighting that should be applied if the entry is unset + # 3. and 4. Two integer values describing the beginning and end of the + # range. The order does not matter. +____ + +Has 20 line(s). Doesn't call other functions. + +Called by: + + _zsh_highlight + +_zsh_highlight_bind_widgets +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Rebind all ZLE widgets to make them invoke _zsh_highlights. +____ + +Has 34 line(s). Doesn't call other functions. + +Uses feature(s): _eval_, _zle_, _zmodload_ + +Called by: + + Script-Body + +_zsh_highlight_buffer_modified +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Whether the command line buffer has been modified or not. + # + # Returns 0 if the buffer has changed since _zsh_highlight was last called. +____ + +Has 1 line(s). Doesn't call other functions. + +Not called by script or any function, may be a hook or Zle widget, etc. + +_zsh_highlight_call_widget +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Helper for _zsh_highlight_bind_widgets + # $1 is name of widget to call +____ + +Has 2 line(s). Calls functions: + + _zsh_highlight_call_widget + `-- _zsh_highlight + `-- _zsh_highlight_apply_zle_highlight + +Uses feature(s): _zle_ + +Not called by script or any function, may be a hook or Zle widget, etc. + +_zsh_highlight_cursor_moved +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Whether the cursor has moved or not. + # + # Returns 0 if the cursor has moved since _zsh_highlight was last called. +____ + +Has 1 line(s). Doesn't call other functions. + +Not called by script or any function, may be a hook or Zle widget, etc. + +_zsh_highlight_load_highlighters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Load highlighters from directory. + # + # Arguments: + # 1) Path to the highlighters directory. +____ + +Has 25 line(s). Doesn't call other functions. + +Uses feature(s): _eval_, _type_ + +Called by: + + Script-Body + +_zsh_highlight_preexec_hook +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Reset scratch variables when commandline is done. +____ + +Has 2 line(s). *Is a preexec hook*. Doesn't call other functions. + +Not called by script or any function, may be a hook or Zle widget, etc. + +add-zsh-hook +~~~~~~~~~~~~ + +Has 93 line(s). Doesn't call other functions. + +Uses feature(s): _autoload_ + +Called by: + + Script-Body + +is-at-least +~~~~~~~~~~~ + +Has 56 line(s). Doesn't call other functions. + +Called by: + + Script-Body + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/zsh-syntax-highlighting.zsh.pdf b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/zsh-syntax-highlighting.zsh.pdf new file mode 100644 index 0000000..28e8e00 Binary files /dev/null and b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/zsh-syntax-highlighting.zsh.pdf differ diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/run-tree-convert.mod b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/run-tree-convert.mod new file mode 100644 index 0000000..6c26c61 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/run-tree-convert.mod @@ -0,0 +1,33 @@ +# vim:ft=zsh + +# A module, i.e. it provides multiple functions, not a single one. +# Public functions have `zsd' prefix. + +# Converts tree (STDIN input) with possible +# special characters to ASCII-only +convert_tree() +{ + local IFS="" line + while read -r line; do + line="${line//├──/|--}" + line="${line//└──/\`--}" + line="${line//│/|}" + line="${line//_-_//}" + echo "$line" + done +} + +# Searches for supported tree command, +# invokes to-ASCII conversion +zsd-run-tree-convert() { + if type tree 2>/dev/null 1>&2; then + tree -n --charset="utf-8" -- "$1" 2>&1 | convert_tree + else + { + print "$fg[red]No \`tree' program, it is required$reset_color" + print "Download from: http://mama.indstate.edu/users/ice/tree/" + print "It is also available probably in all distributions and Homebrew, as package \`tree'" + exit 1 + } >&2 + fi +} diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/token-types.mod b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/token-types.mod new file mode 100644 index 0000000..876f6f9 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/token-types.mod @@ -0,0 +1,52 @@ +# vim:ft=zsh + +typeset -gA TOKEN_TYPES + +TOKEN_TYPES=( + + # Precommand + + '-' 1 + 'builtin' 1 + 'command' 1 + 'exec' 1 + 'nocorrect' 1 + 'noglob' 1 + 'pkexec' 1 + 'stdbuf' 1 + 'setsid' 1 + 'eatmydata' 1 + 'sudo' 1 + 'doas' 1 + 'nice' 1 + 'ssh-agent' 1 + + # Control flow + # Tokens that at "command position" are followed by a command + + $'\x7b' 2 # { + $'\x28' 2 # ( + '()' 2 + 'while' 2 + 'until' 2 + 'if' 2 + 'then' 2 + 'elif' 2 + 'else' 2 + 'do' 2 + 'time' 2 + 'coproc' 2 + '!' 2 # reserved word; unrelated to $histchars[1] + + # Command separators + + '|' 3 + '||' 3 + '&&' 3 + + '|&' 4 + '&!' 4 + '&|' 4 + '&' 4 + ';' 4 +) diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-detect.main b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-detect.main new file mode 100644 index 0000000..86ca578 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-detect.main @@ -0,0 +1,535 @@ +# vim:ft=zsh:et:sw=4 +# This file is double-licensed under GPLv3 and MIT (see LICENSE file) + +### Options ### + +local OPT_HELP OPT_VERBOSE OPT_QUIET OPT_NOANSI OPT_CIGNORE +local -A opthash +zparseopts -E -D -A opthash h -help v -verbose q -quiet r -really-quiet \ + -cignore: n -noansi || { echo "Improper options given, see help (-h/--help)"; return 1; } + +(( ${+opthash[-h]} + ${+opthash[--help]} )) && OPT_HELP=-h +(( ${+opthash[-v]} + ${+opthash[--verbose]} )) && OPT_VERBOSE=-v +(( ${+opthash[-r]} + ${+opthash[--really-quiet]} )) && { OPT_QUIET=-q; OPT_RQUIET=-r; } +(( ${+opthash[-q]} + ${+opthash[--quiet]} )) && OPT_QUIET=-q +(( ${+opthash[-n]} + ${+opthash[--noansi]} )) && OPT_NOANSI=-n +(( ${+opthash[--cignore]} )) && OPT_CIGNORE=${opthash[--cignore]} + +[[ -n $OPT_NOANSI ]] && { colors=(); fg=(); bg=(); fg_bold=(); bg_bold=(); reset_color=""; } + +[[ -z $OPT_QUIET ]] && print "$fg[cyan]== zsd-detect starting for file \`$fg_bold[yellow]$1$fg_no_bold[cyan]' (2nd pass)$reset_color" + +if [[ -n $OPT_HELP ]]; then + usage + return 0 +fi + +if [[ $# -le 0 || $* = [[:space:]]## ]]; then + print "Argument needed, see help (-h/--help)" + return 1 +fi + +if [[ ! -f $1 ]]; then + [[ -z $OPT_QUIET || -n $OPT_VERBOSE ]] && print "$fg[magenta]File \`$1' doesn't exist, skipping it (see help, with -h/--help option)$reset_color" + return 1 +fi + +if [[ ! -r $1 ]]; then + [[ -z $OPT_QUIET || -n $OPT_VERBOSE ]] && print "$fg[magenta]File \`$1' is unreadable, skipping it (see help, with -h/--help option)$reset_color" + return 1 +fi + +### Code ### + +local name=${1:t} +local doc="$(<zsdoc/data/extended/$name)" token prev_token="" spaces prev_spaces="" next_token next_spaces + +# Function and preamble extraction +local preamble="" fun_name="" env_var="" +local -A funs +integer next_fun=0 cur_fun=0 prev_fun=0 +integer depth=0 prev_depth=0 fun_depth=-1 anon_depth=-1 descentff=0 descentfa=0 + +# Nested functions tracking +integer nested_fun=0 next_nested_fun=0 prev_nested_fun=0 +local -a fun_stack_depths + +# Function-comment extraction +local cproposal="" given_fun_name="" +local -A cfuns + +# Call-tree extraction +integer at_command=1 +local -A call_tree rev_call_tree features env_list +local -a known_functions known_env +known_functions=( zsdoc/data/functions/**/*(ND) zsdoc/data/autoload/**/*(ND) ) +known_functions=( ${known_functions[@]#zsdoc/data/functions/} ) +known_functions=( ${known_functions[@]#zsdoc/data/autoload/} ) +known_env=( zsdoc/data/exports/**/*(ND) ) +known_env=( ${known_env[@]#zsdoc/data/exports/} ) + +local -a feature_list +feature_list=( + # General + eval source zle bindkey alias unalias unfunction stat + export jobs kill trap type wait read type disown + suspend + + # Zsh specific ones + zmodload zprof vared zcompile zformat autoload + sched zparseopts zstyle zpty zsocket setopt + + # Zsh functions + compinit compdef zrecompile allopt add-zsh-hook + add-zle-hook-widget backward-kill-word-match backward-word-match + bashcompinit before bracketed-paste-magic bracketed-paste-url-magic + calendar calendar_edit calendar_lockfiles calendar_parse calendar_read + calendar_scandate calendar_show calendar_showdate calendar_sort + capitalize-word-match catch cdr chpwd_recent_add chpwd_recent_dirs + chpwd_recent_filehandler colors compaudit compdump compinit compinstall + copy-earlier-word cycle-completion-positions define-composed-chars + delete-whole-word-match down-case-word-match down-line-or-beginning-search + edit-command-line expand-absolute-path forward-word-match getjobs + history-beginning-search-menu history-pattern-search history-search-end + incarg incremental-complete-word insert-composed-char insert-files + insert-unicode-char is-at-least keeper keymap+widget kill-word-match + match-word-context match-words-by-style mere modify-current-argument + move-line-in-buffer narrow-to-region narrow-to-region-invisible nslookup + pick-web-browser predict-on prompt_adam1_setup prompt_adam2_setup + prompt_bart_setup prompt_bigfade_setup prompt_clint_setup + prompt_default_setup prompt_elite2_setup prompt_elite_setup + prompt_fade_setup prompt_fire_setup prompt_off_setup prompt_oliver_setup + prompt_pws_setup prompt_redhat_setup prompt_restore_setup + prompt_special_chars prompt_suse_setup prompt_walters_setup + prompt_zefram_setup promptinit promptnl quote-and-complete-word + read-from-minibuffer regexp-replace relative replace-argument + replace-string replace-string-again run-help-git run-help-openssl + run-help-p4 run-help-sudo run-help-svk run-help-svn select-bracketed + select-quoted select-word-match select-word-style send-invisible + smart-insert-last-word split-shell-arguments surround tcp_alias tcp_close + tcp_command tcp_expect tcp_fd_handler tcp_log tcp_open tcp_output tcp_point + tcp_proxy tcp_read tcp_rename tcp_send tcp_sess tcp_shoot tcp_spam tcp_talk + tcp_wait tetris tetriscurses throw transpose-lines transpose-words-match + up-case-word-match up-line-or-beginning-search url-quote-magic vcs_info + vcs_info_hookadd vcs_info_hookdel vcs_info_lastmsg vcs_info_printsys + vcs_info_setsys vi-pipe which-command xtermctl zargs zcalc-auto-insert + zed-set-file-name zfanon zfautocheck zfcd zfcd_match zfcget zfclose zfcput + zfdir zffcache zfgcp zfget zfget_match zfgoto zfhere zfinit zfls zfmark + zfopen zfparams zfpcp zfput zfrglob zfrtime zfsession zfstat zftp_chpwd + zftp_progress zftransfer zftype zfuget zfuput zmathfunc zmathfuncdef zmv + zrecompile zsh-mime-contexts zsh-mime-handler zsh-mime-setup + zsh-newuser-install zsh_directory_name_cdr zsh_directory_name_generic + zstyle+ ztodo + + # Bash specific ones + bash getopts mapfile shopt readarray compgen complete + compopt caller +) + +# Arguments passed to `source' are gathered in this array. +# Then, it is used to choose better candidate from possible +# external files, in which functions are called. +local -a sourced_files + +# Patterns +local pattern_give_function_name="{function:([^}]##)}" + +# +# Functions +# + +line_count() +{ + local -a list + list=( "${(@f)1}" ) + local count=${#list} + [[ $1 = *$'\n' ]] && (( -- count )) + print -r -- "${(l:3:: :)count}" +} + +# Finds variables in given string, returns their names. +# +# $1 - the string to search in +# +# The while [[ ... ]] pattern is logically ((A)|(B)|(C)|(D)|(E))(*), where: +# - A matches $var[abc] +# - B matches ${(...)var[abc]} +# - C matches $ +# - D matches \$ or \" or \' +# - E matches \* +# +# and the first condition -n ${match[7] uses D to continue searching when +# backslash-something (not ' or " or $) is occured. +# +# Returns names of all variables in reply. +find-variables() +{ + local _mybuf=$1 + reply=() + + while [[ $_mybuf = (#b)[^\$\\]#((\$([a-zA-Z_:][a-zA-Z0-9_:]#|[0-9]##)(#B)(\[[^\]]#\])(#c0,1))|(\$[{](#B)([#+\!^=~](#c1,2))(#c0,1)(#B)(\([a-zA-Z0-9_:@%#]##\))(#c0,1)(#b)([a-zA-Z0-9_]##)(\[[^\]]#\])(#c0,1)(#B)([:+?^*=%#|/*-]##|[}]))|\$|[\\][\'\"\$]|[\\](#b)(*))(*) ]]; do + [[ -n ${match[7]} ]] && { + # Skip following char – it is quoted + _mybuf=${match[7]:1} + } || { + _mybuf=${match[8]} + [[ ${match[1]} = \$?## ]] && reply+=( ${match[3]:-${match[5]}} ) + } + done + return 0 +} + +# +# Main code +# + +zsd-process-buffer $doc 1 +integer i size=${#ZSD_PB_WORDS} + +for (( i=1; i<=size; ++ i )); do + token=${ZSD_PB_WORDS[i]} + spaces=${ZSD_PB_SPACES[i]} + next_token=${ZSD_PB_WORDS[i+1]} + next_spaces=${ZSD_PB_SPACES[i+1]} + + cur_fun=0 prev_fun=0 descentff=0 descentfa=0 + nested_fun=0 prev_nested_fun=0 + + (( next_fun )) && { next_fun=0 cur_fun=1 prev_fun=0 anon_depth=-1; } + (( next_nested_fun )) && { next_nested_fun=0 nested_fun=1 prev_nested_fun=0; } + + # Explicit future-function + if [[ $token = function && ( $fun_depth -lt 0 ) && ( $anon_depth -lt 0 ) ]]; then + next_fun=1 cur_fun=0 prev_fun=0 anon_depth=-1 + # Detect top-level prev-function differentiating from anonymous function + elif [[ $token = "()" && ( $fun_depth -lt 0 ) && ( $anon_depth -lt 0 ) ]]; then + if [[ $spaces = [[:blank:]]#([^\\]|)$'\n'* || -z $prev_token || ${TOKEN_TYPES[$prev_token]} = [1234] ]]; then + next_fun=0 cur_fun=0 prev_fun=0 anon_depth=$depth + else + next_fun=0 cur_fun=0 prev_fun=1 anon_depth=-1 + fi + # Must be a nested future-function + elif [[ $token = function ]]; then + next_nested_fun=1 nested_fun=0 prev_nested_fun=0 + # Is it a nested prev-function? + elif [[ $token = "()" && $nested_fun -eq 0 && $depth -gt $fun_stack_depths[-1] ]]; then + if [[ $spaces != [[:blank:]]#([^\\]|)$'\n'* && -n $prev_token && ${TOKEN_TYPES[$prev_token]} != [1234] ]]; then + next_nested_fun=0 nested_fun=0 prev_nested_fun=1 + fi + elif [[ $token = "{" ]]; then + (( ++ depth )) + elif [[ $token = "}" ]]; then + (( -- depth )) + fi + + # Check if any final function-flag is raised + if (( cur_fun )); then + fun_name=$token + fun_depth=$depth + fun_stack_depths+=( $depth ) + elif (( prev_fun )); then + fun_name=$prev_token + fun_depth=$depth + fun_stack_depths+=( $depth ) + fi + + # Track nested functions + if (( nested_fun + prev_nested_fun )); then + fun_stack_depths+=( $depth ) + fi + + # Ascent to function - skip '{' + if (( fun_depth >= 0 && depth == (fun_depth + 1) )) && [[ $token = "{" ]]; then + : + # In function + elif (( fun_depth >= 0 && depth > fun_depth )); then + if [[ $token != [[:blank:]]#\#* ]]; then + funs[$fun_name]+=${spaces}${token} + fi + # Handle descent from nested function + if (( ${#fun_stack_depths} > 0 && depth == fun_stack_depths[-1] && prev_depth == fun_stack_depths[-1] + 1 )); then + fun_stack_depths[-1]=() + fi + # In anonymous-function + elif (( anon_depth >= 0 && depth > anon_depth )); then + if (( ${#fun_stack_depths} > 0 && depth == fun_stack_depths[-1] && prev_depth == fun_stack_depths[-1] + 1 )); then + fun_stack_depths[-1]=() + fi + # Descent from function - skip '}' + elif (( fun_depth >= 0 && depth == fun_depth && prev_depth == fun_depth + 1 )); then + descentff=1 + # Descent from anon + elif (( anon_depth >= 0 && depth == anon_depth && prev_depth == anon_depth + 1 )); then + descentfa=1 + fi + + # Anon function in top-level + if (( anon_depth >= 0 && fun_depth < 0 )); then + [[ $token != [[:blank:]]#\#* ]] && preamble+=${spaces}${token} + fi + + ### Detect function call + if [[ $spaces = [[:blank:]]#([^\\]|)$'\n'* || -z $prev_token || ${TOKEN_TYPES[$prev_token]} = [1234] ]]; then + if [[ $spaces != [[:blank:]]#"\\"(|$'\r')$'\n'[[:blank:]]# || ${TOKEN_TYPES[$prev_token]} = [1234] ]]; then + at_command=1 + fi + fi + if (( at_command )); then + at_command=0 + + # Prepare call-tree extraction + # Search for this possible function ($token) in current script + local tokenEx=${(q)name}/${(q)token} + local found=${known_functions[(r)$tokenEx]} candidate="" last_candidate="" + integer nth=1 + if [[ -z $found ]]; then + # Search for other scripts having this possible function + tokenEx="*/${(q)token}" + while (( 1 )); do + candidate=${known_functions[(rn:nth:)$tokenEx]} + if [[ -n $candidate ]]; then + last_candidate=$candidate + found=${sourced_files[(r)*${candidate:h}*]} + [[ -n $found ]] && break + else + break + fi + (( ++ nth )) + done + found=$last_candidate + fi + if [[ -z $fun_name ]]; then + local needle=${(q)name}/zsd_script_body + else + local needle=${(q)name}/${(q)fun_name} + fi + + # Extract call-tree and reversed call-tree, and also features + if [[ $cur_fun -eq 0 && $next_token != "()" && -n $found && $fun_stack_depths[-1] -le 0 ]]; then + [[ -z $fun_name ]] && local fukey=zsd_script_body || local fukey=${(q)fun_name} + [[ ${call_tree[${(q)name}/$fukey]} != *[[:blank:]]${(q)found}[[:blank:]]* ]] && { + call_tree[${(q)name}/$fukey]+=" ${(q)found} " + } + [[ ${rev_call_tree[${(q)found}]} != *[[:blank:]]${needle}[[:blank:]]* ]] && { + rev_call_tree[${(q)found}]+=" ${(q)name}/$fukey " + } + fi + + if [[ $cur_fun -eq 0 && $next_token != "()" && $fun_stack_depths[-1] -le 0 ]]; then + # Features + if [[ $token = ${(~j:|:)feature_list} ]]; then + [[ -z $fun_name ]] && local fkey=zsd_script_body || local fkey=$fun_name + [[ ${features[$fkey]} != *[[:blank:]]${token}[[:blank:]]* ]] && features[$fkey]+=" $token " + [[ $token = source ]] && sourced_files+=( $next_token ) + fi + fi + + # Extract before-function comments + if [[ $token = [[:space:]]#\#* ]]; then + if [[ $spaces = [[:space:]]#$'\n'[[:space:]]#$'\n'* ]]; then + # Store comment in case of the {function:<name.} + # has been given + [[ -n "${cproposal%%$'\n'##}" && -n $given_fun_name ]] && { + cfuns[$given_fun_name]="${${cproposal//$~pattern_give_function_name/}%%$'\n'##}" + given_fun_name="" + } + + # Comment reset + if [[ -n $OPT_CIGNORE && $token = ${~OPT_CIGNORE} ]]; then + cproposal="" + else + cproposal=$token$next_spaces + fi + given_fun_name="" + else + # Comment extension + if [[ -z $OPT_CIGNORE || $token != ${~OPT_CIGNORE} ]]; then + cproposal+=$token$next_spaces + fi + fi + # Detect the {function:<NAME>} bit that sets the function + # to which the comment belongs + if [[ $token = (#b)*${~pattern_give_function_name}* ]]; then + given_fun_name=${match[1]} + fi + else + [[ -n "${cproposal%%$'\n'##}" && -n $given_fun_name ]] && { + cfuns[$given_fun_name]="${${cproposal//$~pattern_give_function_name/}%%$'\n'##}" + given_fun_name="" + } + if [[ $token != function && ( $next_token != "()" || $next_spaces = *$'\n'* ) ]]; then + cproposal="" + fi + fi + fi + + ### Use of environment START + + reply=() + [[ $token != [[:blank:]]#\#* ]] && find-variables $token + for env_var in "${reply[@]}"; do + # First search if current script exports the env_var + local env_var_Ext=${(q)name}/${(q)env_var} + local found=${known_env[(r)$env_var_Ext]} candidate="" last_candidate="" + integer nth=1 + if [[ -z $found ]]; then + # Search for other scripts exporting this env_var + env_var_Ext="*/${(q)env_var}" + while (( 1 )); do + candidate=${known_env[(rn:nth:)$env_var_Ext]} + if [[ -n $candidate ]]; then + last_candidate=$candidate + found=${sourced_files[(r)*${candidate:h:h}*]} + [[ -n $found ]] && break + else + break + fi + (( ++ nth )) + done + found=$last_candidate + fi + + [[ -n $found ]] && { + if [[ -z $fun_name ]]; then + [[ ${env_list[${(q)name}/zsd_script_body]} != *[[:blank:]]${(q)found}[[:blank:]]* ]] && { + env_list[${(q)name}/zsd_script_body]+=" ${(q)found} " + } + else + [[ ${env_list[${(q)name}/${(q)fun_name}]} != *[[:blank:]]${(q)found}[[:blank:]]* ]] && { + env_list[${(q)name}/${(q)fun_name}]+=" ${(q)found} " + } + fi + } + done + + ### Use of environment END + + # Store function comment + if (( cur_fun + prev_fun )); then + [[ -n "${cproposal%%$'\n'##}" ]] && cfuns[$fun_name]="${cproposal%%$'\n'##}" + fi + + # Late disable of anonymous function + if (( descentfa )); then + anon_depth=-1 + # Late disable of normal function + elif (( descentff )); then + fun_name="" + fun_depth=-1 + fun_stack_depths[-1]=() + # No-function text gathering + elif (( next_fun == 0 && cur_fun == 0 && prev_fun == 0 && anon_depth < 0 && fun_depth < 0 )); then + if [[ $next_token != "()" || $next_spaces = [[:blank:]]#([^\\]|)$'\n'* || ${TOKEN_TYPES[$token]} = [34] ]]; then + [[ $token != [[:blank:]]#\#* ]] && preamble+=${spaces}${token} + fi + fi + + # History of state + prev_depth=$depth + prev_token=$token + prev_spaces=$spaces +done + +command mkdir -p zsdoc/data +command rm -f zsdoc/data/call_tree.zsd +command rm -f zsdoc/data/rev_call_tree.zsd + +# +# Call-tree file +# + +for fun_name in "${(ko@)call_tree}"; do + echo "$fun_name:${call_tree[$fun_name]% }" >>| zsdoc/data/call_tree.zsd +done + +[[ -z $OPT_QUIET ]] && print "$fg[yellow]Written call tree$reset_color (${#call_tree} callers)" + +# +# Reverse call-tree file +# + +for fun_name in "${(ko@)rev_call_tree}"; do + echo "$fun_name: ${rev_call_tree[$fun_name]% }" >>| zsdoc/data/rev_call_tree.zsd +done + +[[ -z $OPT_QUIET ]] && print "$fg[yellow]Written reverse call tree$reset_color (${#rev_call_tree} called functions)" + +# +# Comments +# + +command mkdir -p zsdoc/data/descriptions/$name + +for fun_name in "${(ko@)cfuns}"; do + [[ -z ${cfuns[$fun_name]} ]] && continue + [[ -z $OPT_QUIET ]] && print "Extracted `line_count ${cfuns[$fun_name]}`-line comment of \`$fg[green]${fun_name}$reset_color'..." + command mkdir -p "zsdoc/data/descriptions/$name/${fun_name:h}" + echo ${cfuns[$fun_name]} >| zsdoc/data/descriptions/$name/$fun_name +done + +# +# Trees +# + +command mkdir -p zsdoc/data/trees/$name +local PWDBKP=$PWD +cd zsdoc/data/trees/$name +integer count=0 +local -a PROCESSED +for fun_name in "${(ko@)call_tree}"; do + (( ++ count )) + PROCESSED=() + process_node $fun_name + local fun=${fun_name##*/} + if [[ $fun = zsd_script_body ]]; then + zsd-run-tree-convert Script_Body_ > Script_Body_.tree + else + zsd-run-tree-convert $fun > ${fun}.tree + fi +done + +cd $PWDBKP + +[[ -z $OPT_QUIET ]] && print "$fg[yellow]Generated $count trees$reset_color" + +# +# Features +# + +command mkdir -p zsdoc/data/features/$name +local -a feats +local feat holder +for fun_name in "${(ko@)features}"; do + if [[ $fun_name = zsd_script_body ]]; then + holder=zsdoc/data/features/$name/Script_Body_ + else + holder=zsdoc/data/features/$name/$fun_name + fi + + command mkdir -p $holder + + feats=( "${(z@)features[$fun_name]}" ) + for feat in "${(o)feats[@]}"; do + echo >| $holder/$feat + done +done + +# +# Use of environment +# + +command mkdir -p zsdoc/data/env-use/$name +local -a used_envs +local myenv +for fun_name in "${(ko@)env_list}"; do + used_envs=( "${(z@)env_list[$fun_name]}" ) + [[ -z ${used_envs[1]} ]] && continue + + for myenv in "${used_envs[@]}"; do + # env-use/of-script-$name/in-its-function-${fun_name:t}/exported-in-script-.../in-function-.../the-varname-... + command mkdir -p "zsdoc/data/env-use/$name/${fun_name:t}/${myenv:h}" + command touch "zsdoc/data/env-use/$name/${fun_name:t}/$myenv" + done +done + +return 0 diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-detect.preamble b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-detect.preamble new file mode 100644 index 0000000..4e3e229 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-detect.preamble @@ -0,0 +1,69 @@ +#!/usr/bin/env zsh +# This file is double-licensed under GPLv3 and MIT (see LICENSE file) + +emulate -R zsh +setopt extendedglob typesetsilent warncreateglobal noshortloops + +local MATCH REPLY +integer MBEGIN MEND +local -a match mbegin mend reply + +local -a ZSD_PB_WORDS ZSD_PB_SPACES ZSD_PB_WORDS_BEGINNINGS +integer ZSD_PB_SELECTED_WORD +local ZSD_PB_LEFT ZSD_PB_RIGHT + +local -A colors +autoload colors +colors 2>/dev/null + +usage() { + print "ZSD INTERNAL SCRIPT" + print "$fg[green]Usage:$reset_color zsd-detect [-h/--help] [-v/--verbose] [-q/--quiet] [-n/--noansi] [--cignore <pattern>] $fg_bold[magenta]{file}$reset_color" + print "The $fg_bold[magenta]file$reset_color will be processed to gather function comments and call trees." + print "Data extracted by \`zsd-transform' is needed. Supported are Bash and Zsh scripts." + print + print "$fg[green]Options:$reset_color" + print -- "$fg[magenta]-h/--help$reset_color Usage information" + print -- "$fg[magenta]-v/--verbose$reset_color More verbose operation-status output" + print -- "$fg[magenta]-q/--quiet$reset_color No status messages" + print -- "$fg[magenta]-n/--noansi$reset_color No colors in terminal output" + print -- "$fg[magenta]--cignore$reset_color Specify which comment lines should be ignored" + print + print -- "Example --cignore options:" + print -- "--cignore '\\#*FUNCTION:*{{{*' - ignore comments like: $fg[green]# FUNCTION: usage {{{$reset_color" + print -- "--cignore '(\\#*FUNCTION:*{{{*|\\#*FUN:*{{{*)' - also ignore comments like: $fg[green]# FUN: usage {{{$reset_color" +} + +# Obtains node name (function with owning script), checks what functions does +# it call, recursively entering to those functions and repeating the process. +# After each call, it first creates directory of the passed node name, and +# enters it. At exit, it moves to previous parent directory. +process_node() { + local node=$1 + if [[ $node = ${(q)name}/zsd_script_body ]]; then + command mkdir -p -- Script_Body_ + cd Script_Body_ + else + if [[ ${node#${(q)name}} = ${node} ]]; then + local dir=${node/\//_-_} + else + local dir=${node##*/} + fi + command mkdir -p -- $dir + cd -- $dir + PROCESSED+=( $node ) + fi + + local subfuns=${call_tree[$node]} + local -a asubfuns + asubfuns=( ${(z@)subfuns} ) + + local f + for f in ${asubfuns[@]}; do + [[ -n ${PROCESSED[(r)$f]} ]] && continue + process_node $f + done + + cd .. + PROCESSED[-1]=() +} diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-process-buffer b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-process-buffer new file mode 100644 index 0000000..d3be60d --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-process-buffer @@ -0,0 +1,108 @@ +# This file is double-licensed under GPLv3 and MIT (see LICENSE file) + +# Input: +# $1 - optional buffer to process (default is $BUFFER) +# $2 - optional parameter containing cursor (default is $CURSOR) +# +# Output: +# ZSD_PB_WORDS - split of "$1" into shell words; array +# ZSD_PB_WORDS_BEGINNINGS - indexes of first letters of corresponding words in ZSD_PB_WORDS +# ZSD_PB_SPACES - white spaces before corresponding words in ZSD_PB_WORDS +# ZSD_PB_SELECTED_WORD - index in ZSD_PB_WORDS pointing to word activated by cursor position +# ZSD_PB_LEFT - left part of active word +# ZSD_PB_RIGHT - right part of active word +# + +emulate -LR zsh +setopt extendedglob typesetsilent warncreateglobal noshortloops + +local MATCH +integer MBEGIN MEND +local -a mbegin mend match + +local buf=${1:-$BUFFER} +local cursor=${2:-$CURSOR} + +# All output variables are either overwritten or cleared +ZSD_PB_WORDS=( ${(Z+cn+)buf} ) +ZSD_PB_SPACES=( ) +ZSD_PB_WORDS_BEGINNINGS=( ) +ZSD_PB_SELECTED_WORD=-1 +ZSD_PB_LEFT="" +ZSD_PB_RIGHT="" + +# (Z+n+) will return 1 element for buf that is empty or only whitespace +if [[ $buf = ( |$'\t')# ]]; then + ZSD_PB_WORDS=( ) + integer nwords=0 +else + integer nwords=${#ZSD_PB_WORDS} +fi + +# Remove ZSD_PB_WORDS one by one, counting characters, +# computing beginning of each word, to find +# place to break the word into 2 halves (for +# complete_in_word option) + +local i word wordlen tword +integer char_count=0 + +# (Z) handles spaces nicely, but we need them for the user +# Also compute words beginnings and the selected word +for (( i=1; i<=nwords; i++ )); do + # Remove spurious space generated by Z-flag when + # input is an unbound '$(' (happens with zsh < 5.1) + # and also real spaces gathered by an unbound '$(', + # to handle them in a way normal to this loop + ZSD_PB_WORDS[i]=${ZSD_PB_WORDS[i]%% ##} + word=${ZSD_PB_WORDS[i]} + wordlen=${#word} + + # In general, $buf can start with white spaces + # We will not search for them, but instead for + # leading character of current shell word, + # negated. This is an ambition to completely + # avoid character classes + + # Remove white spaces + buf=${buf##(#m)[^$word[1]]#} + # Count them + char_count=char_count+${#MATCH} + # This is the beginning of current word + ZSD_PB_WORDS_BEGINNINGS[i]=$(( char_count + 1 )) + # Remember the spaces + ZSD_PB_SPACES[i]=$MATCH + + tword=${buf[1,wordlen]} + + # Remove the word + [[ $tword != $word && ${#word} != ${#tword} ]] && { echo "BUG in processing ${buf[1,wordlen]} vs. $word"; return 1; } + buf=${buf[wordlen+1,-1]} + + # Spaces point to previous shell word + # Visual cursor right after spaces (-ge) -> not enough to select previous word (-gt required) + [[ $ZSD_PB_SELECTED_WORD -eq -1 && $char_count -gt $cursor ]] && ZSD_PB_SELECTED_WORD=$(( i-1 )) + + # Actual characters point to current shell word + # Visual cursor right after letters (-ge) -> enough to select current word + char_count=char_count+${#word} + [[ $ZSD_PB_SELECTED_WORD -eq -1 && $char_count -ge $cursor ]] && ZSD_PB_SELECTED_WORD=$i +done + +# What's left in $buf can be only white spaces +char_count=char_count+${#buf} +ZSD_PB_SPACES[i]=$buf + +# Visual cursor right after spaces (-ge) -> enough to select last word +[[ $ZSD_PB_SELECTED_WORD -eq -1 && $char_count -ge $cursor ]] && ZSD_PB_SELECTED_WORD=$(( i-1 )) + +# Divide active word into two halves +integer diff=$(( cursor - ZSD_PB_WORDS_BEGINNINGS[ZSD_PB_SELECTED_WORD] + 1 )) +word=${ZSD_PB_WORDS[ZSD_PB_SELECTED_WORD]} +ZSD_PB_LEFT=${word[1,diff]} +ZSD_PB_RIGHT=${word[diff+1,-1]} + +# This function should be tested +return 0 + +# vim:ft=zsh diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-to-adoc.main b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-to-adoc.main new file mode 100644 index 0000000..3b45517 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-to-adoc.main @@ -0,0 +1,352 @@ +# vim:ft=zsh:et:sw=4 +# This file is double-licensed under GPLv3 and MIT (see LICENSE file) + +### Options ### + +local OPT_HELP OPT_VERBOSE OPT_QUIET OPT_NOANSI OPT_SYNOPSIS OPT_BLOCKA OPT_BLOCKB OPT_SCOMM OPT_BASH +local -A opthash +zparseopts -E -D -A opthash h -help v -verbose q -quiet r -really-quiet \ + n -noansi -synopsis: -blocka: -blockb: -scomm -bash || \ + { echo "Improper options given, see help (-h/--help)"; return 1; } + +(( ${+opthash[-h]} + ${+opthash[--help]} )) && OPT_HELP=-h +(( ${+opthash[-v]} + ${+opthash[--verbose]} )) && OPT_VERBOSE=-v +(( ${+opthash[-q]} + ${+opthash[--quiet]} )) && OPT_QUIET=-q +(( ${+opthash[-r]} + ${+opthash[--really-quiet]} )) && { OPT_QUIET=-q; OPT_RQUIET=-r; } +(( ${+opthash[-n]} + ${+opthash[--noansi]} )) && OPT_NOANSI=-n +(( ${+opthash[--synopsis]} )) && OPT_SYNOPSIS=${opthash[--synopsis]} +(( ${+opthash[--blocka]} )) && OPT_BLOCKA=${opthash[--blocka]} +(( ${+opthash[--blockb]} )) && OPT_BLOCKB=${opthash[--blockb]} +(( ${+opthash[--scomm]} )) && OPT_SCOMM=--scomm +(( ${+opthash[--bash]} )) && OPT_BASH=--bash + +[[ -n $OPT_NOANSI ]] && { colors=(); fg=(); bg=(); fg_bold=(); bg_bold=(); reset_color=; } + +if [[ -n $OPT_HELP ]]; then + usage + return 0 +fi + +if [[ $# -le 0 || $* = [[:space:]]## ]]; then + print -- "zsd-to-adoc: Argument needed, see help (-h/--help)" + return 1 +fi + +if [[ ! -f $1 ]]; then + [[ -z $OPT_QUIET || -n $OPT_VERBOSE ]] && print -- "$fg[magenta]zsd-to-adoc: File \`$1' doesn't exist, skipping it (see help, with -h/--help option)$reset_color" + return 1 +fi + +if [[ ! -r $1 ]]; then + [[ -z $OPT_QUIET || -n $OPT_VERBOSE ]] && print -- "$fg[magenta]zsd-to-adoc: File \`$1' is unreadable, skipping it (see help, with -h/--help option)$reset_color" + return 1 +fi + +[[ -z $OPT_QUIET ]] && print -- "$fg[cyan]== zsd-to-adoc starting for file \`$fg_bold[yellow]$1$fg_no_bold[cyan]' (3rd pass)$reset_color" + +### Code ### + +line_count() +{ + local -a list + list=( "${(@f)1}" ) + local count=${#list} + [[ $1 = *$'\n' ]] && (( -- count )) + print -r -- $count +} + +local name=${1:t} empty tmp script_body body_comments body nl=$'\n' +local ofname="${name}.adoc" fun +integer len env_vars_count=0 empty_env_vars_count=0 +local -a arr + +command rm -f -- zsdoc/$ofname + +{ + # Load body of the script + script_body=$(<zsdoc/data/bodies/$name) + + # HEADER + if [[ ${name[1]} = . ]]; then + print -- " $name(1)" + len=${#name}+4 + print -- ${(r:len::=:)empty} + else + print -- "$name(1)" + len=${#name}+3 + print -- ${(r:len::=:)empty} + fi + print -- ":compat-mode!:" + print -- + + # NAME + print -- NAME + if [[ ${name[1]} = "." ]]; then + print -- ${(r:5::-:)empty} + print -- " $name - a shell script" + else + print -- ${(r:4::-:)empty} + print -- "$name - a shell script" + fi + print + + # SYNOPSIS + print -- SYNOPSIS + print -- ${(r:8::-:)empty} + if [[ -n $OPT_SYNOPSIS ]]; then + print -- $OPT_SYNOPSIS + else + # Load body of the script + body_comments=$(<zsdoc/data/bodies/$name.comments) + + body=${(S)body_comments/(#bi)*synopsis*${OPT_BLOCKA:-\{\{\{}(*)${OPT_BLOCKB:-\}\}\}}*/${match[1]}}; + if [[ $body_comments != $body ]]; then + print -r -- "${match[1]//((#s)|$nl)[[:blank:]]#\#[[:blank:]]#/$nl}" + else + print "Documentation automatically generated with \`zshelldoc'" + fi + fi + print + + # FUNCTIONS + print -- FUNCTIONS + print -- ${(r:9::-:)empty} + print + + local -a funs hooks + funs=( zsdoc/data/functions/$name/*(DN) ) + funs=( "${funs[@]:t}" ) + hooks=( zsdoc/data/hooks/$name/*(DN) ) + hooks=( "${hooks[@]:t}" ) + + # Not hooks, not autoloads + for fun in "${funs[@]}"; do + if [[ -n ${hooks[(r)$fun]} ]]; then + continue + else + print -r -- " $fun" + fi + done + + # Autoloads + funs=( zsdoc/data/autoload/$name/*(DN) ) + funs=( "${funs[@]:t}" ) + + for fun in "${funs[@]}"; do + print -r -- "AUTOLOAD $fun" + done + + # Hooks + for fun in "${hooks[@]}"; do + print -r -- "${(U)$(<zsdoc/data/hooks/$name/$fun)}-HOOK $fun" + done + + # PREPARE ENVIRONMENT VARS' DESCRIPTIONS + local work_comments=$body_comments + local env_desc=${(S)work_comments/(#bi)*env-vars*${OPT_BLOCKA:-\{\{\{}(*)${OPT_BLOCKB:-\}\}\}}/${match[1]}}; + integer fixed_envs_offset=0 + local -a fixed_env_descs + while [[ $work_comments != $env_desc ]]; do + local -a sorted + sorted=( "${(@s:->:)match[1]}" ) + integer sidx ssize=${#sorted} + for (( sidx=2; sidx <= ssize; ++ sidx )); do + if [[ ${sorted[sidx-1]} = (#b)*((#s)|$nl)([^$nl]#)(#e) ]]; then + fixed_env_descs[fixed_envs_offset+(sidx-1)*2-1]="${${match[2]##\#[[:space:]]#}%%[[:space:]]#}" + fixed_env_descs[fixed_envs_offset+(sidx-1)*2]=${sorted[sidx]} + (( sidx > 2 )) && fixed_env_descs[fixed_envs_offset+(sidx-2)*2]="${fixed_env_descs[fixed_envs_offset+(sidx-2)*2]%${match[2]}}" + fi + done + + (( ssize >= 2 )) && env_vars_count+=1 || empty_env_vars_count+=1 + + ssize=${#fixed_env_descs} + for (( sidx=fixed_envs_offset+1; sidx <= ssize; ++ sidx )); do + fixed_env_descs[sidx]="${${${fixed_env_descs[sidx]//((#s)|$nl)[[:blank:]]#\##[[:blank:]]#/$nl}%$nl}#$nl}" + done + + fixed_envs_offset=${#fixed_env_descs} + + # Any following env-vars section? + work_comments=$env_desc + env_desc=${(S)work_comments/(#bi)*env-vars*${OPT_BLOCKA:-\{\{\{}(*)${OPT_BLOCKB:-\}\}\}}(*)/${match[1]}}; + done + + if (( ${#fixed_env_descs} > 0 )); then + local -A fixed_env_descs_hash + fixed_env_descs_hash=( "${fixed_env_descs[@]}" ) + print + print -- "ENVIRONMENT VARIABLES" + print -- ${(r:21::-:)empty} + print -- '[width="80%",cols="4,10"]' + print -- "|======" + local key + for key in "${(kon)fixed_env_descs_hash[@]}"; do + print -- "|$key|${fixed_env_descs_hash[$key]}" + done + print -- "|======" + fi + + # DETAILS + print + print -- DETAILS + print -- ${(r:7::-:)empty} + print + + # SCRIPT BODY + + [[ -f zsdoc/data/bodies/$name ]] && { + print -r -- "Script Body" + print -r -- "${(r:11::~:)empty}" + print + [[ -f zsdoc/data/trees/$name/Script_Body_.tree ]] && { + print -r -- "Has `line_count $script_body` line(s). Calls functions:" + print + arr=( "${(@f)"$(<zsdoc/data/trees/$name/Script_Body_.tree)"}" ) + [[ ${#arr} -ge 3 ]] && arr=( "${(@)arr[1,-3]}" ) + arr=( "${arr[@]/Script_Body_/Script-Body}" ) + arr=( "${arr[@]/(#s)/ }" ) + print -r -- ${(F)arr} + } || { + if [[ -n $OPT_BASH ]]; then + print -r -- "Has `line_count $script_body` line(s). No functions are called (may set up e.g. command_not_found_handle or call a function indirectly in other way)." + else + print -r -- "Has `line_count $script_body` line(s). No functions are called (may set up e.g. a hook, a Zle widget bound to a key, etc.)." + fi + } + print + + local -a features + features=( zsdoc/data/features/$name/Script_Body_/*(DN) ) + features=( "${(@)features#zsdoc/data/features/$name/Script_Body_/}" ) + + if [[ ${#features} -gt 0 ]]; then + print -- "Uses feature(s): _${(oj:_, _:)features}_" + print + fi + + local -a exports + exports=( zsdoc/data/exports/$name/Script_Body_/*(DN) ) + exports=( "${(@)exports#zsdoc/data/exports/$name/Script_Body_/}" ) + + if [[ ${#exports} -gt 0 ]]; then + print -- "_Exports (environment):_ ${(oj: [big]*//* :)exports}" + print + fi + } + + # FUNCTIONS + + funs=( zsdoc/data/functions/$name/*(DN) zsdoc/data/autoload/$name/*(DN) ) + funs=( "${funs[@]:t}" ) + + for fun in "${funs[@]}"; do + print -r -- $fun + len=${#fun} + print -r -- "${(r:len::~:)empty}" + print + + ## Comment + + [[ -f zsdoc/data/descriptions/$name/$fun ]] && { + zsd-trim-indent "$(<zsdoc/data/descriptions/$name/$fun)" + [[ -n $OPT_SCOMM ]] && REPLY="${REPLY//((#s)|$nl)[[:blank:]]#\#[[:blank:]]#/$nl}" + arr=( "${(@f)REPLY}" ) + [[ ${arr[-1]} = [[:space:]]#\#[[:space:]]# ]] && arr[-1]=() + arr=( "${arr[@]/(#s)/ }" ) + print -- ____ + print -rl -- "${arr[@]}" + print -- ____ + print + } + + ## Number of lines information and call tree + + { body=$(<zsdoc/data/functions/$name/$fun); } 2>/dev/null + [[ -z $body ]] && body=$(<zsdoc/data/autoload/$name/$fun) + + [[ -f zsdoc/data/trees/$name/$fun.tree ]] && { + arr=( "${(@f)"$(<zsdoc/data/trees/$name/$fun.tree)"}" ) + [[ ${#arr} -ge 3 ]] && arr=( "${(@)arr[1,-3]}" ) + arr=( "${arr[@]/Script_Body_/Script-Body}" ) + arr=( "${arr[@]/(#s)/ }" ) + if [[ -n ${hooks[(r)$fun]} ]]; then + print -r -- "Has `line_count $body` line(s). *Is a $(<zsdoc/data/hooks/$name/$fun) hook*. Calls functions:" + else + print -r -- "Has `line_count $body` line(s). Calls functions:" + fi + print + print -r -- ${(F)arr} + } || { + if [[ -n ${hooks[(r)$fun]} ]]; then + print -- "Has `line_count $body` line(s). *Is a $(<zsdoc/data/hooks/$name/$fun) hook*. Doesn't call other functions." + else + print -- "Has `line_count $body` line(s). Doesn't call other functions." + fi + } + print + + ## Features + + local -a features + features=( zsdoc/data/features/$name/$fun/*(DN) ) + features=( "${(@)features#zsdoc/data/features/$name/$fun/}" ) + + if [[ ${#features} -gt 0 ]]; then + print -- "Uses feature(s): _${(oj:_, _:)features}_" + print + fi + + ## Reverse call tree + + arr=( zsdoc/data/trees/$name/*/$fun(DN) zsdoc/data/trees/*/*/*_-_$fun(DN) ) + if [[ ${#arr} -eq 0 ]]; then + if [[ -n $OPT_BASH ]]; then + print -r -- "Not called by script or any function (may be e.g. command_not_found_handle or called indirectly in other way)." + else + print -r -- "Not called by script or any function (may be e.g. a hook, a Zle widget, etc.)." + fi + print + else + print -- "Called by:" + print + arr=( "${arr[@]#zsdoc/data/trees/$name/}" ) + arr=( "${arr[@]#zsdoc/data/trees/}" ) + arr=( "${arr[@]%/*}" ) + arr=( "${arr[@]//_-_//}" ) + arr=( "${arr[@]/Script_Body_/Script-Body}" ) + arr=( "${(u)arr[@]}" ) + print -rl -- "${arr[@]/(#s)/ }" + print + fi + + ## Exports + + local -a exports + exports=( zsdoc/data/exports/$name/$fun/*(DN) ) + exports=( "${exports[@]#zsdoc/data/exports/$name/$fun/}" ) + + if [[ ${#exports} -gt 0 ]]; then + print -- "_List of exports (to environment):_ ${(oj: [big]*//* :)exports}" + print + fi + + ## Use of environment variables + + local -a env_uses + env_uses=( zsdoc/data/env-use/$name/$fun/*/*/*(DN) ) + env_uses=( "${env_uses[@]#zsdoc/data/env-use/$name/$fun/}" ) + env_uses=( "${env_uses[@]//(#b)([^\/]##)\/[^\/]##\/(*)/${match[1]} -> ${match[2]}}" ) + env_uses=( "${env_uses[@]/$name -> /}" ) + + if [[ ${#env_uses} -gt 0 ]]; then + print -- "_Environment variables used:_ ${(oj: [big]*//* :)env_uses}" + print + fi + done +} >>| zsdoc/$ofname + +print -- "AsciiDoc generated under \`zsdoc/$ofname'. Found $env_vars_count env-vars sections${${${empty_env_vars_count:#0}:+ (+$empty_env_vars_count empty ones).}:-.}" +[[ -z $OPT_RQUIET ]] && print -- "You can run \`asciidoctor $ofname' to generate HTML or commit" +[[ -z $OPT_RQUIET ]] && print -- "$ofname into GitHub (GitHub does render *.adoc files)." +return 0 diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-to-adoc.preamble b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-to-adoc.preamble new file mode 100644 index 0000000..d2e7288 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-to-adoc.preamble @@ -0,0 +1,17 @@ +#!/usr/bin/env zsh +# This file is double-licensed under GPLv3 and MIT (see LICENSE file) + +emulate -R zsh +setopt extendedglob typesetsilent warncreateglobal noshortloops + +local MATCH REPLY +integer MBEGIN MEND +local -a match mbegin mend reply + +local -a ZSD_PB_WORDS ZSD_PB_SPACES ZSD_PB_WORDS_BEGINNINGS +integer ZSD_PB_SELECTED_WORD +local ZSD_PB_LEFT ZSD_PB_RIGHT + +local -A colors +autoload colors +colors 2>/dev/null diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-transform.main b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-transform.main new file mode 100644 index 0000000..6a2fad6 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-transform.main @@ -0,0 +1,303 @@ +# vim:ft=zsh:et:sw=4 +# This file is double-licensed under GPLv3 and MIT (see LICENSE file) + +### Options ### + +local OPT_HELP OPT_VERBOSE OPT_QUIET OPT_NOANSI +local -A opthash +zparseopts -E -D -A opthash h -help v -verbose q -quiet r -really-quiet n -noansi || { echo "Improper options given, see help (-h/--help)"; return 1; } + +(( ${+opthash[-h]} + ${+opthash[--help]} )) && OPT_HELP=-h +(( ${+opthash[-v]} + ${+opthash[--verbose]} )) && OPT_VERBOSE=-v +(( ${+opthash[-q]} + ${+opthash[--quiet]} )) && OPT_QUIET=-q +(( ${+opthash[-r]} + ${+opthash[--really-quiet]} )) && { OPT_QUIET=-q; OPT_RQUIET=-r; } +(( ${+opthash[-n]} + ${+opthash[--noansi]} )) && OPT_NOANSI=-n + +[[ -n $OPT_NOANSI ]] && { colors=(); fg=(); bg=(); fg_bold=(); bg_bold=(); reset_color=; } + +[[ -z $OPT_QUIET ]] && print "$fg[cyan]== zsd-transform starting for file \`$fg_bold[yellow]$1$fg_no_bold[cyan]' (1-st pass)$reset_color" + +if [[ -n $OPT_HELP ]]; then + usage + return 0 +fi + +if [[ $# -le 0 || $* = [[:space:]]## ]]; then + print "Argument needed, see help (-h/--help)" + return 1 +fi + +if [[ ! -f $1 ]]; then + [[ -z $OPT_QUIET || -n $OPT_VERBOSE ]] && print "$fg[magenta]File \`$1' doesn't exist, skipping it (see help, with -h/--help option)$reset_color" + return 1 +fi + +if [[ ! -r $1 ]]; then + [[ -z $OPT_QUIET || -n $OPT_VERBOSE ]] && print "$fg[magenta]File \`$1' is unreadable, skipping it (see help, with -h/--help option)$reset_color" + return 1 +fi + +### Code ### + +local name=${1:t} nl=$'\n' +local doc="$(<$1)" token prev_token= spaces prev_spaces= next_token next_spaces + +local preamble= preamble_comment= fun_name= hook= +local -A funs auto_funs myhooks myexports +local -a arr + +# Function extraction +integer at_command=1 in_autoload=0 in_azh=0 in_export=0 +integer next_fun=0 cur_fun=0 prev_fun=0 +integer depth=0 prev_depth=0 fun_depth=-1 anon_depth=-1 descentff=0 descentfa=0 + +# Nested functions tracking +integer nested_fun=0 next_nested_fun=0 prev_nested_fun=0 +local -a fun_stack_depths + +line_count() +{ + local -a list + list=( "${(@f)1}" ) + local count=${#list} + [[ $1 = *$'\n' ]] && (( -- count )) + print -r -- "${(l:3:: :)count}" +} + + +zsd-process-buffer $doc 1 +integer i size=${#ZSD_PB_WORDS} + +for (( i=1; i<=size; ++ i )); do + token=${ZSD_PB_WORDS[i]} + spaces=${ZSD_PB_SPACES[i]} + next_token=${ZSD_PB_WORDS[i+1]} + next_spaces=${ZSD_PB_SPACES[i+1]} + + cur_fun=0 prev_fun=0 descentff=0 descentfa=0 + nested_fun=0 prev_nested_fun=0 + + (( next_fun )) && { next_fun=0 cur_fun=1 prev_fun=0 anon_depth=-1; } + (( next_nested_fun )) && { next_nested_fun=0 nested_fun=1 prev_nested_fun=0; } + + # Explicit future-function + if [[ $token = function && ( $fun_depth -lt 0 ) && ( $anon_depth -lt 0 ) ]]; then + next_fun=1 cur_fun=0 prev_fun=0 anon_depth=-1 + # (Detect function if not already in function) + # Detect top-level prev-function differentiating from anonymous function + elif [[ $token = "()" && ( $fun_depth -lt 0 ) && ( $anon_depth -lt 0 ) ]]; then + if [[ $spaces = *$'\n'* || -z $prev_token || ${TOKEN_TYPES[$prev_token]} = [1234] ]]; then + next_fun=0 cur_fun=0 prev_fun=0 anon_depth=$depth + else + next_fun=0 cur_fun=0 prev_fun=1 anon_depth=-1 + fi + # Must be a nested future-function + elif [[ $token = function ]]; then + next_nested_fun=1 nested_fun=0 prev_nested_fun=0 + # Is it a nested prev-function? + elif [[ $token = "()" && $nested_fun -eq 0 && $depth -gt $fun_stack_depths[-1] ]]; then + if [[ $spaces != *$'\n'* && -n $prev_token && ${TOKEN_TYPES[$prev_token]} != [1234] ]]; then + next_nested_fun=0 nested_fun=0 prev_nested_fun=1 + fi + elif [[ $token = "{" ]]; then + (( ++ depth )) + elif [[ $token = "}" ]]; then + (( -- depth )) + fi + + # Check if any final function-flag is raised + if (( cur_fun )); then + fun_name=$token + fun_depth=$depth + fun_stack_depths+=( $depth ) + elif (( prev_fun )); then + fun_name=$prev_token + fun_depth=$depth + fun_stack_depths+=( $depth ) + fi + + # Track nested functions + if (( nested_fun + prev_nested_fun )); then + fun_stack_depths+=( $depth ) + fi + + # Ascent to function - skip '{' + if (( fun_depth >= 0 && depth == (fun_depth + 1) )) && [[ $token = "{" ]]; then + : + # In function + elif (( fun_depth >= 0 && depth > fun_depth )); then + if [[ $token != [[:blank:]]#\#* ]]; then + funs[$fun_name]+=${spaces}${token} + fi + # Handle descent from nested function + if (( ${#fun_stack_depths} > 0 && depth == fun_stack_depths[-1] && prev_depth == fun_stack_depths[-1] + 1 )); then + fun_stack_depths[-1]=() + fi + # In anonymous-function + elif (( anon_depth >= 0 && depth > anon_depth )); then + if (( ${#fun_stack_depths} > 0 && depth == fun_stack_depths[-1] && prev_depth == fun_stack_depths[-1] + 1 )); then + fun_stack_depths[-1]=() + fi + # Descent from function - skip '}' + elif (( fun_depth >= 0 && depth == fun_depth && prev_depth == fun_depth + 1 )); then + descentff=1 + # Descent from anon + elif (( anon_depth >= 0 && depth == anon_depth && prev_depth == anon_depth + 1 )); then + descentfa=1 + fi + + # Anon function in top-level + if (( anon_depth >= 0 && fun_depth < 0 )); then + [[ $token != [[:blank:]]#\#* ]] && preamble+=${spaces}${token} || preamble_comment+=${spaces}${token} + fi + + ### Detect function call + # Check for introduction of autoload function + if [[ $spaces = *$'\n'* || -z $prev_token || ${TOKEN_TYPES[$prev_token]} = [1234] ]]; then + if [[ $spaces != [[:blank:]]#"\\"(|$'\r')$'\n'[[:blank:]]# || ${TOKEN_TYPES[$prev_token]} = [1234] ]]; then + at_command=1 + in_autoload=0 + in_azh=0 + in_export=0 + fi + fi + if (( at_command )); then + at_command=0 + if [[ $cur_fun -eq 0 && $next_token != "()" && $fun_stack_depths[-1] -le 0 ]]; then + if [[ $token = autoload ]]; then + in_autoload=1 + elif [[ $token = add-zsh-hook ]]; then + in_azh=1 + elif [[ $token = export ]]; then + in_export=1 + fi + fi + elif (( in_autoload )); then + if [[ $token = -- ]]; then + in_autoload=2 + elif [[ $token != -* || $in_autoload -eq 2 ]]; then + arr=( $^fpath/$token(N) ) + if [[ ${#arr} -ge 1 ]]; then + auto_funs[$token]="$(<${arr[1]})" + #else + # auto_funs[$token]="Autoload-function source not found" + fi + fi + elif (( in_azh )); then + if (( in_azh == 1 )); then + if [[ $token = -- ]]; then + # Defer increment of in_azh + : + elif [[ $token = -* ]]; then + in_azh=0 + else + in_azh=2 + fi + else + myhooks[${token%$'\r'}]=$prev_token + in_azh=0 + fi + elif (( in_export )); then + if [[ $token = (#b)([a-zA-Z_][a-zA-Z0-9_]#)(=(*))(#c0,1) ]]; then + [[ -z $fun_name ]] && local fkey=zsd_script_body || local fkey=$fun_name + [[ ${myexports[$fkey]} != *[[:blank:]]${match[1]}[[:blank:]]* ]] && myexports[$fkey]+=" ${match[1]} " + fi + fi + + # Late disable of anonymous function + if (( descentfa )); then + anon_depth=-1 + # Late disable of normal function + elif (( descentff )); then + fun_name= + fun_depth=-1 + fun_stack_depths[-1]=() + # No-function text gathering + elif (( next_fun == 0 && cur_fun == 0 && prev_fun == 0 && anon_depth < 0 && fun_depth < 0 )); then + if [[ $next_token != "()" || $next_spaces = *$'\n'* || ${TOKEN_TYPES[$token]} = [34] ]]; then + [[ $token != [[:blank:]]#\#* ]] && preamble+=${spaces}${token} || preamble_comment+=${spaces}${token} + fi + fi + + # History of state + prev_depth=$depth + prev_token=$token + prev_spaces=$spaces +done + +### OUTPUT: FUNCTIONS + +command mkdir -p zsdoc/data/functions/$name/${fun_name:h} + +for fun_name in "${(ko@)funs}"; do + [[ -z $OPT_QUIET ]] && print "Extracted `line_count ${funs[$fun_name]}`-line function \`$fg[green]${fun_name}$reset_color'..." + { + zsd-trim-indent ${funs[$fun_name]} + print -r -- $REPLY + } >| zsdoc/data/functions/$name/$fun_name +done + +### OUTPUT: HOOKS + +command mkdir -p zsdoc/data/hooks/$name + +for hook in "${(k)myhooks[@]}"; do + print -r -- ${myhooks[$hook]} >| zsdoc/data/hooks/$name/$hook +done + +### OUTPUT: EXTENDED BODY + +command mkdir -p zsdoc/data/extended/${name:h} +command rm -f zsdoc/data/extended/$name +print -r -- $doc >| zsdoc/data/extended/$name + +### OUTPUT: AUTOLOAD FUNCTIONS AND EXTENDED BODY + +for fun_name in "${(ko@)auto_funs}"; do + command mkdir -p zsdoc/data/autoload/$name/${fun_name:h} + + [[ -z $OPT_QUIET ]] && print "Extracted `line_count ${auto_funs[$fun_name]}`-line $fg[red]AUTOLOAD$reset_color function \`$fg[green]${fun_name}$reset_color'..." + # Autoload function + zsd-trim-indent ${auto_funs[$fun_name]} + print -r -- $REPLY >| zsdoc/data/autoload/$name/$fun_name + + # Appendix of the function to extended body + print "\n" >>| zsdoc/data/extended/$name + arr=( "${(@f)${(M)REPLY##(#s)([[:space:]]#\#[^$nl]#$nl)##}}" ) + print -r -- ${(F)arr[1,10]} >>| \ + zsdoc/data/extended/$name + print -r -- "${fun_name}() {" >>| zsdoc/data/extended/$name + print -r -- $REPLY >>| zsdoc/data/extended/$name + print -r -- "}" >>| zsdoc/data/extended/$name +done + +### OUTPUT: SCRIPT BODIES + +command mkdir -p zsdoc/data/bodies + +print -r -- $preamble >| zsdoc/data/bodies/${name} +print -r -- $preamble_comment >| zsdoc/data/bodies/${name}.comments +[[ -z $OPT_QUIET ]] && print "$fg[yellow]Generated body of script \`$name\'$reset_color (`line_count $preamble` lines)" + +### OUTPUT: EXPORTED VARS + +command mkdir -p zsdoc/data/exports/$name + +local -a exps +local exp holder +for fun_name in "${(ko@)myexports}"; do + if [[ $fun_name = zsd_script_body ]]; then + holder=zsdoc/data/exports/$name/Script_Body_ + else + holder=zsdoc/data/exports/$name/$fun_name + fi + + command mkdir -p $holder + + exps=( "${(z@)myexports[$fun_name]}" ) + for exp in "${(o)exps[@]}"; do + echo >| $holder/$exp + done +done + +return 0 diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-transform.preamble b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-transform.preamble new file mode 100644 index 0000000..7513d92 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-transform.preamble @@ -0,0 +1,30 @@ +#!/usr/bin/env zsh +# This file is double-licensed under GPLv3 and MIT (see LICENSE file) + +emulate -R zsh +setopt extendedglob typesetsilent warncreateglobal noshortloops + +local MATCH REPLY +integer MBEGIN MEND +local -a match mbegin mend reply + +local -a ZSD_PB_WORDS ZSD_PB_SPACES ZSD_PB_WORDS_BEGINNINGS +integer ZSD_PB_SELECTED_WORD +local ZSD_PB_LEFT ZSD_PB_RIGHT + +local -A colors +autoload colors +colors 2>/dev/null + +usage() { + print "ZSD INTERNAL SCRIPT" + print "$fg[green]Usage:$reset_color zsd-transform [-h/--help] [-v/--verbose] [-q/--quiet] [-n/--noansi] $fg_bold[magenta]{file}$reset_color" + print "The $fg_bold[magenta]file$reset_color will be converted into extracted data: functions, script body." + print "Supported are Bash and Zsh script files." + print + print "$fg[green]Options:$reset_color" + print -- "$fg[magenta]-h/--help$reset_color Usage information" + print -- "$fg[magenta]-v/--verbose$reset_color More verbose operation-status output" + print -- "$fg[magenta]-q/--quiet$reset_color No status messages" + print -- "$fg[magenta]-n/--noansi$reset_color No colors in terminal output" +} diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-trim-indent b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-trim-indent new file mode 100644 index 0000000..dae93df --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-trim-indent @@ -0,0 +1,35 @@ +# vim:ft=zsh +# This file is double-licensed under GPLv3 and MIT (see LICENSE file) + +local blob="$1" +local -a lines new_lines match mbegin mend +lines=( "${(@f)blob}" ) + +# Measure every line +local line spaces +integer indent=-1 +for line in "${lines[@]}"; do + if [[ "$line" = (#b)([[:space:]]#)* && "$line" != [[:space:]]# ]]; then + spaces="${match[1]}" + if [[ "${#spaces}" -lt "$indent" || "$indent" = "-1" ]]; then + indent="${#spaces}" + fi + elif [[ "$line" = [[:space:]]# ]]; then + : + else + print -u 2 "Bug in Zsh, pattern didn't match" + fi +done + +integer top_spaces=1 +for line in "${lines[@]}"; do + if [[ "$line" != [[:space:]]# ]]; then + top_spaces=0 + fi + if (( top_spaces == 0 )); then + line[1,indent]="" + new_lines+=( "$line" ) + fi +done + +REPLY="${(F)new_lines}" diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd.main b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd.main new file mode 100644 index 0000000..a32a62e --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd.main @@ -0,0 +1,170 @@ +# -*- Mode: sh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim:ft=zsh:sw=2:sts=2:et +# This file is double-licensed under GPLv3 and MIT (see LICENSE file) + +### Options ### + +local OPT_HELP OPT_VERBOSE OPT_QUIET OPT_RQUIET OPT_NOANSI OPT_CIGNORE \ + OPT_FPATH OPT_SYNOPSIS OPT_BLOCKA OPT_BLOCKB OPT_SCOMM OPT_BASH +local -A opthash +zparseopts -E -D -A opthash h -help v -verbose q -quiet r -really-quiet \ + n -noansi -cignore: f: -fpath: -synopsis: -blocka: -blockb: \ + -scomm -bash || \ + { print "Improper options given, see help (-h/--help)"; return 1; } + +(( ${+opthash[-h]} + ${+opthash[--help]} )) && OPT_HELP=-h +(( ${+opthash[-v]} + ${+opthash[--verbose]} )) && OPT_VERBOSE=-v +(( ${+opthash[-q]} + ${+opthash[--quiet]} )) && OPT_QUIET=-q +(( ${+opthash[-r]} + ${+opthash[--really-quiet]} )) && { OPT_QUIET=-q; OPT_RQUIET=-r; } +(( ${+opthash[-n]} + ${+opthash[--noansi]} )) && OPT_NOANSI=-n +(( ${+opthash[--cignore]} )) && OPT_CIGNORE=${opthash[--cignore]} +(( ${+opthash[-f]} )) && OPT_FPATH=${opthash[-f]} +(( ${+opthash[--fpath]} )) && OPT_FPATH=${opthash[--fpath]} +(( ${+opthash[--synopsis]} )) && OPT_SYNOPSIS=${opthash[--synopsis]} +(( ${+opthash[--blocka]} )) && OPT_BLOCKA=${opthash[--blocka]} +(( ${+opthash[--blockb]} )) && OPT_BLOCKB=${opthash[--blockb]} +(( ${+opthash[--scomm]} )) && OPT_SCOMM=--scomm +(( ${+opthash[--bash]} )) && OPT_BASH=--bash + +[[ -n $OPT_NOANSI ]] && { colors=(); fg=(); bg=(); fg_bold=(); bg_bold=(); reset_color=""; } + +[[ -z $OPT_QUIET ]] && echo "Zsh control binary is: \`$zsh_control_bin'" + +### Code ### + +# Script with path to it, if it works +local ZERO=${(%):-%N} + +if [[ $ZERO != */* ]]; then + if [[ -x /usr/local/bin/zsd ]]; then + ZERO=/usr/local/bin/zsd + elif [[ -x /usr/bin/zsd ]]; then + ZERO=/usr/bin/zsd + elif [[ -x /opt/bin/zsd ]]; then + ZERO=/opt/bin/zsd + fi +fi + +local TRANSFORM=${ZERO:h}/zsd-transform DETECT=${ZERO:h}/zsd-detect TOADOC=${ZERO:h}/zsd-to-adoc + +if [[ -n $OPT_HELP ]]; then + usage + return 0 +fi + +if [[ $# -le 0 || $* = [[:space:]]## ]]; then + print "Argument needed, see help (-h/--help)" + return 1 +fi + +### FPATH ### + +if [[ -n $OPT_FPATH ]]; then + export FPATH=$OPT_FPATH:$FPATH +fi + +### First pass ### + +[[ -n $OPT_VERBOSE ]] && { + print + print "$fg[cyan]= zsd starts first-pass processing (function and script extraction)$reset_color" + local -a storage + storage=( zsdoc/data/functions/**/*(N) ) + print "$fg[cyan]Number of functions already generated: ${#storage}" + storage=( zsdoc/data/functions/*(N) ) + print "$fg[cyan]Number of scripts already processed: ${#storage}" +} + +### TRANSFORM ### + +local fname +integer ret=0 count=0 + +for fname; do + (( count ++ )) + + [[ -z $fname ]] && { + [[ -z $OPT_QUIET || -n $OPT_VERBOSE ]] && print "$fg[magenta]Skipping empty file name$reset_color" + continue + } + + if [[ ! -f $fname ]]; then + [[ -z $OPT_QUIET || -n $OPT_VERBOSE ]] && print "$fg[magenta]File \`$fname' doesn't exist, skipping it (see help, with -h/--help option)$reset_color" + ret=1 + continue + fi + + if [[ ! -r $fname ]]; then + [[ -z $OPT_QUIET || -n $OPT_VERBOSE ]] && print "$fg[magenta]File \`$fname' is unreadable, skipping it (see help, with -h/--help option)$reset_color" + ret=1 + continue + fi + + /usr/bin/env $zsh_control_bin $TRANSFORM $OPT_VERBOSE $OPT_QUIET $OPT_RQUIET $OPT_NOANSI $fname +done + +### Second pass ### + +[[ -n $OPT_VERBOSE ]] && { + print + print "$fg[cyan]= zsd starts second-pass processing (extraction of call-trees and comments)$reset_color" + local -a storage + storage=( zsdoc/data/functions/**/*(N) ) + print "$fg[cyan]Total number of functions generated: ${#storage}" + storage=( zsdoc/data/functions/*(N) ) + print "$fg[cyan]Total number of scripts processed: ${#storage}" +} + +### Detect ### + +for fname; do + [[ -z $fname ]] && continue + + if [[ ! -f $fname ]]; then + ret=1 + continue + fi + + if [[ ! -r $fname ]]; then + ret=1 + continue + fi + + local cignore="" ciarg="" + [[ -n $OPT_CIGNORE ]] && { cignore=--cignore ciarg=$OPT_CIGNORE; } + + /usr/bin/env $zsh_control_bin $DETECT $OPT_VERBOSE $OPT_QUIET $OPT_RQUIET $OPT_NOANSI $cignore $ciarg $fname +done + +### ASCIIDOC ### + +[[ -n $OPT_VERBOSE ]] && { + print + print "$fg[cyan]= zsd starts third-pass processing (generation of asciidoc documents)$reset_color" +} + +for fname; do + [[ -z $fname ]] && continue + + if [[ ! -f $fname ]]; then + ret=1 + continue + fi + + if [[ ! -r $fname ]]; then + ret=1 + continue + fi + + local synopsis="" syarg="" + [[ -n $OPT_SYNOPSIS ]] && { synopsis=--synopsis syarg=$OPT_SYNOPSIS; } + local blocka="" baarg="" blockb="" bbarg="" + [[ -n $OPT_BLOCKA ]] && { blocka=--blocka; baarg=$OPT_BLOCKA; } + [[ -n $OPT_BLOCKB ]] && { blockb=--blockb; bbarg=$OPT_BLOCKB; } + + /usr/bin/env $zsh_control_bin $TOADOC $OPT_BASH $OPT_VERBOSE $OPT_QUIET $OPT_RQUIET $OPT_NOANSI $synopsis $syarg \ + $blocka $baarg $blockb $bbarg $OPT_SCOMM $fname +done + + +return $ret diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd.preamble b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd.preamble new file mode 100644 index 0000000..b3ba8f8 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd.preamble @@ -0,0 +1,90 @@ +#!/bin/sh +# -*- Mode: sh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim:ft=zsh:sw=2:sts=2:et +# This file is double-licensed under GPLv3 and MIT (see LICENSE file) + +# +# /bin/sh stage, load configuration to obtain $zsh_control_bin +# + +ZERO="$0" +ZSD_DIR="${ZERO%/*}" +[ "$ZSD_DIR" = "${ZSD_DIR#/}" ] && ZSD_DIR="$PWD/$ZSD_DIR" + +[ "x$ZSD_CONFIG" = "x" ] && { + if [ -f ${XDG_CONFIG_HOME:-$HOME/.config}/zshelldoc/zsd.config ]; then + ZSD_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/zshelldoc/zsd.config" + elif [ -f "${ZSD_DIR}/zsd.config" ]; then + ZSD_CONFIG="${ZSD_DIR}/zsd.config" + elif [ -f /usr/local/share/zshelldoc/zsd.config ]; then + ZSD_CONFIG="/usr/local/share/zshelldoc/zsd.config" + elif [ -f /usr/share/zshelldoc/zsd.config ]; then + ZSD_CONFIG="/usr/share/zshelldoc/zsd.config" + elif [ -f /opt/share/zshelldoc/zsd.config ]; then + ZSD_CONFIG="/opt/share/zshelldoc/zsd.config" + fi + + [ "x$ZSD_CONFIG" != "x" ] && { + [ "$1" != -q ] && [ "$2" != -q ] && [ "$3" != -q ] && \ + [ "$1" != -r ] && [ "$2" != -r ] && [ "$3" != -r ] && \ + echo "Reading configuration from: $ZSD_CONFIG" + export ZSD_CONFIG + [ -f "$ZSD_CONFIG" ] && . "$ZSD_CONFIG" + } +} || { + [ -f "$ZSD_CONFIG" ] && . "$ZSD_CONFIG" +} + +[ -z "$zsh_control_bin" ] && zsh_control_bin="zsh" + +if [ -z "$ZSH_VERSION" ]; then + args="\"$0\"" + for arg; do + args="$args \"$arg\"" + done + exec /usr/bin/env "$zsh_control_bin" -f -c "source $args" +fi + +local -A colors fg bg fg_bold bg_bold +autoload colors +colors + +setopt extendedglob typesetsilent + +### Functions ### + +usage() { + print "$fg[green]Usage:$reset_color zsd [-h/--help] [-v/--verbose] [-q/--quiet] [-n/--noansi] [--cignore <pattern>] $fg_bold[magenta]{file1} [file2] ...$reset_color" + print "The files will be processed and their documentation will be generated" + print "in subdirectory \`zsdoc' (with meta-data in subdirectory \`data')." + print "Supported are Bash and Zsh scripts." + print + print "$fg[green]Options:$reset_color" + print -- "$fg[magenta]-h/--help$reset_color Usage information" + print -- "$fg[magenta]-v/--verbose$reset_color More verbose operation-status output" + print -- "$fg[magenta]-q/--quiet$reset_color No status messages" + print -- "$fg[magenta]-n/--noansi$reset_color No colors in terminal output" + print -- "$fg[magenta]--cignore$reset_color Specify which comment lines should be ignored" + print -- "$fg[magenta]-f/--fpath$reset_color Paths separated by : pointing to directories with functions" + print -r -- "$fg[magenta]--synopsis$reset_color Text to be used in SYNOPSIS section. Line break \"... +\n\", paragraph \"...\n\n\"" + print -r -- "$fg[magenta]--blocka$reset_color String used as block-begin, default: {{{" + print -r -- "$fg[magenta]--blockb$reset_color String used as block-end, default: }}}" + print -r -- "$fg[magenta]--scomm$reset_color Strip comment char \"#\" from function comments" + print -r -- "$fg[magenta]--bash$reset_color Output slightly tailored to Bash specifics (instead of Zsh specifics)" + print + print -- "Example --cignore options:" + print -- "--cignore '\\#*FUNCTION:*{{{*' - ignore comments like: $fg[green]# FUNCTION: usage {{{$reset_color" + print -- "--cignore '(\\#*FUNCTION:*{{{*|\\#*FUN:*{{{*)' - also ignore comments like: $fg[green]# FUN: usage {{{$reset_color" + print + print -- "File is parsed for synopsis block, which can be e.g.:" + print -- "# synopsis {{{my synopsis, can be multi-line}}}" + print + print -- "Other block that is parsed is commenting on environment variables. There can be multiple such blocks," + print -- "their content will be merged. Single block consists of multiple $fg[green]'VAR_NAME -> var-description'$reset_color lines" + print -- 'and results in a table in the output AsciiDoc document. An example block:' + print -- "# env-vars {{{" + print -- "# PATH -> paths to executables" + print -- "# MANPATH -> paths to manuals }}}" + print + print -- "Change the default brace block-delimeters with --blocka, --blockb. Block body should be AsciiDoc." +} diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/Makefile b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/Makefile new file mode 100644 index 0000000..cd08eb5 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/Makefile @@ -0,0 +1,62 @@ +test: nested real-zsyh cross-calls + +.PHONY: nested +nested: ../build/zsd ../build/zsd-transform ../build/zsd-detect ../build/zsd-to-adoc nested.zsh + @echo "=== Testing: nested.zsh ===" + rm -rf zsdoc + ../build/zsd -q nested.zsh + @zsh -c 'exit 0' || { echo "Zsh unavailable, cannot test if scripts compile (nested.zsh)"; exit 1; } + @zsh -c 'zcompile "zsdoc/data/bodies/nested.zsh" 2>/dev/null || echo "Error compiling script body"' + @zsh -c 'zcompile "zsdoc/data/functions/nested.zsh/dummy" 2>/dev/null || echo "Error compiling function: dummy"' + @zsh -c 'zcompile "zsdoc/data/functions/nested.zsh/test1" 2>/dev/null || echo "Error compiling function: test1"' + @zsh -c 'zcompile "zsdoc/data/functions/nested.zsh/test2" 2>/dev/null || echo "Error compiling function: test2"' + @zsh -c 'zcompile "zsdoc/data/functions/nested.zsh/test3" 2>/dev/null || echo "Error compiling function: test3"' + @zsh -c 'zcompile "zsdoc/data/functions/nested.zsh/test4" 2>/dev/null || echo "Error compiling function: test4"' + @zsh -c 'zcompile "zsdoc/data/functions/nested.zsh/test5" 2>/dev/null || echo "Error compiling function: test5"' + @zsh -c 'zcompile "zsdoc/data/functions/nested.zsh/test6" 2>/dev/null || echo "Error compiling function: test6"' + @find zsdoc/data/functions zsdoc/data/bodies -iname '*.zwc' -exec rm -f \{\} \; + @mkdir -p nested/data/hooks/nested.zsh + diff -r zsdoc nested + @echo + +.PHONY: real-zsyh +real-zsyh: ../build/zsd ../build/zsd-transform ../build/zsd-detect ../build/zsd-to-adoc real-zsyh.zsh + @echo "=== Testing: real-zsyh.zsh ===" + rm -rf zsdoc + ../build/zsd -q real-zsyh.zsh + @zsh -c 'exit 0' || { echo "Zsh unavailable, cannot test if scripts compile (real-zsyh.zsh)"; exit 1; } + @zsh -c 'zcompile "zsdoc/data/bodies/real-zsyh.zsh" 2>/dev/null || echo "Error compiling script body"' + @zsh -c 'zcompile "zsdoc/data/functions/real-zsyh.zsh/_zsh_highlight" 2>/dev/null || echo "Error compiling function: _zsh_highlight"' + @zsh -c 'zcompile "zsdoc/data/functions/real-zsyh.zsh/_zsh_highlight_add_highlight" 2>/dev/null || echo "Error compiling function: _zsh_highlight_add_highlight"' + @zsh -c 'zcompile "zsdoc/data/functions/real-zsyh.zsh/_zsh_highlight_apply_zle_highlight" 2>/dev/null || echo "Error compiling function: _zsh_highlight_apply_zle_highlight"' + @zsh -c 'zcompile "zsdoc/data/functions/real-zsyh.zsh/_zsh_highlight_bind_widgets" 2>/dev/null || echo "Error compiling function: _zsh_highlight_bind_widgets"' + @zsh -c 'zcompile "zsdoc/data/functions/real-zsyh.zsh/_zsh_highlight_buffer_modified" 2>/dev/null || echo "Error compiling function: _zsh_highlight_buffer_modified"' + @zsh -c 'zcompile "zsdoc/data/functions/real-zsyh.zsh/_zsh_highlight_call_widget" 2>/dev/null || echo "Error compiling function: _zsh_highlight_call_widget"' + @zsh -c 'zcompile "zsdoc/data/functions/real-zsyh.zsh/_zsh_highlight_cursor_moved" 2>/dev/null || echo "Error compiling function: _zsh_highlight_cursor_moved"' + @zsh -c 'zcompile "zsdoc/data/functions/real-zsyh.zsh/_zsh_highlight_load_highlighters" 2>/dev/null || echo "Error compiling function: _zsh_highlight_load_highlighters"' + @zsh -c 'zcompile "zsdoc/data/functions/real-zsyh.zsh/_zsh_highlight_preexec_hook" 2>/dev/null || echo "Error compiling function: _zsh_highlight_preexec_hook"' + @find zsdoc/data/functions zsdoc/data/bodies -iname '*.zwc' -exec rm -f \{\} \; + @mkdir -p real-zsyh/data/hooks/real-zsyh.zsh + diff -r zsdoc real-zsyh + @echo + +.PHONY: cross-calls +cross-calls: ../build/zsd ../build/zsd-transform ../build/zsd-detect ../build/zsd-to-adoc cross-calls1.zsh cross-calls2.zsh + @echo "=== Testing: cross-calls1.zsh, cross-calls2.zsh ===" + rm -rf zsdoc + ../build/zsd -q cross-calls1.zsh cross-calls2.zsh + @zsh -c 'exit 0' || { echo "Zsh unavailable, cannot test if scripts compile (cross-calls*.zsh)"; exit 1; } + @zsh -c 'zcompile "zsdoc/data/bodies/cross-calls1.zsh" 2>/dev/null || echo "Error compiling 1st script body"' + @zsh -c 'zcompile "zsdoc/data/bodies/cross-calls2.zsh" 2>/dev/null || echo "Error compiling 2nd script body"' + @zsh -c 'zcompile "zsdoc/data/functions/cross-calls1.zsh/myfun1" 2>/dev/null || echo "Error compiling function: cross-calls1.zsh/myfun1"' + @zsh -c 'zcompile "zsdoc/data/functions/cross-calls1.zsh/myfun2" 2>/dev/null || echo "Error compiling function: cross-calls1.zsh/myfun2"' + @zsh -c 'zcompile "zsdoc/data/functions/cross-calls2.zsh/myfun1" 2>/dev/null || echo "Error compiling function: cross-calls2.zsh/myfun1"' + @zsh -c 'zcompile "zsdoc/data/functions/cross-calls2.zsh/myfun3" 2>/dev/null || echo "Error compiling function: cross-calls2.zsh/myfun3"' + @find zsdoc/data/functions zsdoc/data/bodies -iname '*.zwc' -exec rm -f \{\} \; + @mkdir -p cross-calls/data/hooks/cross-calls1.zsh cross-calls/data/hooks/cross-calls2.zsh + diff -r zsdoc cross-calls + @echo + +.PHONY: clean +clean: + rm -rf zsdoc diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/cross-calls1.zsh.adoc b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/cross-calls1.zsh.adoc new file mode 100644 index 0000000..9052456 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/cross-calls1.zsh.adoc @@ -0,0 +1,48 @@ +cross-calls1.zsh(1) +=================== +:compat-mode!: + +NAME +---- +cross-calls1.zsh - a shell script + +SYNOPSIS +-------- +Documentation automatically generated with `zshelldoc' + +FUNCTIONS +--------- + + myfun1 + myfun2 + +DETAILS +------- + +Script Body +~~~~~~~~~~~ + +Has 4 line(s). Calls functions: + + Script-Body + |-- cross-calls2.zsh/myfun3 + `-- myfun1 + +myfun1 +~~~~~~ + +Has 1 line(s). Doesn't call other functions. + +Called by: + + Script-Body + +myfun2 +~~~~~~ + +Has 1 line(s). Doesn't call other functions. + +Called by: + + cross-calls2.zsh/Script-Body + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/cross-calls2.zsh.adoc b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/cross-calls2.zsh.adoc new file mode 100644 index 0000000..e68b4d3 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/cross-calls2.zsh.adoc @@ -0,0 +1,48 @@ +cross-calls2.zsh(1) +=================== +:compat-mode!: + +NAME +---- +cross-calls2.zsh - a shell script + +SYNOPSIS +-------- +Documentation automatically generated with `zshelldoc' + +FUNCTIONS +--------- + + myfun1 + myfun3 + +DETAILS +------- + +Script Body +~~~~~~~~~~~ + +Has 4 line(s). Calls functions: + + Script-Body + |-- cross-calls1.zsh/myfun2 + `-- myfun1 + +myfun1 +~~~~~~ + +Has 1 line(s). Doesn't call other functions. + +Called by: + + Script-Body + +myfun3 +~~~~~~ + +Has 1 line(s). Doesn't call other functions. + +Called by: + + cross-calls1.zsh/Script-Body + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/bodies/cross-calls1.zsh b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/bodies/cross-calls1.zsh new file mode 100644 index 0000000..07981b3 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/bodies/cross-calls1.zsh @@ -0,0 +1,4 @@ + + +myfun1 +myfun3 diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/bodies/cross-calls1.zsh.comments b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/bodies/cross-calls1.zsh.comments new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/bodies/cross-calls1.zsh.comments @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/bodies/cross-calls2.zsh b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/bodies/cross-calls2.zsh new file mode 100644 index 0000000..c30e57e --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/bodies/cross-calls2.zsh @@ -0,0 +1,4 @@ + + +myfun1 +myfun2 diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/bodies/cross-calls2.zsh.comments b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/bodies/cross-calls2.zsh.comments new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/bodies/cross-calls2.zsh.comments @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/call_tree.zsd b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/call_tree.zsd new file mode 100644 index 0000000..3623b62 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/call_tree.zsd @@ -0,0 +1 @@ +cross-calls2.zsh/zsd_script_body: cross-calls2.zsh/myfun1 cross-calls1.zsh/myfun2 diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/extended/cross-calls1.zsh b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/extended/cross-calls1.zsh new file mode 100644 index 0000000..7f02f4d --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/extended/cross-calls1.zsh @@ -0,0 +1,10 @@ +myfun1() { + echo "Cross-calls test" +} + +function myfun2() { + echo "Cross-calls test" +} + +myfun1 +myfun3 diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/extended/cross-calls2.zsh b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/extended/cross-calls2.zsh new file mode 100644 index 0000000..afcc07d --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/extended/cross-calls2.zsh @@ -0,0 +1,10 @@ +myfun1() { + echo "Cross-calls test" +} + +function myfun3() { + echo "Cross-calls test" +} + +myfun1 +myfun2 diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/functions/cross-calls1.zsh/myfun1 b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/functions/cross-calls1.zsh/myfun1 new file mode 100644 index 0000000..0056759 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/functions/cross-calls1.zsh/myfun1 @@ -0,0 +1 @@ +echo "Cross-calls test" diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/functions/cross-calls1.zsh/myfun2 b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/functions/cross-calls1.zsh/myfun2 new file mode 100644 index 0000000..0056759 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/functions/cross-calls1.zsh/myfun2 @@ -0,0 +1 @@ +echo "Cross-calls test" diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/functions/cross-calls2.zsh/myfun1 b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/functions/cross-calls2.zsh/myfun1 new file mode 100644 index 0000000..0056759 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/functions/cross-calls2.zsh/myfun1 @@ -0,0 +1 @@ +echo "Cross-calls test" diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/functions/cross-calls2.zsh/myfun3 b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/functions/cross-calls2.zsh/myfun3 new file mode 100644 index 0000000..0056759 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/functions/cross-calls2.zsh/myfun3 @@ -0,0 +1 @@ +echo "Cross-calls test" diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/rev_call_tree.zsd b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/rev_call_tree.zsd new file mode 100644 index 0000000..4c0a0cd --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/rev_call_tree.zsd @@ -0,0 +1,2 @@ +cross-calls1.zsh/myfun2: cross-calls2.zsh/zsd_script_body +cross-calls2.zsh/myfun1: cross-calls2.zsh/zsd_script_body diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/trees/cross-calls1.zsh/Script_Body_.tree b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/trees/cross-calls1.zsh/Script_Body_.tree new file mode 100644 index 0000000..4c80a84 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/trees/cross-calls1.zsh/Script_Body_.tree @@ -0,0 +1,5 @@ +Script_Body_ +|-- cross-calls2.zsh/myfun3 +`-- myfun1 + +2 directories, 0 files diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/trees/cross-calls2.zsh/Script_Body_.tree b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/trees/cross-calls2.zsh/Script_Body_.tree new file mode 100644 index 0000000..1fce9be --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/trees/cross-calls2.zsh/Script_Body_.tree @@ -0,0 +1,5 @@ +Script_Body_ +|-- cross-calls1.zsh/myfun2 +`-- myfun1 + +2 directories, 0 files diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls1.zsh b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls1.zsh new file mode 100644 index 0000000..7f02f4d --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls1.zsh @@ -0,0 +1,10 @@ +myfun1() { + echo "Cross-calls test" +} + +function myfun2() { + echo "Cross-calls test" +} + +myfun1 +myfun3 diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls2.zsh b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls2.zsh new file mode 100644 index 0000000..afcc07d --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls2.zsh @@ -0,0 +1,10 @@ +myfun1() { + echo "Cross-calls test" +} + +function myfun3() { + echo "Cross-calls test" +} + +myfun1 +myfun2 diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested.zsh b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested.zsh new file mode 100644 index 0000000..90233fc --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested.zsh @@ -0,0 +1,37 @@ +# Dummy function +dummy() { : } +# Test1 function +test1() { echo "Hello"; test2; () { test6; a() { : } } } + +# Test2 function +test2() { + echo "Hello" + test3 +} + +# Test3 function +function test3 { echo "Hello"; test4; } +# Test4 function +function test4() { echo "Hello"; test5; } + +# Test5 function +function test5 { + echo "Hello" + test6 + function test5sub() { + echo Hello2 + } + test5sub2() { + echo Hello2 + } +} +# Test6 function +function test6() { + echo "Hello" + dummy +} + +() { + anonSub() { echo Hello2; } + () { echo Hello2; } +} diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/bodies/nested.zsh b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/bodies/nested.zsh new file mode 100644 index 0000000..1666f45 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/bodies/nested.zsh @@ -0,0 +1,6 @@ + + +() { + anonSub() { echo Hello2; } + () { echo Hello2; } +} diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/bodies/nested.zsh.comments b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/bodies/nested.zsh.comments new file mode 100644 index 0000000..de31609 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/bodies/nested.zsh.comments @@ -0,0 +1,10 @@ +# Dummy function +# Test1 function + +# Test2 function + +# Test3 function +# Test4 function + +# Test5 function +# Test6 function diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/call_tree.zsd b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/call_tree.zsd new file mode 100644 index 0000000..1b9f21e --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/call_tree.zsd @@ -0,0 +1,6 @@ +nested.zsh/test1: nested.zsh/test2 nested.zsh/test6 +nested.zsh/test2: nested.zsh/test3 +nested.zsh/test3: nested.zsh/test4 +nested.zsh/test4: nested.zsh/test5 +nested.zsh/test5: nested.zsh/test6 +nested.zsh/test6: nested.zsh/dummy diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/dummy b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/dummy new file mode 100644 index 0000000..956a6d6 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/dummy @@ -0,0 +1 @@ +# Dummy function diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test1 b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test1 new file mode 100644 index 0000000..303f623 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test1 @@ -0,0 +1 @@ +# Test1 function diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test2 b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test2 new file mode 100644 index 0000000..ffb1937 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test2 @@ -0,0 +1 @@ +# Test2 function diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test3 b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test3 new file mode 100644 index 0000000..e094af5 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test3 @@ -0,0 +1 @@ +# Test3 function diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test4 b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test4 new file mode 100644 index 0000000..8e3f839 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test4 @@ -0,0 +1 @@ +# Test4 function diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test5 b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test5 new file mode 100644 index 0000000..cfc9be9 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test5 @@ -0,0 +1 @@ +# Test5 function diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test6 b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test6 new file mode 100644 index 0000000..3c5e657 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test6 @@ -0,0 +1 @@ +# Test6 function diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/extended/nested.zsh b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/extended/nested.zsh new file mode 100644 index 0000000..90233fc --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/extended/nested.zsh @@ -0,0 +1,37 @@ +# Dummy function +dummy() { : } +# Test1 function +test1() { echo "Hello"; test2; () { test6; a() { : } } } + +# Test2 function +test2() { + echo "Hello" + test3 +} + +# Test3 function +function test3 { echo "Hello"; test4; } +# Test4 function +function test4() { echo "Hello"; test5; } + +# Test5 function +function test5 { + echo "Hello" + test6 + function test5sub() { + echo Hello2 + } + test5sub2() { + echo Hello2 + } +} +# Test6 function +function test6() { + echo "Hello" + dummy +} + +() { + anonSub() { echo Hello2; } + () { echo Hello2; } +} diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/dummy b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/dummy new file mode 100644 index 0000000..397db75 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/dummy @@ -0,0 +1 @@ +: diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test1 b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test1 new file mode 100644 index 0000000..e776031 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test1 @@ -0,0 +1 @@ +echo "Hello"; test2; () { test6; a() { : } } diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test2 b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test2 new file mode 100644 index 0000000..710371d --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test2 @@ -0,0 +1,2 @@ +echo "Hello" +test3 diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test3 b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test3 new file mode 100644 index 0000000..21e6b7d --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test3 @@ -0,0 +1 @@ +echo "Hello"; test4; diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test4 b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test4 new file mode 100644 index 0000000..8a29b82 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test4 @@ -0,0 +1 @@ +echo "Hello"; test5; diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test5 b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test5 new file mode 100644 index 0000000..73c6b05 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test5 @@ -0,0 +1,8 @@ +echo "Hello" +test6 +function test5sub() { + echo Hello2 +} +test5sub2() { + echo Hello2 +} diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test6 b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test6 new file mode 100644 index 0000000..4e31e8e --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test6 @@ -0,0 +1,2 @@ +echo "Hello" +dummy diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/rev_call_tree.zsd b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/rev_call_tree.zsd new file mode 100644 index 0000000..99d24e9 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/rev_call_tree.zsd @@ -0,0 +1,6 @@ +nested.zsh/dummy: nested.zsh/test6 +nested.zsh/test2: nested.zsh/test1 +nested.zsh/test3: nested.zsh/test2 +nested.zsh/test4: nested.zsh/test3 +nested.zsh/test5: nested.zsh/test4 +nested.zsh/test6: nested.zsh/test1 nested.zsh/test5 diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test1.tree b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test1.tree new file mode 100644 index 0000000..d259156 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test1.tree @@ -0,0 +1,11 @@ +test1 +|-- test2 +|   `-- test3 +|   `-- test4 +|   `-- test5 +|   `-- test6 +|   `-- dummy +`-- test6 + `-- dummy + +8 directories, 0 files diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test2.tree b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test2.tree new file mode 100644 index 0000000..252caa8 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test2.tree @@ -0,0 +1,8 @@ +test2 +`-- test3 + `-- test4 + `-- test5 + `-- test6 + `-- dummy + +5 directories, 0 files diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test3.tree b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test3.tree new file mode 100644 index 0000000..e5c0a94 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test3.tree @@ -0,0 +1,7 @@ +test3 +`-- test4 + `-- test5 + `-- test6 + `-- dummy + +4 directories, 0 files diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test4.tree b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test4.tree new file mode 100644 index 0000000..c29e76b --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test4.tree @@ -0,0 +1,6 @@ +test4 +`-- test5 + `-- test6 + `-- dummy + +3 directories, 0 files diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test5.tree b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test5.tree new file mode 100644 index 0000000..e9aa9e3 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test5.tree @@ -0,0 +1,5 @@ +test5 +`-- test6 + `-- dummy + +2 directories, 0 files diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test6.tree b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test6.tree new file mode 100644 index 0000000..b541343 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test6.tree @@ -0,0 +1,4 @@ +test6 +`-- dummy + +1 directory, 0 files diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/nested.zsh.adoc b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/nested.zsh.adoc new file mode 100644 index 0000000..bd74f5e --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/nested.zsh.adoc @@ -0,0 +1,156 @@ +nested.zsh(1) +============= +:compat-mode!: + +NAME +---- +nested.zsh - a shell script + +SYNOPSIS +-------- +Documentation automatically generated with `zshelldoc' + +FUNCTIONS +--------- + + dummy + test1 + test2 + test3 + test4 + test5 + test6 + +DETAILS +------- + +Script Body +~~~~~~~~~~~ + +Has 6 line(s). No functions are called (may set up e.g. a hook, a Zle widget bound to a key, etc.). + +dummy +~~~~~ + +____ + # Dummy function +____ + +Has 1 line(s). Doesn't call other functions. + +Called by: + + test6 + +test1 +~~~~~ + +____ + # Test1 function +____ + +Has 1 line(s). Calls functions: + + test1 + |-- test2 + |   `-- test3 + |   `-- test4 + |   `-- test5 + |   `-- test6 + |   `-- dummy + `-- test6 + `-- dummy + +Not called by script or any function (may be e.g. a hook, a Zle widget, etc.). + +test2 +~~~~~ + +____ + # Test2 function +____ + +Has 2 line(s). Calls functions: + + test2 + `-- test3 + `-- test4 + `-- test5 + `-- test6 + `-- dummy + +Called by: + + test1 + +test3 +~~~~~ + +____ + # Test3 function +____ + +Has 1 line(s). Calls functions: + + test3 + `-- test4 + `-- test5 + `-- test6 + `-- dummy + +Called by: + + test2 + +test4 +~~~~~ + +____ + # Test4 function +____ + +Has 1 line(s). Calls functions: + + test4 + `-- test5 + `-- test6 + `-- dummy + +Called by: + + test3 + +test5 +~~~~~ + +____ + # Test5 function +____ + +Has 8 line(s). Calls functions: + + test5 + `-- test6 + `-- dummy + +Called by: + + test4 + +test6 +~~~~~ + +____ + # Test6 function +____ + +Has 2 line(s). Calls functions: + + test6 + `-- dummy + +Called by: + + test1 + test5 + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh.zsh b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh.zsh new file mode 100644 index 0000000..60f9113 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh.zsh @@ -0,0 +1,419 @@ +# ------------------------------------------------------------------------------------------------- +# Copyright (c) 2010-2016 zsh-syntax-highlighting contributors +# All rights reserved. +# +# The only licensing for this file follows. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted +# provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this list of conditions +# and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, this list of +# conditions and the following disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the zsh-syntax-highlighting contributors nor the names of its contributors +# may be used to endorse or promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ------------------------------------------------------------------------------------------------- +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et +# ------------------------------------------------------------------------------------------------- + +# First of all, ensure predictable parsing. +zsh_highlight__aliases=`builtin alias -Lm '[^+]*'` +# In zsh <= 5.2, `alias -L` emits aliases that begin with a plus sign ('alias -- +foo=42') +# them without a '--' guard, so they don't round trip. +# +# Hence, we exclude them from unaliasing: +builtin unalias -m '[^+]*' + +# Set $0 to the expected value, regardless of functionargzero. +0=${(%):-%N} +if true; then + # $0 is reliable + typeset -g ZSH_HIGHLIGHT_VERSION=$(<"${0:A:h}"/.version) + typeset -g ZSH_HIGHLIGHT_REVISION=$(<"${0:A:h}"/.revision-hash) + if [[ $ZSH_HIGHLIGHT_REVISION == \$Format:* ]]; then + # When running from a source tree without 'make install', $ZSH_HIGHLIGHT_REVISION + # would be set to '$Format:%H$' literally. That's an invalid value, and obtaining + # the valid value (via `git rev-parse HEAD`, as Makefile does) might be costly, so: + ZSH_HIGHLIGHT_REVISION=HEAD + fi +fi + +# ------------------------------------------------------------------------------------------------- +# Core highlighting update system +# ------------------------------------------------------------------------------------------------- + +# Use workaround for bug in ZSH? +# zsh-users/zsh@48cadf4 http://www.zsh.org/mla/workers//2017/msg00034.html +autoload -U is-at-least +if is-at-least 5.3.2; then + zsh_highlight__pat_static_bug=false +else + zsh_highlight__pat_static_bug=true +fi + +# Array declaring active highlighters names. +typeset -ga ZSH_HIGHLIGHT_HIGHLIGHTERS + +# Update ZLE buffer syntax highlighting. +# +# Invokes each highlighter that needs updating. +# This function is supposed to be called whenever the ZLE state changes. +_zsh_highlight() +{ + # Store the previous command return code to restore it whatever happens. + local ret=$? + + # Remove all highlighting in isearch, so that only the underlining done by zsh itself remains. + # For details see FAQ entry 'Why does syntax highlighting not work while searching history?'. + # This disables highlighting during isearch (for reasons explained in README.md) unless zsh is new enough + # and doesn't have the 5.3.1 bug + if [[ $WIDGET == zle-isearch-update ]] && { $zsh_highlight__pat_static_bug || ! (( $+ISEARCHMATCH_ACTIVE )) }; then + region_highlight=() + return $ret + fi + + setopt localoptions warncreateglobal + setopt localoptions noksharrays + local REPLY # don't leak $REPLY into global scope + + # Do not highlight if there are more than 300 chars in the buffer. It's most + # likely a pasted command or a huge list of files in that case.. + [[ -n ${ZSH_HIGHLIGHT_MAXLENGTH:-} ]] && [[ $#BUFFER -gt $ZSH_HIGHLIGHT_MAXLENGTH ]] && return $ret + + # Do not highlight if there are pending inputs (copy/paste). + [[ $PENDING -gt 0 ]] && return $ret + + # Reset region highlight to build it from scratch + typeset -ga region_highlight + region_highlight=(); + + { + local cache_place + local -a region_highlight_copy + + # Select which highlighters in ZSH_HIGHLIGHT_HIGHLIGHTERS need to be invoked. + local highlighter; for highlighter in $ZSH_HIGHLIGHT_HIGHLIGHTERS; do + + # eval cache place for current highlighter and prepare it + cache_place="_zsh_highlight__highlighter_${highlighter}_cache" + typeset -ga ${cache_place} + + # If highlighter needs to be invoked + if ! type "_zsh_highlight_highlighter_${highlighter}_predicate" >&/dev/null; then + echo "zsh-syntax-highlighting: warning: disabling the ${(qq)highlighter} highlighter as it has not been loaded" >&2 + # TODO: use ${(b)} rather than ${(q)} if supported + ZSH_HIGHLIGHT_HIGHLIGHTERS=( ${ZSH_HIGHLIGHT_HIGHLIGHTERS:#${highlighter}} ) + elif "_zsh_highlight_highlighter_${highlighter}_predicate"; then + + # save a copy, and cleanup region_highlight + region_highlight_copy=("${region_highlight[@]}") + region_highlight=() + + # Execute highlighter and save result + { + "_zsh_highlight_highlighter_${highlighter}_paint" + } always { + eval "${cache_place}=(\"\${region_highlight[@]}\")" + } + + # Restore saved region_highlight + region_highlight=("${region_highlight_copy[@]}") + + fi + + # Use value form cache if any cached + eval "region_highlight+=(\"\${${cache_place}[@]}\")" + + done + + # Re-apply zle_highlight settings + + # region + if (( REGION_ACTIVE == 1 )); then + _zsh_highlight_apply_zle_highlight region standout "$MARK" "$CURSOR" + elif (( REGION_ACTIVE == 2 )); then + () { + local needle=$'\n' + integer min max + if (( MARK > CURSOR )) ; then + min=$CURSOR max=$MARK + else + min=$MARK max=$CURSOR + fi + (( min = ${${BUFFER[1,$min]}[(I)$needle]} )) + (( max += ${${BUFFER:($max-1)}[(i)$needle]} - 1 )) + _zsh_highlight_apply_zle_highlight region standout "$min" "$max" + } + fi + + # yank / paste (zsh-5.1.1 and newer) + (( $+YANK_ACTIVE )) && (( YANK_ACTIVE )) && _zsh_highlight_apply_zle_highlight paste standout "$YANK_START" "$YANK_END" + + # isearch + (( $+ISEARCHMATCH_ACTIVE )) && (( ISEARCHMATCH_ACTIVE )) && _zsh_highlight_apply_zle_highlight isearch underline "$ISEARCHMATCH_START" "$ISEARCHMATCH_END" + + # suffix + (( $+SUFFIX_ACTIVE )) && (( SUFFIX_ACTIVE )) && _zsh_highlight_apply_zle_highlight suffix bold "$SUFFIX_START" "$SUFFIX_END" + + + return $ret + + + } always { + typeset -g _ZSH_HIGHLIGHT_PRIOR_BUFFER="$BUFFER" + typeset -gi _ZSH_HIGHLIGHT_PRIOR_CURSOR=$CURSOR + } +} + +# Apply highlighting based on entries in the zle_highlight array. +# This function takes four arguments: +# 1. The exact entry (no patterns) in the zle_highlight array: +# region, paste, isearch, or suffix +# 2. The default highlighting that should be applied if the entry is unset +# 3. and 4. Two integer values describing the beginning and end of the +# range. The order does not matter. +_zsh_highlight_apply_zle_highlight() { + local entry="$1" default="$2" + integer first="$3" second="$4" + + # read the relevant entry from zle_highlight + local region="${zle_highlight[(r)${entry}:*]}" + + if [[ -z "$region" ]]; then + # entry not specified at all, use default value + region=$default + else + # strip prefix + region="${region#${entry}:}" + + # no highlighting when set to the empty string or to 'none' + if [[ -z "$region" ]] || [[ "$region" == none ]]; then + return + fi + fi + + integer start end + if (( first < second )); then + start=$first end=$second + else + start=$second end=$first + fi + region_highlight+=("$start $end $region") +} + + +# ------------------------------------------------------------------------------------------------- +# API/utility functions for highlighters +# ------------------------------------------------------------------------------------------------- + +# Array used by highlighters to declare user overridable styles. +typeset -gA ZSH_HIGHLIGHT_STYLES + +# Whether the command line buffer has been modified or not. +# +# Returns 0 if the buffer has changed since _zsh_highlight was last called. +_zsh_highlight_buffer_modified() +{ + [[ "${_ZSH_HIGHLIGHT_PRIOR_BUFFER:-}" != "$BUFFER" ]] +} + +# Whether the cursor has moved or not. +# +# Returns 0 if the cursor has moved since _zsh_highlight was last called. +_zsh_highlight_cursor_moved() +{ + [[ -n $CURSOR ]] && [[ -n ${_ZSH_HIGHLIGHT_PRIOR_CURSOR-} ]] && (($_ZSH_HIGHLIGHT_PRIOR_CURSOR != $CURSOR)) +} + +# Add a highlight defined by ZSH_HIGHLIGHT_STYLES. +# +# Should be used by all highlighters aside from 'pattern' (cf. ZSH_HIGHLIGHT_PATTERN). +# Overwritten in tests/test-highlighting.zsh when testing. +_zsh_highlight_add_highlight() +{ + local -i start end + local highlight + start=$1 + end=$2 + shift 2 + for highlight; do + if (( $+ZSH_HIGHLIGHT_STYLES[$highlight] )); then + region_highlight+=("$start $end $ZSH_HIGHLIGHT_STYLES[$highlight]") + break + fi + done +} + +# ------------------------------------------------------------------------------------------------- +# Setup functions +# ------------------------------------------------------------------------------------------------- + +# Helper for _zsh_highlight_bind_widgets +# $1 is name of widget to call +_zsh_highlight_call_widget() +{ + builtin zle "$@" && + _zsh_highlight +} + +# Rebind all ZLE widgets to make them invoke _zsh_highlights. +_zsh_highlight_bind_widgets() +{ + setopt localoptions noksharrays + typeset -F SECONDS + local prefix=orig-s$SECONDS-r$RANDOM # unique each time, in case we're sourced more than once + + # Load ZSH module zsh/zleparameter, needed to override user defined widgets. + zmodload zsh/zleparameter 2>/dev/null || { + print -r -- >&2 'zsh-syntax-highlighting: failed loading zsh/zleparameter.' + return 1 + } + + # Override ZLE widgets to make them invoke _zsh_highlight. + local -U widgets_to_bind + widgets_to_bind=(${${(k)widgets}:#(.*|run-help|which-command|beep|set-local-history|yank)}) + + # Always wrap special zle-line-finish widget. This is needed to decide if the + # current line ends and special highlighting logic needs to be applied. + # E.g. remove cursor imprint, don't highlight partial paths, ... + widgets_to_bind+=(zle-line-finish) + + # Always wrap special zle-isearch-update widget to be notified of updates in isearch. + # This is needed because we need to disable highlighting in that case. + widgets_to_bind+=(zle-isearch-update) + + local cur_widget + for cur_widget in $widgets_to_bind; do + case $widgets[$cur_widget] in + + # Already rebound event: do nothing. + user:_zsh_highlight_widget_*);; + + # The "eval"'s are required to make $cur_widget a closure: the value of the parameter at function + # definition time is used. + # + # We can't use ${0/_zsh_highlight_widget_} because these widgets are always invoked with + # NO_function_argzero, regardless of the option's setting here. + + # User defined widget: override and rebind old one with prefix "orig-". + user:*) zle -N $prefix-$cur_widget ${widgets[$cur_widget]#*:} + eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget ${(q)prefix}-${(q)cur_widget} -- \"\$@\" }" + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; + + # Completion widget: override and rebind old one with prefix "orig-". + completion:*) zle -C $prefix-$cur_widget ${${(s.:.)widgets[$cur_widget]}[2,3]} + eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget ${(q)prefix}-${(q)cur_widget} -- \"\$@\" }" + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; + + # Builtin widget: override and make it call the builtin ".widget". + builtin) eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget .${(q)cur_widget} -- \"\$@\" }" + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; + + # Incomplete or nonexistent widget: Bind to z-sy-h directly. + *) + if [[ $cur_widget == zle-* ]] && [[ -z $widgets[$cur_widget] ]]; then + _zsh_highlight_widget_${cur_widget}() { :; _zsh_highlight } + zle -N $cur_widget _zsh_highlight_widget_$cur_widget + else + # Default: unhandled case. + print -r -- >&2 "zsh-syntax-highlighting: unhandled ZLE widget ${(qq)cur_widget}" + print -r -- >&2 "zsh-syntax-highlighting: (This is sometimes caused by doing \`bindkey <keys> ${(q-)cur_widget}\` without creating the ${(qq)cur_widget} widget with \`zle -N\` or \`zle -C\`.)" + fi + esac + done +} + +# Load highlighters from directory. +# +# Arguments: +# 1) Path to the highlighters directory. +_zsh_highlight_load_highlighters() +{ + setopt localoptions noksharrays + + # Check the directory exists. + [[ -d "$1" ]] || { + print -r -- >&2 "zsh-syntax-highlighting: highlighters directory ${(qq)1} not found." + return 1 + } + + # Load highlighters from highlighters directory and check they define required functions. + local highlighter highlighter_dir + for highlighter_dir ($1/*/); do + highlighter="${highlighter_dir:t}" + [[ -f "$highlighter_dir${highlighter}-highlighter.zsh" ]] && + . "$highlighter_dir${highlighter}-highlighter.zsh" + if type "_zsh_highlight_highlighter_${highlighter}_paint" &> /dev/null && + type "_zsh_highlight_highlighter_${highlighter}_predicate" &> /dev/null; + then + # New (0.5.0) function names + elif type "_zsh_highlight_${highlighter}_highlighter" &> /dev/null && + type "_zsh_highlight_${highlighter}_highlighter_predicate" &> /dev/null; + then + # Old (0.4.x) function names + if false; then + # TODO: only show this warning for plugin authors/maintainers, not for end users + print -r -- >&2 "zsh-syntax-highlighting: warning: ${(qq)highlighter} highlighter uses deprecated entry point names; please ask its maintainer to update it: https://github.com/zsh-users/zsh-syntax-highlighting/issues/329" + fi + # Make it work. + eval "_zsh_highlight_highlighter_${(q)highlighter}_paint() { _zsh_highlight_${(q)highlighter}_highlighter \"\$@\" }" + eval "_zsh_highlight_highlighter_${(q)highlighter}_predicate() { _zsh_highlight_${(q)highlighter}_highlighter_predicate \"\$@\" }" + else + print -r -- >&2 "zsh-syntax-highlighting: ${(qq)highlighter} highlighter should define both required functions '_zsh_highlight_highlighter_${highlighter}_paint' and '_zsh_highlight_highlighter_${highlighter}_predicate' in ${(qq):-"$highlighter_dir${highlighter}-highlighter.zsh"}." + fi + done +} + + +# ------------------------------------------------------------------------------------------------- +# Setup +# ------------------------------------------------------------------------------------------------- + +# Try binding widgets. +_zsh_highlight_bind_widgets || { + print -r -- >&2 'zsh-syntax-highlighting: failed binding ZLE widgets, exiting.' + return 1 +} + +# Resolve highlighters directory location. +_zsh_highlight_load_highlighters "${ZSH_HIGHLIGHT_HIGHLIGHTERS_DIR:-${${0:A}:h}/highlighters}" || { + print -r -- >&2 'zsh-syntax-highlighting: failed loading highlighters, exiting.' + return 1 +} + +# Reset scratch variables when commandline is done. +_zsh_highlight_preexec_hook() +{ + typeset -g _ZSH_HIGHLIGHT_PRIOR_BUFFER= + typeset -gi _ZSH_HIGHLIGHT_PRIOR_CURSOR= +} +autoload -U add-zsh-hook +add-zsh-hook preexec _zsh_highlight_preexec_hook 2>/dev/null || { + print -r -- >&2 'zsh-syntax-highlighting: failed loading add-zsh-hook.' + } + +# Load zsh/parameter module if available +zmodload zsh/parameter 2>/dev/null || true + +# Initialize the array of active highlighters if needed. +[[ $#ZSH_HIGHLIGHT_HIGHLIGHTERS -eq 0 ]] && ZSH_HIGHLIGHT_HIGHLIGHTERS=(main) + +# Restore the aliases we unned +eval "$zsh_highlight__aliases" +builtin unset zsh_highlight__aliases + +# Set $?. +true diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/autoload/real-zsyh.zsh/add-zsh-hook b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/autoload/real-zsyh.zsh/add-zsh-hook new file mode 100644 index 0000000..3bc952e --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/autoload/real-zsyh.zsh/add-zsh-hook @@ -0,0 +1,93 @@ +# Add to HOOK the given FUNCTION. +# HOOK is one of chpwd, precmd, preexec, periodic, zshaddhistory, +# zshexit, zsh_directory_name (the _functions subscript is not required). +# +# With -d, remove the function from the hook instead; delete the hook +# variable if it is empty. +# +# -D behaves like -d, but pattern characters are active in the +# function name, so any matching function will be deleted from the hook. +# +# Without -d, the FUNCTION is marked for autoload; -U is passed down to +# autoload if that is given, as are -z and -k. (This is harmless if the +# function is actually defined inline.) + +emulate -L zsh + +local -a hooktypes +hooktypes=( + chpwd precmd preexec periodic zshaddhistory zshexit + zsh_directory_name +) +local usage="Usage: add-zsh-hook hook function\nValid hooks are:\n $hooktypes" + +local opt +local -a autoopts +integer del list help + +while getopts "dDhLUzk" opt; do + case $opt in + (d) + del=1 + ;; + + (D) + del=2 + ;; + + (h) + help=1 + ;; + + (L) + list=1 + ;; + + ([Uzk]) + autoopts+=(-$opt) + ;; + + (*) + return 1 + ;; + esac +done +shift $(( OPTIND - 1 )) + +if (( list )); then + typeset -mp "(${1:-${(@j:|:)hooktypes}})_functions" + return $? +elif (( help || $# != 2 || ${hooktypes[(I)$1]} == 0 )); then + print -u$(( 2 - help )) $usage + return $(( 1 - help )) +fi + +local hook="${1}_functions" +local fn="$2" + +if (( del )); then + # delete, if hook is set + if (( ${(P)+hook} )); then + if (( del == 2 )); then + set -A $hook ${(P)hook:#${~fn}} + else + set -A $hook ${(P)hook:#$fn} + fi + # unset if no remaining entries --- this can give better + # performance in some cases + if (( ! ${(P)#hook} )); then + unset $hook + fi + fi +else + if (( ${(P)+hook} )); then + if (( ${${(P)hook}[(I)$fn]} == 0 )); then + typeset -ga $hook + set -A $hook ${(P)hook} $fn + fi + else + typeset -ga $hook + set -A $hook $fn + fi + autoload $autoopts -- $fn +fi diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/autoload/real-zsyh.zsh/is-at-least b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/autoload/real-zsyh.zsh/is-at-least new file mode 100644 index 0000000..d4ff355 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/autoload/real-zsyh.zsh/is-at-least @@ -0,0 +1,56 @@ +# +# Test whether $ZSH_VERSION (or some value of your choice, if a second argument +# is provided) is greater than or equal to x.y.z-r (in argument one). In fact, +# it'll accept any dot/dash-separated string of numbers as its second argument +# and compare it to the dot/dash-separated first argument. Leading non-number +# parts of a segment (such as the "zefram" in 3.1.2-zefram4) are not considered +# when the comparison is done; only the numbers matter. Any left-out segments +# in the first argument that are present in the version string compared are +# considered as zeroes, eg 3 == 3.0 == 3.0.0 == 3.0.0.0 and so on. +# +# Usage examples: +# is-at-least 3.1.6-15 && setopt NO_GLOBAL_RCS +# is-at-least 3.1.0 && setopt HIST_REDUCE_BLANKS +# is-at-least 586 $MACHTYPE && echo 'You could be running Mandrake!' +# is-at-least $ZSH_VERSION || print 'Something fishy here.' +# +# Note that segments that contain no digits at all are ignored, and leading +# text is discarded if trailing digits follow, because this was the meaning +# of certain zsh version strings in the early 2000s. Other segments that +# begin with digits are compared using NUMERIC_GLOB_SORT semantics, and any +# other segments starting with text are compared lexically. + +emulate -L zsh + +local IFS=".-" min_cnt=0 ver_cnt=0 part min_ver version order + +min_ver=(${=1}) +version=(${=2:-$ZSH_VERSION} 0) + +while (( $min_cnt <= ${#min_ver} )); do + while [[ "$part" != <-> ]]; do + (( ++ver_cnt > ${#version} )) && return 0 + if [[ ${version[ver_cnt]} = *[0-9][^0-9]* ]]; then + # Contains a number followed by text. Not a zsh version string. + order=( ${version[ver_cnt]} ${min_ver[ver_cnt]} ) + if [[ ${version[ver_cnt]} = <->* ]]; then + # Leading digits, compare by sorting with numeric order. + [[ $order != ${${(On)order}} ]] && return 1 + else + # No leading digits, compare by sorting in lexical order. + [[ $order != ${${(O)order}} ]] && return 1 + fi + [[ $order[1] != $order[2] ]] && return 0 + fi + part=${version[ver_cnt]##*[^0-9]} + done + + while true; do + (( ++min_cnt > ${#min_ver} )) && return 0 + [[ ${min_ver[min_cnt]} = <-> ]] && break + done + + (( part > min_ver[min_cnt] )) && return 0 + (( part < min_ver[min_cnt] )) && return 1 + part='' +done diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/bodies/real-zsyh.zsh b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/bodies/real-zsyh.zsh new file mode 100644 index 0000000..061a872 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/bodies/real-zsyh.zsh @@ -0,0 +1,36 @@ + +zsh_highlight__aliases=`builtin alias -Lm '[^+]*'` +builtin unalias -m '[^+]*' +0=${(%):-%N} +if true; then + typeset -g ZSH_HIGHLIGHT_VERSION=$(<"${0:A:h}"/.version) + typeset -g ZSH_HIGHLIGHT_REVISION=$(<"${0:A:h}"/.revision-hash) + if [[ $ZSH_HIGHLIGHT_REVISION == \$Format:* ]]; then + ZSH_HIGHLIGHT_REVISION=HEAD + fi +fi +autoload -U is-at-least +if is-at-least 5.3.2; then + zsh_highlight__pat_static_bug=false +else + zsh_highlight__pat_static_bug=true +fi +typeset -ga ZSH_HIGHLIGHT_HIGHLIGHTERS +typeset -gA ZSH_HIGHLIGHT_STYLES +_zsh_highlight_bind_widgets || { + print -r -- >&2 'zsh-syntax-highlighting: failed binding ZLE widgets, exiting.' + return 1 +} +_zsh_highlight_load_highlighters "${ZSH_HIGHLIGHT_HIGHLIGHTERS_DIR:-${${0:A}:h}/highlighters}" || { + print -r -- >&2 'zsh-syntax-highlighting: failed loading highlighters, exiting.' + return 1 +} +autoload -U add-zsh-hook +add-zsh-hook preexec _zsh_highlight_preexec_hook 2>/dev/null || { + print -r -- >&2 'zsh-syntax-highlighting: failed loading add-zsh-hook.' + } +zmodload zsh/parameter 2>/dev/null || true +[[ $#ZSH_HIGHLIGHT_HIGHLIGHTERS -eq 0 ]] && ZSH_HIGHLIGHT_HIGHLIGHTERS=(main) +eval "$zsh_highlight__aliases" +builtin unset zsh_highlight__aliases +true diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/bodies/real-zsyh.zsh.comments b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/bodies/real-zsyh.zsh.comments new file mode 100644 index 0000000..11627a6 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/bodies/real-zsyh.zsh.comments @@ -0,0 +1,117 @@ +# ------------------------------------------------------------------------------------------------- +# Copyright (c) 2010-2016 zsh-syntax-highlighting contributors +# All rights reserved. +# +# The only licensing for this file follows. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted +# provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this list of conditions +# and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, this list of +# conditions and the following disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the zsh-syntax-highlighting contributors nor the names of its contributors +# may be used to endorse or promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ------------------------------------------------------------------------------------------------- +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et +# ------------------------------------------------------------------------------------------------- + +# First of all, ensure predictable parsing. +# In zsh <= 5.2, `alias -L` emits aliases that begin with a plus sign ('alias -- +foo=42') +# them without a '--' guard, so they don't round trip. +# +# Hence, we exclude them from unaliasing: + +# Set $0 to the expected value, regardless of functionargzero. + # $0 is reliable + # When running from a source tree without 'make install', $ZSH_HIGHLIGHT_REVISION + # would be set to '$Format:%H$' literally. That's an invalid value, and obtaining + # the valid value (via `git rev-parse HEAD`, as Makefile does) might be costly, so: + +# ------------------------------------------------------------------------------------------------- +# Core highlighting update system +# ------------------------------------------------------------------------------------------------- + +# Use workaround for bug in ZSH? +# zsh-users/zsh@48cadf4 http://www.zsh.org/mla/workers//2017/msg00034.html + +# Array declaring active highlighters names. + +# Update ZLE buffer syntax highlighting. +# +# Invokes each highlighter that needs updating. +# This function is supposed to be called whenever the ZLE state changes. + +# Apply highlighting based on entries in the zle_highlight array. +# This function takes four arguments: +# 1. The exact entry (no patterns) in the zle_highlight array: +# region, paste, isearch, or suffix +# 2. The default highlighting that should be applied if the entry is unset +# 3. and 4. Two integer values describing the beginning and end of the +# range. The order does not matter. + + +# ------------------------------------------------------------------------------------------------- +# API/utility functions for highlighters +# ------------------------------------------------------------------------------------------------- + +# Array used by highlighters to declare user overridable styles. + +# Whether the command line buffer has been modified or not. +# +# Returns 0 if the buffer has changed since _zsh_highlight was last called. + +# Whether the cursor has moved or not. +# +# Returns 0 if the cursor has moved since _zsh_highlight was last called. + +# Add a highlight defined by ZSH_HIGHLIGHT_STYLES. +# +# Should be used by all highlighters aside from 'pattern' (cf. ZSH_HIGHLIGHT_PATTERN). +# Overwritten in tests/test-highlighting.zsh when testing. + +# ------------------------------------------------------------------------------------------------- +# Setup functions +# ------------------------------------------------------------------------------------------------- + +# Helper for _zsh_highlight_bind_widgets +# $1 is name of widget to call + +# Rebind all ZLE widgets to make them invoke _zsh_highlights. + +# Load highlighters from directory. +# +# Arguments: +# 1) Path to the highlighters directory. + + +# ------------------------------------------------------------------------------------------------- +# Setup +# ------------------------------------------------------------------------------------------------- + +# Try binding widgets. + +# Resolve highlighters directory location. + +# Reset scratch variables when commandline is done. + +# Load zsh/parameter module if available + +# Initialize the array of active highlighters if needed. + +# Restore the aliases we unned + +# Set $?. diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/call_tree.zsd b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/call_tree.zsd new file mode 100644 index 0000000..129dd8f --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/call_tree.zsd @@ -0,0 +1,3 @@ +real-zsyh.zsh/_zsh_highlight: real-zsyh.zsh/_zsh_highlight_apply_zle_highlight +real-zsyh.zsh/_zsh_highlight_call_widget: real-zsyh.zsh/_zsh_highlight +real-zsyh.zsh/zsd_script_body: real-zsyh.zsh/is-at-least real-zsyh.zsh/_zsh_highlight_bind_widgets real-zsyh.zsh/_zsh_highlight_load_highlighters real-zsyh.zsh/add-zsh-hook diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight new file mode 100644 index 0000000..d81dcec --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight @@ -0,0 +1,4 @@ +# Update ZLE buffer syntax highlighting. +# +# Invokes each highlighter that needs updating. +# This function is supposed to be called whenever the ZLE state changes. diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_add_highlight b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_add_highlight new file mode 100644 index 0000000..e32b923 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_add_highlight @@ -0,0 +1,4 @@ +# Add a highlight defined by ZSH_HIGHLIGHT_STYLES. +# +# Should be used by all highlighters aside from 'pattern' (cf. ZSH_HIGHLIGHT_PATTERN). +# Overwritten in tests/test-highlighting.zsh when testing. diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_apply_zle_highlight b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_apply_zle_highlight new file mode 100644 index 0000000..29260bb --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_apply_zle_highlight @@ -0,0 +1,7 @@ +# Apply highlighting based on entries in the zle_highlight array. +# This function takes four arguments: +# 1. The exact entry (no patterns) in the zle_highlight array: +# region, paste, isearch, or suffix +# 2. The default highlighting that should be applied if the entry is unset +# 3. and 4. Two integer values describing the beginning and end of the +# range. The order does not matter. diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_bind_widgets b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_bind_widgets new file mode 100644 index 0000000..38cebfe --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_bind_widgets @@ -0,0 +1 @@ +# Rebind all ZLE widgets to make them invoke _zsh_highlights. diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_buffer_modified b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_buffer_modified new file mode 100644 index 0000000..56ce25f --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_buffer_modified @@ -0,0 +1,3 @@ +# Whether the command line buffer has been modified or not. +# +# Returns 0 if the buffer has changed since _zsh_highlight was last called. diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_call_widget b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_call_widget new file mode 100644 index 0000000..bb838db --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_call_widget @@ -0,0 +1,2 @@ +# Helper for _zsh_highlight_bind_widgets +# $1 is name of widget to call diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_cursor_moved b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_cursor_moved new file mode 100644 index 0000000..feec535 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_cursor_moved @@ -0,0 +1,3 @@ +# Whether the cursor has moved or not. +# +# Returns 0 if the cursor has moved since _zsh_highlight was last called. diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_load_highlighters b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_load_highlighters new file mode 100644 index 0000000..d41abe1 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_load_highlighters @@ -0,0 +1,4 @@ +# Load highlighters from directory. +# +# Arguments: +# 1) Path to the highlighters directory. diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_preexec_hook b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_preexec_hook new file mode 100644 index 0000000..a02a30b --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_preexec_hook @@ -0,0 +1 @@ +# Reset scratch variables when commandline is done. diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/extended/real-zsyh.zsh b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/extended/real-zsyh.zsh new file mode 100644 index 0000000..f79aab7 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/extended/real-zsyh.zsh @@ -0,0 +1,574 @@ +# ------------------------------------------------------------------------------------------------- +# Copyright (c) 2010-2016 zsh-syntax-highlighting contributors +# All rights reserved. +# +# The only licensing for this file follows. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted +# provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this list of conditions +# and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, this list of +# conditions and the following disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the zsh-syntax-highlighting contributors nor the names of its contributors +# may be used to endorse or promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ------------------------------------------------------------------------------------------------- +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et +# ------------------------------------------------------------------------------------------------- + +# First of all, ensure predictable parsing. +zsh_highlight__aliases=`builtin alias -Lm '[^+]*'` +# In zsh <= 5.2, `alias -L` emits aliases that begin with a plus sign ('alias -- +foo=42') +# them without a '--' guard, so they don't round trip. +# +# Hence, we exclude them from unaliasing: +builtin unalias -m '[^+]*' + +# Set $0 to the expected value, regardless of functionargzero. +0=${(%):-%N} +if true; then + # $0 is reliable + typeset -g ZSH_HIGHLIGHT_VERSION=$(<"${0:A:h}"/.version) + typeset -g ZSH_HIGHLIGHT_REVISION=$(<"${0:A:h}"/.revision-hash) + if [[ $ZSH_HIGHLIGHT_REVISION == \$Format:* ]]; then + # When running from a source tree without 'make install', $ZSH_HIGHLIGHT_REVISION + # would be set to '$Format:%H$' literally. That's an invalid value, and obtaining + # the valid value (via `git rev-parse HEAD`, as Makefile does) might be costly, so: + ZSH_HIGHLIGHT_REVISION=HEAD + fi +fi + +# ------------------------------------------------------------------------------------------------- +# Core highlighting update system +# ------------------------------------------------------------------------------------------------- + +# Use workaround for bug in ZSH? +# zsh-users/zsh@48cadf4 http://www.zsh.org/mla/workers//2017/msg00034.html +autoload -U is-at-least +if is-at-least 5.3.2; then + zsh_highlight__pat_static_bug=false +else + zsh_highlight__pat_static_bug=true +fi + +# Array declaring active highlighters names. +typeset -ga ZSH_HIGHLIGHT_HIGHLIGHTERS + +# Update ZLE buffer syntax highlighting. +# +# Invokes each highlighter that needs updating. +# This function is supposed to be called whenever the ZLE state changes. +_zsh_highlight() +{ + # Store the previous command return code to restore it whatever happens. + local ret=$? + + # Remove all highlighting in isearch, so that only the underlining done by zsh itself remains. + # For details see FAQ entry 'Why does syntax highlighting not work while searching history?'. + # This disables highlighting during isearch (for reasons explained in README.md) unless zsh is new enough + # and doesn't have the 5.3.1 bug + if [[ $WIDGET == zle-isearch-update ]] && { $zsh_highlight__pat_static_bug || ! (( $+ISEARCHMATCH_ACTIVE )) }; then + region_highlight=() + return $ret + fi + + setopt localoptions warncreateglobal + setopt localoptions noksharrays + local REPLY # don't leak $REPLY into global scope + + # Do not highlight if there are more than 300 chars in the buffer. It's most + # likely a pasted command or a huge list of files in that case.. + [[ -n ${ZSH_HIGHLIGHT_MAXLENGTH:-} ]] && [[ $#BUFFER -gt $ZSH_HIGHLIGHT_MAXLENGTH ]] && return $ret + + # Do not highlight if there are pending inputs (copy/paste). + [[ $PENDING -gt 0 ]] && return $ret + + # Reset region highlight to build it from scratch + typeset -ga region_highlight + region_highlight=(); + + { + local cache_place + local -a region_highlight_copy + + # Select which highlighters in ZSH_HIGHLIGHT_HIGHLIGHTERS need to be invoked. + local highlighter; for highlighter in $ZSH_HIGHLIGHT_HIGHLIGHTERS; do + + # eval cache place for current highlighter and prepare it + cache_place="_zsh_highlight__highlighter_${highlighter}_cache" + typeset -ga ${cache_place} + + # If highlighter needs to be invoked + if ! type "_zsh_highlight_highlighter_${highlighter}_predicate" >&/dev/null; then + echo "zsh-syntax-highlighting: warning: disabling the ${(qq)highlighter} highlighter as it has not been loaded" >&2 + # TODO: use ${(b)} rather than ${(q)} if supported + ZSH_HIGHLIGHT_HIGHLIGHTERS=( ${ZSH_HIGHLIGHT_HIGHLIGHTERS:#${highlighter}} ) + elif "_zsh_highlight_highlighter_${highlighter}_predicate"; then + + # save a copy, and cleanup region_highlight + region_highlight_copy=("${region_highlight[@]}") + region_highlight=() + + # Execute highlighter and save result + { + "_zsh_highlight_highlighter_${highlighter}_paint" + } always { + eval "${cache_place}=(\"\${region_highlight[@]}\")" + } + + # Restore saved region_highlight + region_highlight=("${region_highlight_copy[@]}") + + fi + + # Use value form cache if any cached + eval "region_highlight+=(\"\${${cache_place}[@]}\")" + + done + + # Re-apply zle_highlight settings + + # region + if (( REGION_ACTIVE == 1 )); then + _zsh_highlight_apply_zle_highlight region standout "$MARK" "$CURSOR" + elif (( REGION_ACTIVE == 2 )); then + () { + local needle=$'\n' + integer min max + if (( MARK > CURSOR )) ; then + min=$CURSOR max=$MARK + else + min=$MARK max=$CURSOR + fi + (( min = ${${BUFFER[1,$min]}[(I)$needle]} )) + (( max += ${${BUFFER:($max-1)}[(i)$needle]} - 1 )) + _zsh_highlight_apply_zle_highlight region standout "$min" "$max" + } + fi + + # yank / paste (zsh-5.1.1 and newer) + (( $+YANK_ACTIVE )) && (( YANK_ACTIVE )) && _zsh_highlight_apply_zle_highlight paste standout "$YANK_START" "$YANK_END" + + # isearch + (( $+ISEARCHMATCH_ACTIVE )) && (( ISEARCHMATCH_ACTIVE )) && _zsh_highlight_apply_zle_highlight isearch underline "$ISEARCHMATCH_START" "$ISEARCHMATCH_END" + + # suffix + (( $+SUFFIX_ACTIVE )) && (( SUFFIX_ACTIVE )) && _zsh_highlight_apply_zle_highlight suffix bold "$SUFFIX_START" "$SUFFIX_END" + + + return $ret + + + } always { + typeset -g _ZSH_HIGHLIGHT_PRIOR_BUFFER="$BUFFER" + typeset -gi _ZSH_HIGHLIGHT_PRIOR_CURSOR=$CURSOR + } +} + +# Apply highlighting based on entries in the zle_highlight array. +# This function takes four arguments: +# 1. The exact entry (no patterns) in the zle_highlight array: +# region, paste, isearch, or suffix +# 2. The default highlighting that should be applied if the entry is unset +# 3. and 4. Two integer values describing the beginning and end of the +# range. The order does not matter. +_zsh_highlight_apply_zle_highlight() { + local entry="$1" default="$2" + integer first="$3" second="$4" + + # read the relevant entry from zle_highlight + local region="${zle_highlight[(r)${entry}:*]}" + + if [[ -z "$region" ]]; then + # entry not specified at all, use default value + region=$default + else + # strip prefix + region="${region#${entry}:}" + + # no highlighting when set to the empty string or to 'none' + if [[ -z "$region" ]] || [[ "$region" == none ]]; then + return + fi + fi + + integer start end + if (( first < second )); then + start=$first end=$second + else + start=$second end=$first + fi + region_highlight+=("$start $end $region") +} + + +# ------------------------------------------------------------------------------------------------- +# API/utility functions for highlighters +# ------------------------------------------------------------------------------------------------- + +# Array used by highlighters to declare user overridable styles. +typeset -gA ZSH_HIGHLIGHT_STYLES + +# Whether the command line buffer has been modified or not. +# +# Returns 0 if the buffer has changed since _zsh_highlight was last called. +_zsh_highlight_buffer_modified() +{ + [[ "${_ZSH_HIGHLIGHT_PRIOR_BUFFER:-}" != "$BUFFER" ]] +} + +# Whether the cursor has moved or not. +# +# Returns 0 if the cursor has moved since _zsh_highlight was last called. +_zsh_highlight_cursor_moved() +{ + [[ -n $CURSOR ]] && [[ -n ${_ZSH_HIGHLIGHT_PRIOR_CURSOR-} ]] && (($_ZSH_HIGHLIGHT_PRIOR_CURSOR != $CURSOR)) +} + +# Add a highlight defined by ZSH_HIGHLIGHT_STYLES. +# +# Should be used by all highlighters aside from 'pattern' (cf. ZSH_HIGHLIGHT_PATTERN). +# Overwritten in tests/test-highlighting.zsh when testing. +_zsh_highlight_add_highlight() +{ + local -i start end + local highlight + start=$1 + end=$2 + shift 2 + for highlight; do + if (( $+ZSH_HIGHLIGHT_STYLES[$highlight] )); then + region_highlight+=("$start $end $ZSH_HIGHLIGHT_STYLES[$highlight]") + break + fi + done +} + +# ------------------------------------------------------------------------------------------------- +# Setup functions +# ------------------------------------------------------------------------------------------------- + +# Helper for _zsh_highlight_bind_widgets +# $1 is name of widget to call +_zsh_highlight_call_widget() +{ + builtin zle "$@" && + _zsh_highlight +} + +# Rebind all ZLE widgets to make them invoke _zsh_highlights. +_zsh_highlight_bind_widgets() +{ + setopt localoptions noksharrays + typeset -F SECONDS + local prefix=orig-s$SECONDS-r$RANDOM # unique each time, in case we're sourced more than once + + # Load ZSH module zsh/zleparameter, needed to override user defined widgets. + zmodload zsh/zleparameter 2>/dev/null || { + print -r -- >&2 'zsh-syntax-highlighting: failed loading zsh/zleparameter.' + return 1 + } + + # Override ZLE widgets to make them invoke _zsh_highlight. + local -U widgets_to_bind + widgets_to_bind=(${${(k)widgets}:#(.*|run-help|which-command|beep|set-local-history|yank)}) + + # Always wrap special zle-line-finish widget. This is needed to decide if the + # current line ends and special highlighting logic needs to be applied. + # E.g. remove cursor imprint, don't highlight partial paths, ... + widgets_to_bind+=(zle-line-finish) + + # Always wrap special zle-isearch-update widget to be notified of updates in isearch. + # This is needed because we need to disable highlighting in that case. + widgets_to_bind+=(zle-isearch-update) + + local cur_widget + for cur_widget in $widgets_to_bind; do + case $widgets[$cur_widget] in + + # Already rebound event: do nothing. + user:_zsh_highlight_widget_*);; + + # The "eval"'s are required to make $cur_widget a closure: the value of the parameter at function + # definition time is used. + # + # We can't use ${0/_zsh_highlight_widget_} because these widgets are always invoked with + # NO_function_argzero, regardless of the option's setting here. + + # User defined widget: override and rebind old one with prefix "orig-". + user:*) zle -N $prefix-$cur_widget ${widgets[$cur_widget]#*:} + eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget ${(q)prefix}-${(q)cur_widget} -- \"\$@\" }" + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; + + # Completion widget: override and rebind old one with prefix "orig-". + completion:*) zle -C $prefix-$cur_widget ${${(s.:.)widgets[$cur_widget]}[2,3]} + eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget ${(q)prefix}-${(q)cur_widget} -- \"\$@\" }" + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; + + # Builtin widget: override and make it call the builtin ".widget". + builtin) eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget .${(q)cur_widget} -- \"\$@\" }" + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; + + # Incomplete or nonexistent widget: Bind to z-sy-h directly. + *) + if [[ $cur_widget == zle-* ]] && [[ -z $widgets[$cur_widget] ]]; then + _zsh_highlight_widget_${cur_widget}() { :; _zsh_highlight } + zle -N $cur_widget _zsh_highlight_widget_$cur_widget + else + # Default: unhandled case. + print -r -- >&2 "zsh-syntax-highlighting: unhandled ZLE widget ${(qq)cur_widget}" + print -r -- >&2 "zsh-syntax-highlighting: (This is sometimes caused by doing \`bindkey <keys> ${(q-)cur_widget}\` without creating the ${(qq)cur_widget} widget with \`zle -N\` or \`zle -C\`.)" + fi + esac + done +} + +# Load highlighters from directory. +# +# Arguments: +# 1) Path to the highlighters directory. +_zsh_highlight_load_highlighters() +{ + setopt localoptions noksharrays + + # Check the directory exists. + [[ -d "$1" ]] || { + print -r -- >&2 "zsh-syntax-highlighting: highlighters directory ${(qq)1} not found." + return 1 + } + + # Load highlighters from highlighters directory and check they define required functions. + local highlighter highlighter_dir + for highlighter_dir ($1/*/); do + highlighter="${highlighter_dir:t}" + [[ -f "$highlighter_dir${highlighter}-highlighter.zsh" ]] && + . "$highlighter_dir${highlighter}-highlighter.zsh" + if type "_zsh_highlight_highlighter_${highlighter}_paint" &> /dev/null && + type "_zsh_highlight_highlighter_${highlighter}_predicate" &> /dev/null; + then + # New (0.5.0) function names + elif type "_zsh_highlight_${highlighter}_highlighter" &> /dev/null && + type "_zsh_highlight_${highlighter}_highlighter_predicate" &> /dev/null; + then + # Old (0.4.x) function names + if false; then + # TODO: only show this warning for plugin authors/maintainers, not for end users + print -r -- >&2 "zsh-syntax-highlighting: warning: ${(qq)highlighter} highlighter uses deprecated entry point names; please ask its maintainer to update it: https://github.com/zsh-users/zsh-syntax-highlighting/issues/329" + fi + # Make it work. + eval "_zsh_highlight_highlighter_${(q)highlighter}_paint() { _zsh_highlight_${(q)highlighter}_highlighter \"\$@\" }" + eval "_zsh_highlight_highlighter_${(q)highlighter}_predicate() { _zsh_highlight_${(q)highlighter}_highlighter_predicate \"\$@\" }" + else + print -r -- >&2 "zsh-syntax-highlighting: ${(qq)highlighter} highlighter should define both required functions '_zsh_highlight_highlighter_${highlighter}_paint' and '_zsh_highlight_highlighter_${highlighter}_predicate' in ${(qq):-"$highlighter_dir${highlighter}-highlighter.zsh"}." + fi + done +} + + +# ------------------------------------------------------------------------------------------------- +# Setup +# ------------------------------------------------------------------------------------------------- + +# Try binding widgets. +_zsh_highlight_bind_widgets || { + print -r -- >&2 'zsh-syntax-highlighting: failed binding ZLE widgets, exiting.' + return 1 +} + +# Resolve highlighters directory location. +_zsh_highlight_load_highlighters "${ZSH_HIGHLIGHT_HIGHLIGHTERS_DIR:-${${0:A}:h}/highlighters}" || { + print -r -- >&2 'zsh-syntax-highlighting: failed loading highlighters, exiting.' + return 1 +} + +# Reset scratch variables when commandline is done. +_zsh_highlight_preexec_hook() +{ + typeset -g _ZSH_HIGHLIGHT_PRIOR_BUFFER= + typeset -gi _ZSH_HIGHLIGHT_PRIOR_CURSOR= +} +autoload -U add-zsh-hook +add-zsh-hook preexec _zsh_highlight_preexec_hook 2>/dev/null || { + print -r -- >&2 'zsh-syntax-highlighting: failed loading add-zsh-hook.' + } + +# Load zsh/parameter module if available +zmodload zsh/parameter 2>/dev/null || true + +# Initialize the array of active highlighters if needed. +[[ $#ZSH_HIGHLIGHT_HIGHLIGHTERS -eq 0 ]] && ZSH_HIGHLIGHT_HIGHLIGHTERS=(main) + +# Restore the aliases we unned +eval "$zsh_highlight__aliases" +builtin unset zsh_highlight__aliases + +# Set $?. +true + +add-zsh-hook() { +# Add to HOOK the given FUNCTION. +# HOOK is one of chpwd, precmd, preexec, periodic, zshaddhistory, +# zshexit, zsh_directory_name (the _functions subscript is not required). +# +# With -d, remove the function from the hook instead; delete the hook +# variable if it is empty. +# +# -D behaves like -d, but pattern characters are active in the +# function name, so any matching function will be deleted from the hook. +# +# Without -d, the FUNCTION is marked for autoload; -U is passed down to +# autoload if that is given, as are -z and -k. (This is harmless if the +# function is actually defined inline.) + +emulate -L zsh + +local -a hooktypes +hooktypes=( + chpwd precmd preexec periodic zshaddhistory zshexit + zsh_directory_name +) +local usage="Usage: add-zsh-hook hook function\nValid hooks are:\n $hooktypes" + +local opt +local -a autoopts +integer del list help + +while getopts "dDhLUzk" opt; do + case $opt in + (d) + del=1 + ;; + + (D) + del=2 + ;; + + (h) + help=1 + ;; + + (L) + list=1 + ;; + + ([Uzk]) + autoopts+=(-$opt) + ;; + + (*) + return 1 + ;; + esac +done +shift $(( OPTIND - 1 )) + +if (( list )); then + typeset -mp "(${1:-${(@j:|:)hooktypes}})_functions" + return $? +elif (( help || $# != 2 || ${hooktypes[(I)$1]} == 0 )); then + print -u$(( 2 - help )) $usage + return $(( 1 - help )) +fi + +local hook="${1}_functions" +local fn="$2" + +if (( del )); then + # delete, if hook is set + if (( ${(P)+hook} )); then + if (( del == 2 )); then + set -A $hook ${(P)hook:#${~fn}} + else + set -A $hook ${(P)hook:#$fn} + fi + # unset if no remaining entries --- this can give better + # performance in some cases + if (( ! ${(P)#hook} )); then + unset $hook + fi + fi +else + if (( ${(P)+hook} )); then + if (( ${${(P)hook}[(I)$fn]} == 0 )); then + typeset -ga $hook + set -A $hook ${(P)hook} $fn + fi + else + typeset -ga $hook + set -A $hook $fn + fi + autoload $autoopts -- $fn +fi +} + +is-at-least() { +# +# Test whether $ZSH_VERSION (or some value of your choice, if a second argument +# is provided) is greater than or equal to x.y.z-r (in argument one). In fact, +# it'll accept any dot/dash-separated string of numbers as its second argument +# and compare it to the dot/dash-separated first argument. Leading non-number +# parts of a segment (such as the "zefram" in 3.1.2-zefram4) are not considered +# when the comparison is done; only the numbers matter. Any left-out segments +# in the first argument that are present in the version string compared are +# considered as zeroes, eg 3 == 3.0 == 3.0.0 == 3.0.0.0 and so on. +# +# Usage examples: +# is-at-least 3.1.6-15 && setopt NO_GLOBAL_RCS +# is-at-least 3.1.0 && setopt HIST_REDUCE_BLANKS +# is-at-least 586 $MACHTYPE && echo 'You could be running Mandrake!' +# is-at-least $ZSH_VERSION || print 'Something fishy here.' +# +# Note that segments that contain no digits at all are ignored, and leading +# text is discarded if trailing digits follow, because this was the meaning +# of certain zsh version strings in the early 2000s. Other segments that +# begin with digits are compared using NUMERIC_GLOB_SORT semantics, and any +# other segments starting with text are compared lexically. + +emulate -L zsh + +local IFS=".-" min_cnt=0 ver_cnt=0 part min_ver version order + +min_ver=(${=1}) +version=(${=2:-$ZSH_VERSION} 0) + +while (( $min_cnt <= ${#min_ver} )); do + while [[ "$part" != <-> ]]; do + (( ++ver_cnt > ${#version} )) && return 0 + if [[ ${version[ver_cnt]} = *[0-9][^0-9]* ]]; then + # Contains a number followed by text. Not a zsh version string. + order=( ${version[ver_cnt]} ${min_ver[ver_cnt]} ) + if [[ ${version[ver_cnt]} = <->* ]]; then + # Leading digits, compare by sorting with numeric order. + [[ $order != ${${(On)order}} ]] && return 1 + else + # No leading digits, compare by sorting in lexical order. + [[ $order != ${${(O)order}} ]] && return 1 + fi + [[ $order[1] != $order[2] ]] && return 0 + fi + part=${version[ver_cnt]##*[^0-9]} + done + + while true; do + (( ++min_cnt > ${#min_ver} )) && return 0 + [[ ${min_ver[min_cnt]} = <-> ]] && break + done + + (( part > min_ver[min_cnt] )) && return 0 + (( part < min_ver[min_cnt] )) && return 1 + part='' +done +} diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/Script_Body_/autoload b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/Script_Body_/autoload new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/Script_Body_/autoload @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/Script_Body_/eval b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/Script_Body_/eval new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/Script_Body_/eval @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/Script_Body_/unalias b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/Script_Body_/unalias new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/Script_Body_/unalias @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/Script_Body_/zmodload b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/Script_Body_/zmodload new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/Script_Body_/zmodload @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight/eval b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight/eval new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight/eval @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight/type b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight/type new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight/type @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_bind_widgets/eval b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_bind_widgets/eval new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_bind_widgets/eval @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_bind_widgets/zle b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_bind_widgets/zle new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_bind_widgets/zle @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_bind_widgets/zmodload b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_bind_widgets/zmodload new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_bind_widgets/zmodload @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_call_widget/zle b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_call_widget/zle new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_call_widget/zle @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_load_highlighters/eval b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_load_highlighters/eval new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_load_highlighters/eval @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_load_highlighters/type b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_load_highlighters/type new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_load_highlighters/type @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/add-zsh-hook/autoload b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/add-zsh-hook/autoload new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/add-zsh-hook/autoload @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/add-zsh-hook/getopts b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/add-zsh-hook/getopts new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/add-zsh-hook/getopts @@ -0,0 +1 @@ + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight new file mode 100644 index 0000000..0b9cb08 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight @@ -0,0 +1,65 @@ +local ret=$? +if [[ $WIDGET == zle-isearch-update ]] && { $zsh_highlight__pat_static_bug || ! (( $+ISEARCHMATCH_ACTIVE )) }; then + region_highlight=() + return $ret +fi + +setopt localoptions warncreateglobal +setopt localoptions noksharrays +local REPLY +[[ -n ${ZSH_HIGHLIGHT_MAXLENGTH:-} ]] && [[ $#BUFFER -gt $ZSH_HIGHLIGHT_MAXLENGTH ]] && return $ret +[[ $PENDING -gt 0 ]] && return $ret +typeset -ga region_highlight +region_highlight=(); + +{ + local cache_place + local -a region_highlight_copy + local highlighter; for highlighter in $ZSH_HIGHLIGHT_HIGHLIGHTERS; do + cache_place="_zsh_highlight__highlighter_${highlighter}_cache" + typeset -ga ${cache_place} + if ! type "_zsh_highlight_highlighter_${highlighter}_predicate" >&/dev/null; then + echo "zsh-syntax-highlighting: warning: disabling the ${(qq)highlighter} highlighter as it has not been loaded" >&2 + ZSH_HIGHLIGHT_HIGHLIGHTERS=( ${ZSH_HIGHLIGHT_HIGHLIGHTERS:#${highlighter}} ) + elif "_zsh_highlight_highlighter_${highlighter}_predicate"; then + region_highlight_copy=("${region_highlight[@]}") + region_highlight=() + { + "_zsh_highlight_highlighter_${highlighter}_paint" + } always { + eval "${cache_place}=(\"\${region_highlight[@]}\")" + } + region_highlight=("${region_highlight_copy[@]}") + + fi + eval "region_highlight+=(\"\${${cache_place}[@]}\")" + + done + if (( REGION_ACTIVE == 1 )); then + _zsh_highlight_apply_zle_highlight region standout "$MARK" "$CURSOR" + elif (( REGION_ACTIVE == 2 )); then + () { + local needle=$'\n' + integer min max + if (( MARK > CURSOR )) ; then + min=$CURSOR max=$MARK + else + min=$MARK max=$CURSOR + fi + (( min = ${${BUFFER[1,$min]}[(I)$needle]} )) + (( max += ${${BUFFER:($max-1)}[(i)$needle]} - 1 )) + _zsh_highlight_apply_zle_highlight region standout "$min" "$max" + } + fi + (( $+YANK_ACTIVE )) && (( YANK_ACTIVE )) && _zsh_highlight_apply_zle_highlight paste standout "$YANK_START" "$YANK_END" + (( $+ISEARCHMATCH_ACTIVE )) && (( ISEARCHMATCH_ACTIVE )) && _zsh_highlight_apply_zle_highlight isearch underline "$ISEARCHMATCH_START" "$ISEARCHMATCH_END" + (( $+SUFFIX_ACTIVE )) && (( SUFFIX_ACTIVE )) && _zsh_highlight_apply_zle_highlight suffix bold "$SUFFIX_START" "$SUFFIX_END" + + + return $ret + + +} always { + typeset -g _ZSH_HIGHLIGHT_PRIOR_BUFFER="$BUFFER" + typeset -gi _ZSH_HIGHLIGHT_PRIOR_CURSOR=$CURSOR +} diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_add_highlight b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_add_highlight new file mode 100644 index 0000000..05d4224 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_add_highlight @@ -0,0 +1,11 @@ +local -i start end +local highlight +start=$1 +end=$2 +shift 2 +for highlight; do + if (( $+ZSH_HIGHLIGHT_STYLES[$highlight] )); then + region_highlight+=("$start $end $ZSH_HIGHLIGHT_STYLES[$highlight]") + break + fi +done diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_apply_zle_highlight b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_apply_zle_highlight new file mode 100644 index 0000000..dca21f3 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_apply_zle_highlight @@ -0,0 +1,20 @@ +local entry="$1" default="$2" +integer first="$3" second="$4" +local region="${zle_highlight[(r)${entry}:*]}" + +if [[ -z "$region" ]]; then + region=$default +else + region="${region#${entry}:}" + if [[ -z "$region" ]] || [[ "$region" == none ]]; then + return + fi +fi + +integer start end +if (( first < second )); then + start=$first end=$second +else + start=$second end=$first +fi +region_highlight+=("$start $end $region") diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_bind_widgets b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_bind_widgets new file mode 100644 index 0000000..8c819ce --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_bind_widgets @@ -0,0 +1,34 @@ +setopt localoptions noksharrays +typeset -F SECONDS +local prefix=orig-s$SECONDS-r$RANDOM +zmodload zsh/zleparameter 2>/dev/null || { + print -r -- >&2 'zsh-syntax-highlighting: failed loading zsh/zleparameter.' + return 1 +} +local -U widgets_to_bind +widgets_to_bind=(${${(k)widgets}:#(.*|run-help|which-command|beep|set-local-history|yank)}) +widgets_to_bind+=(zle-line-finish) +widgets_to_bind+=(zle-isearch-update) + +local cur_widget +for cur_widget in $widgets_to_bind; do + case $widgets[$cur_widget] in + user:_zsh_highlight_widget_*);; + user:*) zle -N $prefix-$cur_widget ${widgets[$cur_widget]#*:} + eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget ${(q)prefix}-${(q)cur_widget} -- \"\$@\" }" + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; + completion:*) zle -C $prefix-$cur_widget ${${(s.:.)widgets[$cur_widget]}[2,3]} + eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget ${(q)prefix}-${(q)cur_widget} -- \"\$@\" }" + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; + builtin) eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget .${(q)cur_widget} -- \"\$@\" }" + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; + *) + if [[ $cur_widget == zle-* ]] && [[ -z $widgets[$cur_widget] ]]; then + _zsh_highlight_widget_${cur_widget}() { :; _zsh_highlight } + zle -N $cur_widget _zsh_highlight_widget_$cur_widget + else + print -r -- >&2 "zsh-syntax-highlighting: unhandled ZLE widget ${(qq)cur_widget}" + print -r -- >&2 "zsh-syntax-highlighting: (This is sometimes caused by doing \`bindkey <keys> ${(q-)cur_widget}\` without creating the ${(qq)cur_widget} widget with \`zle -N\` or \`zle -C\`.)" + fi + esac +done diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_buffer_modified b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_buffer_modified new file mode 100644 index 0000000..bae2dce --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_buffer_modified @@ -0,0 +1 @@ +[[ "${_ZSH_HIGHLIGHT_PRIOR_BUFFER:-}" != "$BUFFER" ]] diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_call_widget b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_call_widget new file mode 100644 index 0000000..109d456 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_call_widget @@ -0,0 +1,2 @@ +builtin zle "$@" && +_zsh_highlight diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_cursor_moved b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_cursor_moved new file mode 100644 index 0000000..1fb5894 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_cursor_moved @@ -0,0 +1 @@ +[[ -n $CURSOR ]] && [[ -n ${_ZSH_HIGHLIGHT_PRIOR_CURSOR-} ]] && (($_ZSH_HIGHLIGHT_PRIOR_CURSOR != $CURSOR)) diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_load_highlighters b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_load_highlighters new file mode 100644 index 0000000..828250b --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_load_highlighters @@ -0,0 +1,25 @@ +setopt localoptions noksharrays +[[ -d "$1" ]] || { + print -r -- >&2 "zsh-syntax-highlighting: highlighters directory ${(qq)1} not found." + return 1 +} +local highlighter highlighter_dir +for highlighter_dir ($1/*/); do + highlighter="${highlighter_dir:t}" + [[ -f "$highlighter_dir${highlighter}-highlighter.zsh" ]] && + . "$highlighter_dir${highlighter}-highlighter.zsh" + if type "_zsh_highlight_highlighter_${highlighter}_paint" &> /dev/null && + type "_zsh_highlight_highlighter_${highlighter}_predicate" &> /dev/null; + then + elif type "_zsh_highlight_${highlighter}_highlighter" &> /dev/null && + type "_zsh_highlight_${highlighter}_highlighter_predicate" &> /dev/null; + then + if false; then + print -r -- >&2 "zsh-syntax-highlighting: warning: ${(qq)highlighter} highlighter uses deprecated entry point names; please ask its maintainer to update it: https://github.com/zsh-users/zsh-syntax-highlighting/issues/329" + fi + eval "_zsh_highlight_highlighter_${(q)highlighter}_paint() { _zsh_highlight_${(q)highlighter}_highlighter \"\$@\" }" + eval "_zsh_highlight_highlighter_${(q)highlighter}_predicate() { _zsh_highlight_${(q)highlighter}_highlighter_predicate \"\$@\" }" + else + print -r -- >&2 "zsh-syntax-highlighting: ${(qq)highlighter} highlighter should define both required functions '_zsh_highlight_highlighter_${highlighter}_paint' and '_zsh_highlight_highlighter_${highlighter}_predicate' in ${(qq):-"$highlighter_dir${highlighter}-highlighter.zsh"}." + fi +done diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_preexec_hook b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_preexec_hook new file mode 100644 index 0000000..02138a3 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_preexec_hook @@ -0,0 +1,2 @@ +typeset -g _ZSH_HIGHLIGHT_PRIOR_BUFFER= +typeset -gi _ZSH_HIGHLIGHT_PRIOR_CURSOR= diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/hooks/real-zsyh.zsh/_zsh_highlight_preexec_hook b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/hooks/real-zsyh.zsh/_zsh_highlight_preexec_hook new file mode 100644 index 0000000..54d9f65 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/hooks/real-zsyh.zsh/_zsh_highlight_preexec_hook @@ -0,0 +1 @@ +preexec diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/rev_call_tree.zsd b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/rev_call_tree.zsd new file mode 100644 index 0000000..9468ff3 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/rev_call_tree.zsd @@ -0,0 +1,6 @@ +real-zsyh.zsh/_zsh_highlight: real-zsyh.zsh/_zsh_highlight_call_widget +real-zsyh.zsh/_zsh_highlight_apply_zle_highlight: real-zsyh.zsh/_zsh_highlight +real-zsyh.zsh/_zsh_highlight_bind_widgets: real-zsyh.zsh/zsd_script_body +real-zsyh.zsh/_zsh_highlight_load_highlighters: real-zsyh.zsh/zsd_script_body +real-zsyh.zsh/add-zsh-hook: real-zsyh.zsh/zsd_script_body +real-zsyh.zsh/is-at-least: real-zsyh.zsh/zsd_script_body diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/trees/real-zsyh.zsh/Script_Body_.tree b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/trees/real-zsyh.zsh/Script_Body_.tree new file mode 100644 index 0000000..1022d83 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/trees/real-zsyh.zsh/Script_Body_.tree @@ -0,0 +1,7 @@ +Script_Body_ +|-- _zsh_highlight_bind_widgets +|-- _zsh_highlight_load_highlighters +|-- add-zsh-hook +`-- is-at-least + +4 directories, 0 files diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/trees/real-zsyh.zsh/_zsh_highlight.tree b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/trees/real-zsyh.zsh/_zsh_highlight.tree new file mode 100644 index 0000000..d911b93 --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/trees/real-zsyh.zsh/_zsh_highlight.tree @@ -0,0 +1,4 @@ +_zsh_highlight +`-- _zsh_highlight_apply_zle_highlight + +1 directory, 0 files diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/trees/real-zsyh.zsh/_zsh_highlight_call_widget.tree b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/trees/real-zsyh.zsh/_zsh_highlight_call_widget.tree new file mode 100644 index 0000000..bfb060a --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/trees/real-zsyh.zsh/_zsh_highlight_call_widget.tree @@ -0,0 +1,5 @@ +_zsh_highlight_call_widget +`-- _zsh_highlight + `-- _zsh_highlight_apply_zle_highlight + +2 directories, 0 files diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/real-zsyh.zsh.adoc b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/real-zsyh.zsh.adoc new file mode 100644 index 0000000..dc2ebdd --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/real-zsyh.zsh.adoc @@ -0,0 +1,205 @@ +real-zsyh.zsh(1) +================ +:compat-mode!: + +NAME +---- +real-zsyh.zsh - a shell script + +SYNOPSIS +-------- +Documentation automatically generated with `zshelldoc' + +FUNCTIONS +--------- + + _zsh_highlight + _zsh_highlight_add_highlight + _zsh_highlight_apply_zle_highlight + _zsh_highlight_bind_widgets + _zsh_highlight_buffer_modified + _zsh_highlight_call_widget + _zsh_highlight_cursor_moved + _zsh_highlight_load_highlighters +AUTOLOAD add-zsh-hook +AUTOLOAD is-at-least +PREEXEC-HOOK _zsh_highlight_preexec_hook + +DETAILS +------- + +Script Body +~~~~~~~~~~~ + +Has 36 line(s). Calls functions: + + Script-Body + |-- _zsh_highlight_bind_widgets + |-- _zsh_highlight_load_highlighters + |-- add-zsh-hook + `-- is-at-least + +Uses feature(s): _autoload_, _eval_, _unalias_, _zmodload_ + +_zsh_highlight +~~~~~~~~~~~~~~ + +____ + # Update ZLE buffer syntax highlighting. + # + # Invokes each highlighter that needs updating. + # This function is supposed to be called whenever the ZLE state changes. +____ + +Has 65 line(s). Calls functions: + + _zsh_highlight + `-- _zsh_highlight_apply_zle_highlight + +Uses feature(s): _eval_, _type_ + +Called by: + + _zsh_highlight_call_widget + +_zsh_highlight_add_highlight +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Add a highlight defined by ZSH_HIGHLIGHT_STYLES. + # + # Should be used by all highlighters aside from 'pattern' (cf. ZSH_HIGHLIGHT_PATTERN). + # Overwritten in tests/test-highlighting.zsh when testing. +____ + +Has 11 line(s). Doesn't call other functions. + +Not called by script or any function (may be e.g. a hook, a Zle widget, etc.). + +_zsh_highlight_apply_zle_highlight +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Apply highlighting based on entries in the zle_highlight array. + # This function takes four arguments: + # 1. The exact entry (no patterns) in the zle_highlight array: + # region, paste, isearch, or suffix + # 2. The default highlighting that should be applied if the entry is unset + # 3. and 4. Two integer values describing the beginning and end of the + # range. The order does not matter. +____ + +Has 20 line(s). Doesn't call other functions. + +Called by: + + _zsh_highlight + +_zsh_highlight_bind_widgets +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Rebind all ZLE widgets to make them invoke _zsh_highlights. +____ + +Has 34 line(s). Doesn't call other functions. + +Uses feature(s): _eval_, _zle_, _zmodload_ + +Called by: + + Script-Body + +_zsh_highlight_buffer_modified +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Whether the command line buffer has been modified or not. + # + # Returns 0 if the buffer has changed since _zsh_highlight was last called. +____ + +Has 1 line(s). Doesn't call other functions. + +Not called by script or any function (may be e.g. a hook, a Zle widget, etc.). + +_zsh_highlight_call_widget +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Helper for _zsh_highlight_bind_widgets + # $1 is name of widget to call +____ + +Has 2 line(s). Calls functions: + + _zsh_highlight_call_widget + `-- _zsh_highlight + `-- _zsh_highlight_apply_zle_highlight + +Uses feature(s): _zle_ + +Not called by script or any function (may be e.g. a hook, a Zle widget, etc.). + +_zsh_highlight_cursor_moved +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Whether the cursor has moved or not. + # + # Returns 0 if the cursor has moved since _zsh_highlight was last called. +____ + +Has 1 line(s). Doesn't call other functions. + +Not called by script or any function (may be e.g. a hook, a Zle widget, etc.). + +_zsh_highlight_load_highlighters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Load highlighters from directory. + # + # Arguments: + # 1) Path to the highlighters directory. +____ + +Has 25 line(s). Doesn't call other functions. + +Uses feature(s): _eval_, _type_ + +Called by: + + Script-Body + +_zsh_highlight_preexec_hook +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +____ + # Reset scratch variables when commandline is done. +____ + +Has 2 line(s). *Is a preexec hook*. Doesn't call other functions. + +Not called by script or any function (may be e.g. a hook, a Zle widget, etc.). + +add-zsh-hook +~~~~~~~~~~~~ + +Has 93 line(s). Doesn't call other functions. + +Uses feature(s): _autoload_, _getopts_ + +Called by: + + Script-Body + +is-at-least +~~~~~~~~~~~ + +Has 56 line(s). Doesn't call other functions. + +Called by: + + Script-Body + diff --git a/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/zsd.config b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/zsd.config new file mode 100644 index 0000000..434e12b --- /dev/null +++ b/06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/zsd.config @@ -0,0 +1 @@ +zsh_control_bin="zsh" diff --git a/06-automation/06-02-tools-bats/tools/build-helper.sh b/06-automation/06-02-tools-bats/tools/build-helper.sh new file mode 100755 index 0000000..d189447 --- /dev/null +++ b/06-automation/06-02-tools-bats/tools/build-helper.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +# Skrypty pomocnicze instalujące / ściągające dodatkowe aplikacje takie jak +# Shellcheck / Bats czy ZShell Docs. + +[[ -z $DEBUG ]] || set -o xtrace + +set -o errexit +set -o errtrace +set -o nounset +set -o pipefail + +ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +function usage { + echo "usage: $0: <download-editorconfigchecker|download-shellcheck|download-bats|install-zsd|generate-zsd|initialize-submodules>" + exit 1 +} + +if [[ $# -ne 1 ]]; then + usage +fi + +case $1 in + download-editorconfigchecker) + if [[ "${OSTYPE}" == linux* ]]; then + EC_VERSION="2.1.0" + EC_ARCHIVE="ec-linux-amd64.tar.gz" + if [[ -x "${ROOT_DIR}/../build/editorconfigchecker/bin/ec" ]]; then + echo "editorconfigchecker already downloaded - skipping..." + exit 0 + fi + wget -P "${ROOT_DIR}/../build/editorconfigchecker" \ + "https://github.com/editorconfig-checker/editorconfig-checker/releases/download/${EC_VERSION}/${EC_ARCHIVE}" + pushd "${ROOT_DIR}/../build/editorconfigchecker" + tar xzvf "${EC_ARCHIVE}" + rm -vf -- "${EC_ARCHIVE}" + mv bin/ec-linux-amd64 bin/ec + popd + else + echo "It seems that automatic installation is not supported on your platform." + echo "Please install editorconfig-checker manually:" + echo " https://github.com/editorconfig-checker/editorconfig-checker#installation" + echo " Place binary suitable for your OS under build/editorconfigchecker/bin/ec" + exit 1 + fi + ;; + download-shellcheck) + if [[ "${OSTYPE}" == linux* ]]; then + SHELLCHECK_VERSION="v0.7.1" + SHELLCHECK_ARCHIVE="shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" + if [[ -x "${ROOT_DIR}/../build/shellcheck-${SHELLCHECK_VERSION}/shellcheck" ]]; then + echo "shellcheck already downloaded - skipping..." + exit 0 + fi + wget -P "${ROOT_DIR}/../build/" \ + "https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/${SHELLCHECK_ARCHIVE}" + pushd "${ROOT_DIR}/../build/" + tar xvf "${SHELLCHECK_ARCHIVE}" + rm -vf -- "${SHELLCHECK_ARCHIVE}" + popd + else + echo "It seems that automatic installation is not supported on your platform." + echo "Please install shellcheck manually:" + echo " https://github.com/koalaman/shellcheck#installing" + exit 1 + fi + ;; + download-bats) + if [[ -x "${ROOT_DIR}/../build/bats/bin/bats" ]]; then + echo "bats already downloaded - skipping..." + exit 0 + fi + git clone https://github.com/bats-core/bats-core.git "${ROOT_DIR}/../build/bats" + ;; + install-zsd) + zshInstalled="false" + zsh --version && zshInstalled="true" || echo "zsh is missing" + if [[ "${zshInstalled}" != "true" ]]; then + echo "[WARNING] ZSH is missing! Will return 0 but won't generate any docs" + exit 0 + fi + if [[ -x "${ROOT_DIR}/../build/zsd/bin/zsd" ]]; then + echo "zsd already installed - skipping..." + exit 0 + fi + "${ROOT_DIR}/build-helper.sh" initialize-submodules + pushd "${ROOT_DIR}/../src/test/docs_helper/zshelldoc/" + make install PREFIX="${ROOT_DIR}/../build/zsd" + popd + ;; + generate-zsd) + zshInstalled="false" + zsh --version && zshInstalled="true" || echo "zsh is missing" + if [[ "${zshInstalled}" != "true" ]]; then + echo "[WARNING] ZSH is missing! Will return 0 but won't generate any docs" + exit 0 + fi + pushd "${ROOT_DIR}/../src/main/bash" + # shellcheck disable=SC2035 + "${ROOT_DIR}/../build/zsd/bin/zsd" --cignore '\#*FUNCTION:*{{{*|\#*synopsis*{{{*' *.sh + popd + ;; + initialize-submodules) + files="$( ls "${ROOT_DIR}/../src/test/docs_helper/zshelldoc/" || echo "" )" + if [ -n "${files}" ]; then + echo "Submodules already initialized"; + git submodule foreach git pull origin master || echo "Failed to pull - continuing the script" + else + echo "Initilizing submodules" + git submodule init + git submodule update + git submodule foreach git pull origin master || echo "Failed to pull - continuing the script" + fi + ;; + *) + usage + ;; +esac diff --git a/06-automation/06-02-tools-invoke/README.adoc b/06-automation/06-02-tools-invoke/README.adoc new file mode 100644 index 0000000..97f6ffa --- /dev/null +++ b/06-automation/06-02-tools-invoke/README.adoc @@ -0,0 +1,7 @@ += Budowanie Narzędziem Opartym na Dowolności na Przykładzie invoke + +W skrypcie `tasks.py` kodujemy nasze zadania, które możemy następnie dowolnie uruchamiać poleceniem `inv <nazwa taska>`. + +Na przykład: `inv qa` uruchomi zadanie, które zależne jest od dwóch innych - jedno uruchamia testy, a drugie lintera. + +Więcej informacji: http://docs.pyinvoke.org/en/stable/ diff --git a/06-automation/06-02-tools-invoke/smarttesting/__init__.py b/06-automation/06-02-tools-invoke/smarttesting/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-02-tools-invoke/tasks.py b/06-automation/06-02-tools-invoke/tasks.py new file mode 100644 index 0000000..5dea262 --- /dev/null +++ b/06-automation/06-02-tools-invoke/tasks.py @@ -0,0 +1,18 @@ +from invoke import task + + +@task +def test(ctx): + """Uruchom testy""" + ctx.run("pytest") + + +@task +def lint(ctx): + """Uruchom lintera flake8.""" + ctx.run("flake8 ./") + + +@task(lint, test) +def qa(_ctx): + """Uruchom lintera i testy.""" diff --git a/06-automation/06-02-tools-invoke/tests/__init__.py b/06-automation/06-02-tools-invoke/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-02-tools-invoke/tests/passing_test.py b/06-automation/06-02-tools-invoke/tests/passing_test.py new file mode 100644 index 0000000..70b438b --- /dev/null +++ b/06-automation/06-02-tools-invoke/tests/passing_test.py @@ -0,0 +1,2 @@ +def test_passing() -> None: + """By pytest nie zwrócił kodu błędu z powodu nieznalezienia żadnych testów.""" diff --git a/06-automation/06-02-tools-pybuilder/.gitignore b/06-automation/06-02-tools-pybuilder/.gitignore new file mode 100644 index 0000000..5f4ec0e --- /dev/null +++ b/06-automation/06-02-tools-pybuilder/.gitignore @@ -0,0 +1,2 @@ +.pybuilder +target/ diff --git a/06-automation/06-02-tools-pybuilder/README.adoc b/06-automation/06-02-tools-pybuilder/README.adoc new file mode 100644 index 0000000..72904d8 --- /dev/null +++ b/06-automation/06-02-tools-pybuilder/README.adoc @@ -0,0 +1,13 @@ += Budowanie Narzędziem Opartym o Konwencje na Przykładzie Pybuildera + +Pliki setup.py i pyproject.toml zostały wygenerowane automatycznie z komendy `pyb --start-project`. + +W pliku build.py znajdziemy ustawienia build, używane wtyczki itd. + +Budowanie uruchmiamy poleceniem: +```bash +cd 06-automation/06-03-tools-pybuilder/ +pyb +``` + +Więcej informacji: https://pybuilder.io/ diff --git a/06-automation/06-02-tools-pybuilder/build.py b/06-automation/06-02-tools-pybuilder/build.py new file mode 100644 index 0000000..873b95c --- /dev/null +++ b/06-automation/06-02-tools-pybuilder/build.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from pybuilder.core import init, use_plugin + +use_plugin("python.core") +use_plugin("python.unittest") +use_plugin("python.flake8") + + +name = "smarttesting_pyb_demo" +default_task = "publish" + + +@init +def set_properties(project): + project.set_property("dir_source_main_python", "smarttesting_pyb_demo") + project.set_property("dir_source_unittest_python", "smarttesting_pyb_demo/tests") + project.set_property("dir_source_main_scripts", "scripts") diff --git a/06-automation/06-02-tools-pybuilder/pyproject.toml b/06-automation/06-02-tools-pybuilder/pyproject.toml new file mode 100644 index 0000000..950f549 --- /dev/null +++ b/06-automation/06-02-tools-pybuilder/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["pybuilder>=0.12.0"] +build-backend = "pybuilder.pep517" diff --git a/06-automation/06-02-tools-pybuilder/setup.py b/06-automation/06-02-tools-pybuilder/setup.py new file mode 100644 index 0000000..31bdfd3 --- /dev/null +++ b/06-automation/06-02-tools-pybuilder/setup.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# This file is part of PyBuilder +# +# Copyright 2011-2020 PyBuilder Team +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# This script allows to support installation via: +# pip install git+git://<project>@<branch> +# +# This script is designed to be used in combination with `pip install` ONLY +# +# DO NOT RUN MANUALLY +# + +import glob +import os +import shutil +import subprocess +import sys +from sys import version_info + +py3 = version_info[0] == 3 +py2 = not py3 +if py2: + FileNotFoundError = OSError + + +def install_pyb(): + try: + subprocess.check_call([sys.executable, "-m", "pip", "install", "pybuilder"]) + except subprocess.CalledProcessError as e: + sys.exit(e.returncode) + + +script_dir = os.path.dirname(os.path.realpath(__file__)) +exit_code = 0 + +try: + subprocess.check_call(["pyb", "--version"]) +except FileNotFoundError as e: + if py3 or py2 and e.errno == 2: + install_pyb() + else: + raise +except subprocess.CalledProcessError as e: + if e.returncode == 127: + install_pyb() + else: + sys.exit(e.returncode) + +try: + from pybuilder.cli import main + + # verbose, debug, skip all optional... + if main("-v", "-X", "-o", "--reset-plugins", "clean", "package"): + raise RuntimeError("PyBuilder build failed") + + from pybuilder.reactor import Reactor + + reactor = Reactor.current_instance() + project = reactor.project + dist_dir = project.expand_path("$dir_dist") + + for src_file in glob.glob(os.path.join(dist_dir, "*")): + file_name = os.path.basename(src_file) + target_file_name = os.path.join(script_dir, file_name) + if os.path.exists(target_file_name): + if os.path.isdir(target_file_name): + shutil.rmtree(target_file_name) + else: + os.remove(target_file_name) + shutil.move(src_file, script_dir) + setup_args = sys.argv[1:] + subprocess.check_call([sys.executable, "setup.py"] + setup_args, cwd=script_dir) +except subprocess.CalledProcessError as e: + exit_code = e.returncode +sys.exit(exit_code) diff --git a/06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/docs/.gitkeep b/06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/docs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/scripts/__init__.py b/06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/smarttesting/__init__.py b/06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/smarttesting/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/smarttesting/hello_world.py b/06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/smarttesting/hello_world.py new file mode 100644 index 0000000..19e975c --- /dev/null +++ b/06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/smarttesting/hello_world.py @@ -0,0 +1,10 @@ +from typing import Protocol + + +class Writable(Protocol): + def write(self, data: str) -> None: + ... + + +def hello_world(out: Writable) -> None: + out.write("Hello, world!\n") diff --git a/06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/tests/__init__.py b/06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/tests/hello_world_tests.py b/06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/tests/hello_world_tests.py new file mode 100644 index 0000000..fcaa255 --- /dev/null +++ b/06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/tests/hello_world_tests.py @@ -0,0 +1,13 @@ +import unittest +from unittest.mock import Mock + +from smarttesting.hello_world import hello_world + + +class TestHelloWorld(unittest.TestCase): + def test_writes_hello_world(self) -> None: + out = Mock() + + hello_world(out) + + assert out.write.called_once_with("Hello, world!\n") diff --git a/06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/scripts/__init__.py b/06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/smarttesting/__init__.py b/06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/smarttesting/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/smarttesting/hello_world.py b/06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/smarttesting/hello_world.py new file mode 100644 index 0000000..19e975c --- /dev/null +++ b/06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/smarttesting/hello_world.py @@ -0,0 +1,10 @@ +from typing import Protocol + + +class Writable(Protocol): + def write(self, data: str) -> None: + ... + + +def hello_world(out: Writable) -> None: + out.write("Hello, world!\n") diff --git a/06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/tests/__init__.py b/06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/tests/hello_world_tests.py b/06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/tests/hello_world_tests.py new file mode 100644 index 0000000..fcaa255 --- /dev/null +++ b/06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/tests/hello_world_tests.py @@ -0,0 +1,13 @@ +import unittest +from unittest.mock import Mock + +from smarttesting.hello_world import hello_world + + +class TestHelloWorld(unittest.TestCase): + def test_writes_hello_world(self) -> None: + out = Mock() + + hello_world(out) + + assert out.write.called_once_with("Hello, world!\n") diff --git a/06-automation/06-02-tools-pybuilder/target/reports/TEST-hello_world_tests.TestHelloWorld-20230915210537.xml b/06-automation/06-02-tools-pybuilder/target/reports/TEST-hello_world_tests.TestHelloWorld-20230915210537.xml new file mode 100644 index 0000000..19dc2e2 --- /dev/null +++ b/06-automation/06-02-tools-pybuilder/target/reports/TEST-hello_world_tests.TestHelloWorld-20230915210537.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<testsuite name="hello_world_tests.TestHelloWorld-20230915210537" tests="1" file="hello_world_tests.py" time="0.002" timestamp="2023-09-15T21:05:37" failures="0" errors="0" skipped="0"> + <testcase classname="hello_world_tests.TestHelloWorld" name="test_writes_hello_world" time="0.002" timestamp="2023-09-15T21:05:37"/> +</testsuite> diff --git a/06-automation/06-02-tools-pybuilder/target/reports/unittest b/06-automation/06-02-tools-pybuilder/target/reports/unittest new file mode 100644 index 0000000..1587940 --- /dev/null +++ b/06-automation/06-02-tools-pybuilder/target/reports/unittest @@ -0,0 +1,10 @@ + +Running tests... +---------------------------------------------------------------------- +. +---------------------------------------------------------------------- +Ran 1 test in 0.023s + +OK + +Generating XML reports... diff --git a/06-automation/06-02-tools-pybuilder/target/reports/unittest.json b/06-automation/06-02-tools-pybuilder/target/reports/unittest.json new file mode 100644 index 0000000..d34ede9 --- /dev/null +++ b/06-automation/06-02-tools-pybuilder/target/reports/unittest.json @@ -0,0 +1,5 @@ +{ + "errors": [], + "failures": [], + "tests-run": 1 +} \ No newline at end of file diff --git a/06-automation/06-03-sca/README.adoc b/06-automation/06-03-sca/README.adoc new file mode 100644 index 0000000..7aaac69 --- /dev/null +++ b/06-automation/06-03-sca/README.adoc @@ -0,0 +1,6 @@ += Uruchamianie linterów + +``` +pylint smarttesting/ tests/ +flake8 . +``` diff --git a/06-automation/06-03-sca/smarttesting/__init__.py b/06-automation/06-03-sca/smarttesting/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-03-sca/smarttesting/customer/__init__.py b/06-automation/06-03-sca/smarttesting/customer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-03-sca/smarttesting/customer/customer.py b/06-automation/06-03-sca/smarttesting/customer/customer.py new file mode 100644 index 0000000..e3abc18 --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/customer/customer.py @@ -0,0 +1,28 @@ +from dataclasses import dataclass +from uuid import UUID + +from smarttesting.customer.person import Person + + +@dataclass +class Customer: + """Klient. Klasa opakowująca osobę do zweryfikowania.""" + + _uuid: UUID + _person: Person + + @property + def uuid(self) -> UUID: + return self._uuid + + @property + def person(self) -> Person: + return self._person + + @property + def is_student(self) -> bool: + return self._person.is_student + + @property + def student(self): + return self._person.student diff --git a/06-automation/06-03-sca/smarttesting/customer/person.py b/06-automation/06-03-sca/smarttesting/customer/person.py new file mode 100644 index 0000000..7825d55 --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/customer/person.py @@ -0,0 +1,64 @@ +import enum +from dataclasses import dataclass +from datetime import date + + +class Gender(enum.Enum): + MALE = enum.auto() + FEMALE = enum.auto() + + +class Status(enum.Enum): + STUDENT = enum.auto() + NOT_STUDENT = enum.auto() + + +@dataclass +class Person: + """Reprezentuje osobę do zweryfikowania.""" + + _name: str + _surname: str + _date_of_birth: date + _gender: Gender + _national_id_number: str + _status: Status = Status.NOT_STUDENT + + @property + def name(self) -> str: + return self._name + + @property + def surname(self) -> str: + return self._surname + + @property + def date_of_birth(self) -> date: + return self._date_of_birth + + @property + def gender(self) -> Gender: + return self._gender + + @property + def national_id_number(self) -> str: + return self._national_id_number + + @property + def is_student(self) -> bool: + return self._status == Status.STUDENT + + def student(self) -> None: + self._status = Status.STUDENT + + @property + def age(self): + today = date.today() + years_diff = today.year - self._date_of_birth.year + had_birthday_this_year = ( + today.replace(year=self._date_of_birth.year) < self._date_of_birth + ) + if had_birthday_this_year: + years_diff -= 1 + + return years_diff diff --git a/06-automation/06-03-sca/smarttesting/message.py b/06-automation/06-03-sca/smarttesting/message.py new file mode 100644 index 0000000..1a51c39 --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/message.py @@ -0,0 +1,17 @@ +from typing import ClassVar, Dict, Type + + +class Message: + """Klasa bazowa dla wszystkich wiadomości. + + Potrzebna jest nam 'jedynie' do implementacji własnego kodeka do tasków Celery + by można było przekazywać instancje dataclass jako argumenty wywołania tasków.""" + + __messages_by_name: ClassVar[Dict[str, Type]] = {} + + def __init_subclass__(cls) -> None: + cls.__messages_by_name[cls.__name__] = cls + + @classmethod + def subclass_for_name(cls, name: str) -> Type: + return cls.__messages_by_name[name] diff --git a/06-automation/06-03-sca/smarttesting/module_a/__init__.py b/06-automation/06-03-sca/smarttesting/module_a/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-03-sca/smarttesting/module_a/class_a.py b/06-automation/06-03-sca/smarttesting/module_a/class_a.py new file mode 100644 index 0000000..2dc5b07 --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/module_a/class_a.py @@ -0,0 +1,14 @@ +from smarttesting.module_b.class_b import ClassB +from smarttesting.module_c.class_c import ClassC + + +class ClassA: + """ + Odpowiednik kodu ze slajdów do wizualizacji wzajemnej zależności modułów. + + Klasa ClassA z modułu A korzysta z modułów B i C. + """ + + def __init__(self, class_b: ClassB, class_c: ClassC) -> None: + self.class_b = class_b + self.class_c = class_c diff --git a/06-automation/06-03-sca/smarttesting/module_a/my_popo.py b/06-automation/06-03-sca/smarttesting/module_a/my_popo.py new file mode 100644 index 0000000..01b1883 --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/module_a/my_popo.py @@ -0,0 +1,50 @@ +from typing import Optional + + +class MyPopo: + """ + Plain-Old Python object + gettery/settery. Niespotykane w Pythonie, raczej uważane + za antypattern (tj. nie ma narzędzi korzystającej z tej konwencji i kod pisany jest + na próżno. + + Raczej będziemy zawsze bezpośrednio przypisywać wartości do pól, np: + ``` + inst = MyPopo() + inst.name = "Janusz" + ``` + + Gdy jest potrzeba customizacji tego zachowania lub wprowadzenia pól read-only, + zawsze można użyć dekoratora @property. Zobacz przykład poniżej dla `_age`. + """ + + def __init__(self) -> None: + self._name: Optional[str] = None + self._surname: Optional[str] = None + self._age: int = 0 + + def get_name(self) -> Optional[str]: + return self._name + + def set_name(self, value: str) -> None: + self._name = value + + def get_surname(self) -> Optional[str]: + return self._surname + + def set_surname(self, value: str) -> None: + self._surname = value + + @property + def age(self) -> int: + """Gdybyśmy nie zrobili settera (poniżej) to mielibyśmy pole read-only. + + Oczywiście dopóki ktoś nie postanowi zrobić `inst._age = -10000` (: + """ + return self._age + + @age.setter + def age(self, value: int) -> None: + if value < 0: + raise ValueError("No bez jaj, ujemny wiek?!") + + self._age = value diff --git a/06-automation/06-03-sca/smarttesting/module_b/__init__.py b/06-automation/06-03-sca/smarttesting/module_b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-03-sca/smarttesting/module_b/class_b.py b/06-automation/06-03-sca/smarttesting/module_b/class_b.py new file mode 100644 index 0000000..c307e5c --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/module_b/class_b.py @@ -0,0 +1,19 @@ +import typing + +from smarttesting.module_c.class_c import ClassC + +if typing.TYPE_CHECKING: + # importy cyklicznie nie będa działać w Pythonie + from smarttesting.module_a.class_a import ClassA + + +class ClassB: + """ + Odpowiednik kodu ze slajdów do wizualizacji wzajemnej zależności modułów. + + Klasa ClassB z modułu B korzysta z modułów A i C. + """ + + def __init__(self, class_a: ClassA, class_c: ClassC) -> None: + self.class_a = class_a + self.class_c = class_c diff --git a/06-automation/06-03-sca/smarttesting/module_b/my_popo.py b/06-automation/06-03-sca/smarttesting/module_b/my_popo.py new file mode 100644 index 0000000..35d38f4 --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/module_b/my_popo.py @@ -0,0 +1,15 @@ +from typing import Optional + + +class MyPopo: + """ + Odchudzone POPO - pola publiczne z domyślnymi wartościami. + + Bardziej pythonowe niż akcesory ale w 2020 roku i tak + lepiej użyć @dataclass/@attr.s lub podobnej biblioteki. + """ + + def __init__(self) -> None: + self.name: Optional[str] = None + self.surname: Optional[str] = None + self.age: int = 0 diff --git a/06-automation/06-03-sca/smarttesting/module_c/__init__.py b/06-automation/06-03-sca/smarttesting/module_c/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-03-sca/smarttesting/module_c/class_c.py b/06-automation/06-03-sca/smarttesting/module_c/class_c.py new file mode 100644 index 0000000..fe9b0f6 --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/module_c/class_c.py @@ -0,0 +1,19 @@ +import typing + +if typing.TYPE_CHECKING: + # importy cyklicznie nie będa działać w Pythonie, + # jednak da się je ogarnąc na potrzeby TYPE_CHECKINGU + from smarttesting.module_a.class_a import ClassA + from smarttesting.module_b.class_b import ClassB + + +class ClassC: + """ + Odpowiednik kodu ze slajdów do wizualizacji wzajemnej zależności modułów. + + Klasa ClassC z modułu C korzysta z modułów A i B. + """ + + def __init__(self, class_a: ClassA, class_b: ClassB) -> None: + self.class_a = class_a + self.class_b = class_b diff --git a/06-automation/06-03-sca/smarttesting/serialization.py b/06-automation/06-03-sca/smarttesting/serialization.py new file mode 100644 index 0000000..c69545c --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/serialization.py @@ -0,0 +1,53 @@ +import functools +import json +from typing import Any, Hashable, Type, cast + +from marshmallow import Schema +from marshmallow_dataclass import class_schema +from smarttesting.message import Message + +__all__ = [ + "dataclass_dump", + "dataclass_load", +] + + +def dataclass_dump(data: Any) -> str: + return json.dumps(data, cls=Encoder) + + +def dataclass_load(data: Any) -> Any: + return json.loads(data, object_hook=decoder) + + +class Encoder(json.JSONEncoder): + def default(self, o: Any) -> Any: + try: + schema = _get_schema_for_dataclass(cast(Hashable, type(o))) + except TypeError: + return json.JSONEncoder.default(self, o) + else: + dict_repr = schema.dump(o) + dict_repr["__dataclass_name__"] = type(o).__name__ + return dict_repr + + +def decoder(obj: Any) -> Any: + if "__dataclass_name__" in obj: + dataclass_name = obj.pop("__dataclass_name__") + dataclass = Message.subclass_for_name(dataclass_name) + schema = _get_schema_for_dataclass(cast(Hashable, dataclass)) + return schema.load(obj) + else: + return obj + + +@functools.lru_cache(maxsize=None) +def _get_schema_for_dataclass(dataclass_obj: Type) -> Schema: + """ + Funkcja budująca instancję schemę dla podanej klasy udekorowanej @dataclass. + + Schema jest bezstanowa, więc bezpieczne jest reużywanie obiektów. + """ + schema_cls = class_schema(dataclass_obj) + return schema_cls() diff --git a/06-automation/06-03-sca/smarttesting/verifier/__init__.py b/06-automation/06-03-sca/smarttesting/verifier/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-03-sca/smarttesting/verifier/customer/__init__.py b/06-automation/06-03-sca/smarttesting/verifier/customer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-03-sca/smarttesting/verifier/customer/bik_verification_service.py b/06-automation/06-03-sca/smarttesting/verifier/customer/bik_verification_service.py new file mode 100644 index 0000000..6a37bb9 --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/verifier/customer/bik_verification_service.py @@ -0,0 +1,61 @@ +import logging +import time +from dataclasses import dataclass + +from smarttesting.customer.customer import Customer +from smarttesting.verifier.customer.customer_verification_result import ( + CustomerVerificationResult, +) + +logger = logging.getLogger(__name__) + + +@dataclass +class BIKVerificationService: + _bik_service_url: str + + def verify(self, customer: Customer) -> CustomerVerificationResult: + try: + time.sleep(0.3) + return self.create_passed(customer) + except Exception: + logger.exception("HTTP request execution failed!") + + return CustomerVerificationResult.create_failed(customer.uuid) + + def create_passed(self, customer: Customer) -> CustomerVerificationResult: + return CustomerVerificationResult.create_passed(customer.uuid) + + def complex_method(self, a: int, b: int, c: int) -> int: + """Sztucznie skomplikowana metoda. + + Ukazuje wysoki poziom skomplikowania cyklomatycznego. + """ + d = a + 2 + e = d + 5 if a > 0 else c + f = e + 5 if d > 0 else a + result = 0 + if a > b or f > 1 and d + 1 > 3 or f < 4: + return 8 + if a > c and e > f or a > 1 and e + 1 > 3 or d < 4: + return 1 + else: + if a + 1 > c - 1 or a > b + 3 or f > 19: + return 1233 + if e < a and d > c: + if a + 4 > b - 2: + if c - 5 < a + 11: + return 81 + elif a > c: + return 102 + if a > c + 21 and e > f - 12: + return 13 + else: + if a + 10 > c - 1: + return 123 + elif e + 1 < a and d + 14 > c: + return 111 + if f > 10: + return 1 + + return result diff --git a/06-automation/06-03-sca/smarttesting/verifier/customer/customer_verification.py b/06-automation/06-03-sca/smarttesting/verifier/customer/customer_verification.py new file mode 100644 index 0000000..af6ed67 --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/verifier/customer/customer_verification.py @@ -0,0 +1,18 @@ +from dataclasses import dataclass + +from smarttesting.customer.person import Person +from smarttesting.message import Message +from smarttesting.verifier.customer.customer_verification_result import ( + CustomerVerificationResult, +) + + +@dataclass(frozen=True) +class CustomerVerification(Message): + """Klasa wiadomości, którą wysyłamy poprzez brokera. + + Reprezentuje osobę i rezultat weryfikacji. + """ + + person: Person + result: CustomerVerificationResult diff --git a/06-automation/06-03-sca/smarttesting/verifier/customer/customer_verification_result.py b/06-automation/06-03-sca/smarttesting/verifier/customer/customer_verification_result.py new file mode 100644 index 0000000..3f3006f --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/verifier/customer/customer_verification_result.py @@ -0,0 +1,36 @@ +import enum +from dataclasses import dataclass +from uuid import UUID + + +class Status(enum.Enum): + VERIFICATION_PASSED = "VERIFICATION_PASSED" + VERIFICATION_FAILED = "VERIFICATION_FAILED" + + +@dataclass(frozen=True) +class CustomerVerificationResult: + """Rezultat weryfikacji klienta.""" + + _user_id: UUID + _status: Status + + @property + def user_id(self) -> UUID: + return self._user_id + + @property + def status(self) -> Status: + return self._status + + @property + def passed(self) -> bool: + return self._status == Status.VERIFICATION_PASSED + + @staticmethod + def create_passed(user_id: UUID) -> "CustomerVerificationResult": + return CustomerVerificationResult(user_id, Status.VERIFICATION_PASSED) + + @staticmethod + def create_failed(user_id: UUID) -> "CustomerVerificationResult": + return CustomerVerificationResult(user_id, Status.VERIFICATION_FAILED) diff --git a/06-automation/06-03-sca/smarttesting/verifier/customer/customer_verifier.py b/06-automation/06-03-sca/smarttesting/verifier/customer/customer_verifier.py new file mode 100644 index 0000000..9863d4a --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/verifier/customer/customer_verifier.py @@ -0,0 +1,80 @@ +from dataclasses import dataclass +from typing import Set + +from smarttesting.customer.customer import Customer +from smarttesting.verifier.customer.bik_verification_service import ( + BIKVerificationService, +) +from smarttesting.verifier.customer.customer_verification import CustomerVerification +from smarttesting.verifier.customer.customer_verification_result import ( + CustomerVerificationResult, + Status, +) +from smarttesting.verifier.customer.fraud_alert_task import FraudAlertTask +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting.verifier.customer.verified_person_dto import VerifiedPersonDto +from smarttesting.verifier.verification import Verification + + +@dataclass +class CustomerVerifier: + """Weryfikacja czy klient jest oszustem czy nie. + + Przechodzi po różnych implementacjach weryfikacji i jeśli, przy którejś okaże się, + że użytkownik jest oszustem, wówczas wysyłamy wiadomość do brokera, z informacją + o oszuście. + """ + + _bik_verification_service: BIKVerificationService + _verifications: Set[Verification] + _repository: VerificationRepository + _fraud_alert_task: FraudAlertTask + + def verify(self, customer: Customer) -> CustomerVerificationResult: + """ + Główna metoda biznesowa. Sprawdza, czy już nie doszło do weryfikacji klienta + i jeśli rezultat zostanie odnaleziony w bazie danych to go zwraca. W innym + przypadku zapisuje wynik weryfikacji w bazie danych. Weryfikacja wówczas + zachodzi poprzez odpytanie BIKu o stan naszego klienta. + """ + prior_result = self._repository.find_by_user_id(customer.uuid) + if prior_result: + return CustomerVerificationResult( + prior_result.uuid, Status(prior_result.status) + ) + else: + return self._verify_customer(customer) + + def _verify_customer(self, customer: Customer) -> CustomerVerificationResult: + result = self._perform_checks(customer) + self._save_verification_result(customer, result) + if not result.passed: + customer_verification = CustomerVerification(customer.person, result) + self._fraud_alert_task.delay(customer_verification=customer_verification) + return result + + def _perform_checks(self, customer: Customer) -> CustomerVerificationResult: + external_result = self._bik_verification_service.verify(customer) + + person = customer.person + verifications_passed = all( + verification.passes(person) for verification in self._verifications + ) + + if external_result.passed and verifications_passed: + return CustomerVerificationResult.create_passed(customer.uuid) + else: + return CustomerVerificationResult.create_failed(customer.uuid) + + def _save_verification_result( + self, customer: Customer, result: CustomerVerificationResult + ) -> None: + self._repository.save( + VerifiedPersonDto( + uuid=customer.uuid, + national_identification_number=customer.person.national_id_number, + status=result.status, + ) + ) diff --git a/06-automation/06-03-sca/smarttesting/verifier/customer/fraud_alert_task.py b/06-automation/06-03-sca/smarttesting/verifier/customer/fraud_alert_task.py new file mode 100644 index 0000000..010ab5c --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/verifier/customer/fraud_alert_task.py @@ -0,0 +1,17 @@ +from typing import Protocol + +from smarttesting.verifier.customer.customer_verification import CustomerVerification + + +class TaskResult(Protocol): + """Prosty protokół opokowujący AsyncResult z Celery.""" + + def get(self) -> None: + ... + + +class FraudAlertTask(Protocol): + """Prosty protokół opakowaujący taska celery z danym argumentem.""" + + def delay(self, *, customer_verification: CustomerVerification) -> TaskResult: + ... diff --git a/06-automation/06-03-sca/smarttesting/verifier/customer/fraud_detected_handler.py b/06-automation/06-03-sca/smarttesting/verifier/customer/fraud_detected_handler.py new file mode 100644 index 0000000..91396f3 --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/verifier/customer/fraud_detected_handler.py @@ -0,0 +1,26 @@ +import logging + +from injector import Inject +from smarttesting.verifier.customer.customer_verification import CustomerVerification +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting.verifier.customer.verified_person_dto import VerifiedPersonDto + +logger = logging.getLogger(__name__) + + +def fraud_detected_handler( + repo: Inject[VerificationRepository], *, customer_verification: CustomerVerification +) -> None: + """Implementacja zadania przechodzącego przez brokera RabbitMQ.""" + logger.info("Got customer verification: %s", customer_verification) + person = customer_verification.person + result = customer_verification.result + repo.save( + VerifiedPersonDto( + uuid=result.user_id, + national_identification_number=person.national_id_number, + status=result.status, + ) + ) diff --git a/06-automation/06-03-sca/smarttesting/verifier/customer/module.py b/06-automation/06-03-sca/smarttesting/verifier/customer/module.py new file mode 100644 index 0000000..4005ee3 --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/verifier/customer/module.py @@ -0,0 +1,34 @@ +from typing import Set + +import injector +from smarttesting.verifier.customer.bik_verification_service import ( + BIKVerificationService, +) +from smarttesting.verifier.customer.customer_verifier import ( + CustomerVerifier, + FraudAlertTask, +) +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting.verifier.verification import Verification + + +class CustomerModule(injector.Module): + """Moduł injectora dla modułu klienta.""" + + @injector.provider + def bik_verification_service(self) -> BIKVerificationService: + return BIKVerificationService("http://localhost") + + @injector.provider + def customer_verifier( + self, + bik_verification_service: BIKVerificationService, + verifications: Set[Verification], + repo: VerificationRepository, + fraud_alert_task: FraudAlertTask, + ) -> CustomerVerifier: + return CustomerVerifier( + bik_verification_service, verifications, repo, fraud_alert_task + ) diff --git a/06-automation/06-03-sca/smarttesting/verifier/customer/verification/__init__.py b/06-automation/06-03-sca/smarttesting/verifier/customer/verification/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-03-sca/smarttesting/verifier/customer/verification/age.py b/06-automation/06-03-sca/smarttesting/verifier/customer/verification/age.py new file mode 100644 index 0000000..2bebb13 --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/verifier/customer/verification/age.py @@ -0,0 +1,11 @@ +from smarttesting.customer.person import Person +from smarttesting.verifier.verification import Verification + + +class AgeVerification(Verification): + """Weryfikacja wieku osoby wnioskującej o udzielenie pożyczki.""" + + def passes(self, person: Person) -> bool: + if person.age < 0: + raise ValueError("Age cannot be negative!") + return 18 <= person.age <= 99 diff --git a/06-automation/06-03-sca/smarttesting/verifier/customer/verification/identification_number.py b/06-automation/06-03-sca/smarttesting/verifier/customer/verification/identification_number.py new file mode 100644 index 0000000..194cdc8 --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/verifier/customer/verification/identification_number.py @@ -0,0 +1,45 @@ +from smarttesting.customer.person import Gender, Person +from smarttesting.verifier.verification import Verification + + +class IdentificationNumberVerification(Verification): + """Weryfikacja poprawności numeru PESEL. + + Zobacz: https://pl.wikipedia.org/wiki/PESEL#Cyfra_kontrolna_i_sprawdzanie_poprawno.C5.9Bci_numeru + """ + + def passes(self, person: Person) -> bool: + return ( + self._gender_matches_id_number(person) + and self._starts_with_date_of_birth(person) + and self._weight_is_correct(person) + ) + + def _gender_matches_id_number(self, person: Person) -> bool: + tenth_character = person.national_id_number[9:10] + if int(tenth_character) % 2 == 0: + return person.gender == Gender.FEMALE + else: + return person.gender == Gender.MALE + + def _starts_with_date_of_birth(self, person: Person) -> bool: + dob_formatted = person.date_of_birth.strftime("%y%m%d") + if dob_formatted[0] == "0": + month = person.date_of_birth.month + 20 + dob_formatted = dob_formatted[:2] + str(month) + dob_formatted[4:] + + return dob_formatted == person.national_id_number[:6] + + def _weight_is_correct(self, person: Person) -> bool: + if len(person.national_id_number) != 11: + return False + + weights = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3] + weight_sum = sum( + int(person.national_id_number[index]) * weights[index] + for index in range(10) + ) + actual_sum = (10 - weight_sum % 10) % 10 + + check_sum = int(person.national_id_number[10]) + return actual_sum == check_sum diff --git a/06-automation/06-03-sca/smarttesting/verifier/customer/verification/module.py b/06-automation/06-03-sca/smarttesting/verifier/customer/verification/module.py new file mode 100644 index 0000000..1775bcf --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/verifier/customer/verification/module.py @@ -0,0 +1,24 @@ +from typing import Set + +import injector +from smarttesting.verifier.customer.verification.age import AgeVerification +from smarttesting.verifier.customer.verification.identification_number import ( + IdentificationNumberVerification, +) +from smarttesting.verifier.verification import Verification + + +class VerificationModule(injector.Module): + @injector.provider + def age(self) -> AgeVerification: + return AgeVerification() + + @injector.provider + def id_number(self) -> IdentificationNumberVerification: + return IdentificationNumberVerification() + + @injector.provider + def verifications( + self, age: AgeVerification, id_number: IdentificationNumberVerification + ) -> Set[Verification]: + return {age, id_number} diff --git a/06-automation/06-03-sca/smarttesting/verifier/customer/verification_repository.py b/06-automation/06-03-sca/smarttesting/verifier/customer/verification_repository.py new file mode 100644 index 0000000..17060de --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/verifier/customer/verification_repository.py @@ -0,0 +1,15 @@ +import abc +from typing import Optional +from uuid import UUID + +from smarttesting.verifier.customer.verified_person_dto import VerifiedPersonDto + + +class VerificationRepository(abc.ABC): + @abc.abstractmethod + def find_by_user_id(self, user_id: UUID) -> Optional[VerifiedPersonDto]: + pass + + @abc.abstractmethod + def save(self, verified_person: VerifiedPersonDto) -> None: + pass diff --git a/06-automation/06-03-sca/smarttesting/verifier/customer/verified_person.py b/06-automation/06-03-sca/smarttesting/verifier/customer/verified_person.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-03-sca/smarttesting/verifier/customer/verified_person_dto.py b/06-automation/06-03-sca/smarttesting/verifier/customer/verified_person_dto.py new file mode 100644 index 0000000..936d466 --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/verifier/customer/verified_person_dto.py @@ -0,0 +1,11 @@ +from dataclasses import dataclass +from uuid import UUID + +from smarttesting.verifier.customer.customer_verification_result import Status + + +@dataclass +class VerifiedPersonDto: + uuid: UUID + national_identification_number: str + status: Status diff --git a/06-automation/06-03-sca/smarttesting/verifier/verification.py b/06-automation/06-03-sca/smarttesting/verifier/verification.py new file mode 100644 index 0000000..fb44e92 --- /dev/null +++ b/06-automation/06-03-sca/smarttesting/verifier/verification.py @@ -0,0 +1,9 @@ +import abc + +from smarttesting.customer.person import Person + + +class Verification(abc.ABC): + @abc.abstractmethod + def passes(self, person: Person) -> bool: + pass diff --git a/06-automation/06-03-sca/tests/__init__.py b/06-automation/06-03-sca/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-03-sca/tests/module_a/__init__.py b/06-automation/06-03-sca/tests/module_a/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-03-sca/tests/module_a/test_popo.py b/06-automation/06-03-sca/tests/module_a/test_popo.py new file mode 100644 index 0000000..49b4220 --- /dev/null +++ b/06-automation/06-03-sca/tests/module_a/test_popo.py @@ -0,0 +1,18 @@ +from smarttesting.module_a.my_popo import MyPopo + + +def test_setters_and_getters(): + """Test pokazujący testowanie getterów i setterów. + + Jeśli w tych metodach nie ma specjalnej logiki najlepiej nie pisać takich testów. + Coverage i tak będzie zapewniony dzięki testom wyżej-poziomowym klas/funkcji, które + używają tej struktury danych. + """ + popo = MyPopo() + popo.set_name("Name") + popo.set_surname("Surname") + popo.age = 10 + + assert popo.get_name() == "Name" + assert popo.get_surname() == "Surname" + assert popo.age == 10 diff --git a/06-automation/06-03-sca/tests/verifier/__init__.py b/06-automation/06-03-sca/tests/verifier/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-03-sca/tests/verifier/customer/__init__.py b/06-automation/06-03-sca/tests/verifier/customer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/06-automation/06-03-sca/tests/verifier/customer/bik_verification_service_tests.py b/06-automation/06-03-sca/tests/verifier/customer/bik_verification_service_tests.py new file mode 100644 index 0000000..990c236 --- /dev/null +++ b/06-automation/06-03-sca/tests/verifier/customer/bik_verification_service_tests.py @@ -0,0 +1,46 @@ +import uuid +from datetime import date +from unittest.mock import Mock, patch + +import pytest +from smarttesting.customer.customer import Customer +from smarttesting.customer.person import Gender, Person +from smarttesting.verifier.customer.bik_verification_service import ( + BIKVerificationService, +) + + +class TestBIKVerificationService: + @pytest.fixture(autouse=True) + def setup(self) -> None: + self.service = BIKVerificationService("") + + @pytest.fixture() + def customer(self) -> Customer: + return Customer( + _uuid=uuid.uuid4(), + _person=Person( + _name="Pan", + _surname="Fraudowski", + _gender=Gender.MALE, + _date_of_birth=date.today(), + _national_id_number="12345678011", + ), + ) + + def test_should_return_successful_verification(self, customer: Customer) -> None: + result = self.service.verify(customer) + + assert result.passed + + def test_should_return_failed_verification(self, customer: Customer) -> None: + pass_method_mock = Mock(side_effect=ValueError("BOOM!")) + with patch.object(BIKVerificationService, "create_passed", pass_method_mock): + result = self.service.verify(customer) + + assert not result.passed + + def test_not_blow_up_due_to_cyclomatic_complexity(self) -> None: + result = self.service.complex_method(1, 2, 3) + + assert result == 8 diff --git a/06-automation/README.adoc b/06-automation/README.adoc new file mode 100644 index 0000000..eed3550 --- /dev/null +++ b/06-automation/README.adoc @@ -0,0 +1,56 @@ += Automatyzacja Projektu i Metryki Jakości + +== Automatyzacja Projektu + +W lekcji 06-02 pokazujemy różne narzędzia do testowania skryptów (Bats) / budowania aplikacji (pybuilder / invoke). Wybraliśmy te narzędzia jako przykłady: + +* Testowania skryptów (Bats) +* Procesu budowania opartego o konwencje (pybuilder) +* Procesu budowania opartego na dowolności (invoke) + +== Metryki jakości + +Wszystkie prezentowane do tej pory przykłady kodu (także w poprzednich lekcjach) były sprawdzane od początku kilkoma narzędziami do statycznej analizy kodu, to jest: + +- `flake8` (sprawdź plik setup.cfg w głównym katalogu) +- `pylint` (sprawdź .pylintrc w głównym katalogu) + +Przykładowy output dla flake8 (wyłapie np. nieużywane import czy zbyt dużą złożoność cyklomatyczną): + +``` +06-automation/06-03-sca/smarttesting/verifier/customer/bik_verification_service.py:29:5: C901 'BIKVerificationService.complex_method' is too complex (12) +``` + +Przykładowy output dla pylinta (wyłapał mnóstwo różnych celowo umieszczonych problemów z kodem w tym rozdziale): + +``` +************* Module smarttesting.module_c.class_c +06-automation/06-03-sca/smarttesting/module_c/class_c.py:17:32: E0601: Using variable 'ClassA' before assignment (used-before-assignment) +06-automation/06-03-sca/smarttesting/module_c/class_c.py:17:49: E0601: Using variable 'ClassB' before assignment (used-before-assignment) +************* Module smarttesting.module_b.class_b +06-automation/06-03-sca/smarttesting/module_b/class_b.py:17:32: E0601: Using variable 'ClassA' before assignment (used-before-assignment) +************* Module smarttesting.verifier.customer.bik_verification_service +06-automation/06-03-sca/smarttesting/verifier/customer/bik_verification_service.py:21:15: W0703: Catching too general exception Exception (broad-except) +06-automation/06-03-sca/smarttesting/verifier/customer/bik_verification_service.py:29:4: C0103: Argument name "a" doesn't conform to snake_case naming style (invalid-name) +06-automation/06-03-sca/smarttesting/verifier/customer/bik_verification_service.py:29:4: C0103: Argument name "b" doesn't conform to snake_case naming style (invalid-name) +06-automation/06-03-sca/smarttesting/verifier/customer/bik_verification_service.py:29:4: C0103: Argument name "c" doesn't conform to snake_case naming style (invalid-name) +06-automation/06-03-sca/smarttesting/verifier/customer/bik_verification_service.py:34:8: C0103: Variable name "d" doesn't conform to snake_case naming style (invalid-name) +06-automation/06-03-sca/smarttesting/verifier/customer/bik_verification_service.py:35:8: C0103: Variable name "e" doesn't conform to snake_case naming style (invalid-name) +06-automation/06-03-sca/smarttesting/verifier/customer/bik_verification_service.py:36:8: C0103: Variable name "f" doesn't conform to snake_case naming style (invalid-name) +06-automation/06-03-sca/smarttesting/verifier/customer/bik_verification_service.py:29:4: R0911: Too many return statements (10/6) (too-many-return-statements) +06-automation/06-03-sca/smarttesting/verifier/customer/bik_verification_service.py:29:4: R0912: Too many branches (13/12) (too-many-branches) +************* Module smarttesting.module_a.my_popo +06-automation/06-03-sca/smarttesting/module_a/my_popo.py:1:0: R0401: Cyclic import (smarttesting.module_a.class_a -> smarttesting.module_b.class_b) (cyclic-import) +06-automation/06-03-sca/smarttesting/module_a/my_popo.py:1:0: R0401: Cyclic import (smarttesting.module_a.class_a -> smarttesting.module_b.class_b -> smarttesting.module_c.class_c) (cyclic-import) +06-automation/06-03-sca/smarttesting/module_a/my_popo.py:1:0: R0401: Cyclic import (smarttesting.module_a.class_a -> smarttesting.module_c.class_c) (cyclic-import) +06-automation/06-03-sca/smarttesting/module_a/my_popo.py:1:0: R0401: Cyclic import (smarttesting.module_b.class_b -> smarttesting.module_c.class_c) (cyclic-import) +``` + +Do weryfikacji adnotacji typów (jednak bez ich bezwzględnego wymuszania wszędzie): + +- `mypy` (używamy ustawień domyślnych z opcją --ignore-missing-imports) + +Dodatkowo projekt używa formaterów: + +- `isort` (sprawdź sekcję `[tool.isort]` w pliku pyproject.toml z głównego katalogu) +- `black` z domyślną długością linii 88.