From 71aabc94ff386b7e8cfcf24252ca84909b91738a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Buczyn=CC=81ski?= Date: Mon, 25 Sep 2023 17:36:05 +0200 Subject: [PATCH] Week 6 --- 06-automation/06-02-tools-bats/.ecrc | 18 + 06-automation/06-02-tools-bats/.editorconfig | 11 + 06-automation/06-02-tools-bats/.gitignore | 1 + 06-automation/06-02-tools-bats/Makefile | 53 ++ 06-automation/06-02-tools-bats/README.adoc | 17 + .../06-02-tools-bats/src/main/bash/script.sh | 27 + .../src/main/bash/zsdoc/data/bodies/script.sh | 9 + .../bash/zsdoc/data/bodies/script.sh.comments | 16 + .../src/main/bash/zsdoc/data/call_tree.zsd | 1 + .../zsdoc/data/descriptions/script.sh/request | 1 + .../request/script.sh/Script_Body_/CURL_BIN | 0 .../request/script.sh/Script_Body_/URL | 0 .../script.sh/Script_Body_/CURL_BIN | 0 .../script.sh/Script_Body_/URL | 0 .../exports/script.sh/Script_Body_/CURL_BIN | 1 + .../data/exports/script.sh/Script_Body_/URL | 1 + .../main/bash/zsdoc/data/extended/script.sh | 27 + .../features/script.sh/Script_Body_/export | 1 + .../zsdoc/data/functions/script.sh/request | 1 + .../main/bash/zsdoc/data/rev_call_tree.zsd | 1 + .../data/trees/script.sh/Script_Body_.tree | 4 + .../src/main/bash/zsdoc/script.sh.adoc | 49 ++ .../src/test/bats/script.bats | 51 ++ .../src/test/bats/test_helper.bash | 6 + .../bats/test_helper/bats-assert/.travis.yml | 8 + .../bats/test_helper/bats-assert/CHANGELOG.md | 39 + .../test/bats/test_helper/bats-assert/LICENSE | 116 +++ .../bats/test_helper/bats-assert/README.md | 656 ++++++++++++++++ .../bats/test_helper/bats-assert/load.bash | 1 + .../bats/test_helper/bats-assert/package.json | 8 + .../bats-assert/script/install-bats.sh | 6 + .../test_helper/bats-assert/src/assert.bash | 720 ++++++++++++++++++ .../bats-assert/test/50-assert-11-assert.bats | 18 + .../test/50-assert-12-assert_equal.bats | 50 ++ .../test/50-assert-13-assert_success.bats | 36 + .../test/50-assert-14-assert_failure.bats | 69 ++ .../test/50-assert-15-assert_output.bats | 242 ++++++ .../test/50-assert-16-refute_output.bats | 196 +++++ .../test/50-assert-17-assert_line.bats | 334 ++++++++ .../test/50-assert-18-refute_line.bats | 342 +++++++++ .../bats-assert/test/50-assert-19-refute.bats | 18 + .../bats-assert/test/test_helper.bash | 10 + .../bats/test_helper/bats-support/.travis.yml | 7 + .../test_helper/bats-support/CHANGELOG.md | 46 ++ .../bats/test_helper/bats-support/LICENSE | 116 +++ .../bats/test_helper/bats-support/README.md | 189 +++++ .../bats/test_helper/bats-support/load.bash | 3 + .../test_helper/bats-support/package.json | 5 + .../bats-support/script/install-bats.sh | 6 + .../test_helper/bats-support/src/error.bash | 41 + .../test_helper/bats-support/src/lang.bash | 73 ++ .../test_helper/bats-support/src/output.bash | 279 +++++++ .../test/50-output-10-batslib_err.bats | 16 + .../50-output-11-batslib_count_lines.bats | 21 + .../50-output-12-batslib_is_single_line.bats | 13 + ...batslib_get_max_single_line_key_width.bats | 21 + .../50-output-14-batslib_print_kv_single.bats | 27 + .../50-output-15-batslib_print_kv_multi.bats | 19 + ...t-16-batslib_print_kv_single_or_multi.bats | 31 + .../test/50-output-17-batslib_prefix.bats | 43 ++ .../test/50-output-18-batslib_mark.bats | 72 ++ .../test/50-output-19-batslib_decorate.bats | 13 + .../bats-support/test/51-error-10-fail.bats | 16 + .../test/52-lang-10-batslib_is_caller.bats | 88 +++ .../bats-support/test/test_helper.bash | 6 + .../test/docs_helper/zshelldoc/DONATIONS.md | 441 +++++++++++ .../src/test/docs_helper/zshelldoc/LICENSE | 700 +++++++++++++++++ .../src/test/docs_helper/zshelldoc/Makefile | 85 +++ .../src/test/docs_helper/zshelldoc/NEWS | 0 .../src/test/docs_helper/zshelldoc/README.md | 122 +++ .../examples/.inputs/zsh-autosuggestions.zsh | 693 +++++++++++++++++ .../.inputs/zsh-syntax-highlighting.zsh | 419 ++++++++++ .../examples/zsh-autosuggestions.zsh.adoc | 524 +++++++++++++ .../examples/zsh-autosuggestions.zsh.pdf | Bin 0 -> 140775 bytes .../examples/zsh-syntax-highlighting.zsh.adoc | 203 +++++ .../examples/zsh-syntax-highlighting.zsh.pdf | Bin 0 -> 72152 bytes .../zshelldoc/src/run-tree-convert.mod | 33 + .../docs_helper/zshelldoc/src/token-types.mod | 52 ++ .../docs_helper/zshelldoc/src/zsd-detect.main | 535 +++++++++++++ .../zshelldoc/src/zsd-detect.preamble | 69 ++ .../zshelldoc/src/zsd-process-buffer | 108 +++ .../zshelldoc/src/zsd-to-adoc.main | 352 +++++++++ .../zshelldoc/src/zsd-to-adoc.preamble | 17 + .../zshelldoc/src/zsd-transform.main | 303 ++++++++ .../zshelldoc/src/zsd-transform.preamble | 30 + .../docs_helper/zshelldoc/src/zsd-trim-indent | 35 + .../test/docs_helper/zshelldoc/src/zsd.main | 170 +++++ .../docs_helper/zshelldoc/src/zsd.preamble | 90 +++ .../test/docs_helper/zshelldoc/test/Makefile | 62 ++ .../test/cross-calls/cross-calls1.zsh.adoc | 48 ++ .../test/cross-calls/cross-calls2.zsh.adoc | 48 ++ .../cross-calls/data/bodies/cross-calls1.zsh | 4 + .../data/bodies/cross-calls1.zsh.comments | 1 + .../cross-calls/data/bodies/cross-calls2.zsh | 4 + .../data/bodies/cross-calls2.zsh.comments | 1 + .../test/cross-calls/data/call_tree.zsd | 1 + .../data/extended/cross-calls1.zsh | 10 + .../data/extended/cross-calls2.zsh | 10 + .../data/functions/cross-calls1.zsh/myfun1 | 1 + .../data/functions/cross-calls1.zsh/myfun2 | 1 + .../data/functions/cross-calls2.zsh/myfun1 | 1 + .../data/functions/cross-calls2.zsh/myfun3 | 1 + .../test/cross-calls/data/rev_call_tree.zsd | 2 + .../trees/cross-calls1.zsh/Script_Body_.tree | 5 + .../trees/cross-calls2.zsh/Script_Body_.tree | 5 + .../zshelldoc/test/cross-calls1.zsh | 10 + .../zshelldoc/test/cross-calls2.zsh | 10 + .../docs_helper/zshelldoc/test/nested.zsh | 37 + .../test/nested/data/bodies/nested.zsh | 6 + .../nested/data/bodies/nested.zsh.comments | 10 + .../zshelldoc/test/nested/data/call_tree.zsd | 6 + .../nested/data/descriptions/nested.zsh/dummy | 1 + .../nested/data/descriptions/nested.zsh/test1 | 1 + .../nested/data/descriptions/nested.zsh/test2 | 1 + .../nested/data/descriptions/nested.zsh/test3 | 1 + .../nested/data/descriptions/nested.zsh/test4 | 1 + .../nested/data/descriptions/nested.zsh/test5 | 1 + .../nested/data/descriptions/nested.zsh/test6 | 1 + .../test/nested/data/extended/nested.zsh | 37 + .../nested/data/functions/nested.zsh/dummy | 1 + .../nested/data/functions/nested.zsh/test1 | 1 + .../nested/data/functions/nested.zsh/test2 | 2 + .../nested/data/functions/nested.zsh/test3 | 1 + .../nested/data/functions/nested.zsh/test4 | 1 + .../nested/data/functions/nested.zsh/test5 | 8 + .../nested/data/functions/nested.zsh/test6 | 2 + .../test/nested/data/rev_call_tree.zsd | 6 + .../nested/data/trees/nested.zsh/test1.tree | 11 + .../nested/data/trees/nested.zsh/test2.tree | 8 + .../nested/data/trees/nested.zsh/test3.tree | 7 + .../nested/data/trees/nested.zsh/test4.tree | 6 + .../nested/data/trees/nested.zsh/test5.tree | 5 + .../nested/data/trees/nested.zsh/test6.tree | 4 + .../zshelldoc/test/nested/nested.zsh.adoc | 156 ++++ .../docs_helper/zshelldoc/test/real-zsyh.zsh | 419 ++++++++++ .../data/autoload/real-zsyh.zsh/add-zsh-hook | 93 +++ .../data/autoload/real-zsyh.zsh/is-at-least | 56 ++ .../test/real-zsyh/data/bodies/real-zsyh.zsh | 36 + .../data/bodies/real-zsyh.zsh.comments | 117 +++ .../test/real-zsyh/data/call_tree.zsd | 3 + .../descriptions/real-zsyh.zsh/_zsh_highlight | 4 + .../_zsh_highlight_add_highlight | 4 + .../_zsh_highlight_apply_zle_highlight | 7 + .../real-zsyh.zsh/_zsh_highlight_bind_widgets | 1 + .../_zsh_highlight_buffer_modified | 3 + .../real-zsyh.zsh/_zsh_highlight_call_widget | 2 + .../real-zsyh.zsh/_zsh_highlight_cursor_moved | 3 + .../_zsh_highlight_load_highlighters | 4 + .../real-zsyh.zsh/_zsh_highlight_preexec_hook | 1 + .../real-zsyh/data/extended/real-zsyh.zsh | 574 ++++++++++++++ .../real-zsyh.zsh/Script_Body_/autoload | 1 + .../features/real-zsyh.zsh/Script_Body_/eval | 1 + .../real-zsyh.zsh/Script_Body_/unalias | 1 + .../real-zsyh.zsh/Script_Body_/zmodload | 1 + .../real-zsyh.zsh/_zsh_highlight/eval | 1 + .../real-zsyh.zsh/_zsh_highlight/type | 1 + .../_zsh_highlight_bind_widgets/eval | 1 + .../_zsh_highlight_bind_widgets/zle | 1 + .../_zsh_highlight_bind_widgets/zmodload | 1 + .../_zsh_highlight_call_widget/zle | 1 + .../_zsh_highlight_load_highlighters/eval | 1 + .../_zsh_highlight_load_highlighters/type | 1 + .../real-zsyh.zsh/add-zsh-hook/autoload | 1 + .../real-zsyh.zsh/add-zsh-hook/getopts | 1 + .../functions/real-zsyh.zsh/_zsh_highlight | 65 ++ .../_zsh_highlight_add_highlight | 11 + .../_zsh_highlight_apply_zle_highlight | 20 + .../real-zsyh.zsh/_zsh_highlight_bind_widgets | 34 + .../_zsh_highlight_buffer_modified | 1 + .../real-zsyh.zsh/_zsh_highlight_call_widget | 2 + .../real-zsyh.zsh/_zsh_highlight_cursor_moved | 1 + .../_zsh_highlight_load_highlighters | 25 + .../real-zsyh.zsh/_zsh_highlight_preexec_hook | 2 + .../real-zsyh.zsh/_zsh_highlight_preexec_hook | 1 + .../test/real-zsyh/data/rev_call_tree.zsd | 6 + .../trees/real-zsyh.zsh/Script_Body_.tree | 7 + .../trees/real-zsyh.zsh/_zsh_highlight.tree | 4 + .../_zsh_highlight_call_widget.tree | 5 + .../test/real-zsyh/real-zsyh.zsh.adoc | 205 +++++ .../src/test/docs_helper/zshelldoc/zsd.config | 1 + .../06-02-tools-bats/tools/build-helper.sh | 119 +++ 06-automation/06-02-tools-invoke/README.adoc | 7 + .../smarttesting/__init__.py | 0 06-automation/06-02-tools-invoke/tasks.py | 18 + .../06-02-tools-invoke/tests/__init__.py | 0 .../06-02-tools-invoke/tests/passing_test.py | 2 + .../06-02-tools-pybuilder/.gitignore | 2 + .../06-02-tools-pybuilder/README.adoc | 13 + 06-automation/06-02-tools-pybuilder/build.py | 17 + .../06-02-tools-pybuilder/pyproject.toml | 3 + 06-automation/06-02-tools-pybuilder/setup.py | 91 +++ .../smarttesting_pyb_demo/docs/.gitkeep | 0 .../smarttesting_pyb_demo/scripts/__init__.py | 0 .../smarttesting/__init__.py | 0 .../smarttesting/hello_world.py | 10 + .../smarttesting_pyb_demo/tests/__init__.py | 0 .../tests/hello_world_tests.py | 13 + .../scripts/__init__.py | 0 .../smarttesting/__init__.py | 0 .../smarttesting/hello_world.py | 10 + .../tests/__init__.py | 0 .../tests/hello_world_tests.py | 13 + ...ld_tests.TestHelloWorld-20230915210537.xml | 4 + .../target/reports/unittest | 10 + .../target/reports/unittest.json | 5 + 06-automation/06-03-sca/README.adoc | 6 + .../06-03-sca/smarttesting/__init__.py | 0 .../smarttesting/customer/__init__.py | 0 .../smarttesting/customer/customer.py | 28 + .../06-03-sca/smarttesting/customer/person.py | 64 ++ .../06-03-sca/smarttesting/message.py | 17 + .../smarttesting/module_a/__init__.py | 0 .../smarttesting/module_a/class_a.py | 14 + .../smarttesting/module_a/my_popo.py | 50 ++ .../smarttesting/module_b/__init__.py | 0 .../smarttesting/module_b/class_b.py | 19 + .../smarttesting/module_b/my_popo.py | 15 + .../smarttesting/module_c/__init__.py | 0 .../smarttesting/module_c/class_c.py | 19 + .../06-03-sca/smarttesting/serialization.py | 53 ++ .../smarttesting/verifier/__init__.py | 0 .../verifier/customer/__init__.py | 0 .../customer/bik_verification_service.py | 61 ++ .../customer/customer_verification.py | 18 + .../customer/customer_verification_result.py | 36 + .../verifier/customer/customer_verifier.py | 80 ++ .../verifier/customer/fraud_alert_task.py | 17 + .../customer/fraud_detected_handler.py | 26 + .../smarttesting/verifier/customer/module.py | 34 + .../customer/verification/__init__.py | 0 .../verifier/customer/verification/age.py | 11 + .../verification/identification_number.py | 45 ++ .../verifier/customer/verification/module.py | 24 + .../customer/verification_repository.py | 15 + .../verifier/customer/verified_person.py | 0 .../verifier/customer/verified_person_dto.py | 11 + .../smarttesting/verifier/verification.py | 9 + 06-automation/06-03-sca/tests/__init__.py | 0 .../06-03-sca/tests/module_a/__init__.py | 0 .../06-03-sca/tests/module_a/test_popo.py | 18 + .../06-03-sca/tests/verifier/__init__.py | 0 .../tests/verifier/customer/__init__.py | 0 .../bik_verification_service_tests.py | 46 ++ 06-automation/README.adoc | 56 ++ 244 files changed, 12704 insertions(+) create mode 100644 06-automation/06-02-tools-bats/.ecrc create mode 100644 06-automation/06-02-tools-bats/.editorconfig create mode 100644 06-automation/06-02-tools-bats/.gitignore create mode 100644 06-automation/06-02-tools-bats/Makefile create mode 100644 06-automation/06-02-tools-bats/README.adoc create mode 100755 06-automation/06-02-tools-bats/src/main/bash/script.sh create mode 100644 06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/bodies/script.sh create mode 100644 06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/bodies/script.sh.comments create mode 100644 06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/call_tree.zsd create mode 100644 06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/descriptions/script.sh/request create mode 100644 06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/env-use/script.sh/request/script.sh/Script_Body_/CURL_BIN create mode 100644 06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/env-use/script.sh/request/script.sh/Script_Body_/URL create mode 100644 06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/env-use/script.sh/zsd_script_body/script.sh/Script_Body_/CURL_BIN create mode 100644 06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/env-use/script.sh/zsd_script_body/script.sh/Script_Body_/URL create mode 100644 06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/exports/script.sh/Script_Body_/CURL_BIN create mode 100644 06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/exports/script.sh/Script_Body_/URL create mode 100644 06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/extended/script.sh create mode 100644 06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/features/script.sh/Script_Body_/export create mode 100644 06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/functions/script.sh/request create mode 100644 06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/rev_call_tree.zsd create mode 100644 06-automation/06-02-tools-bats/src/main/bash/zsdoc/data/trees/script.sh/Script_Body_.tree create mode 100644 06-automation/06-02-tools-bats/src/main/bash/zsdoc/script.sh.adoc create mode 100644 06-automation/06-02-tools-bats/src/test/bats/script.bats create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper.bash create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/.travis.yml create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/CHANGELOG.md create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/LICENSE create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/README.md create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/load.bash create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/package.json create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/script/install-bats.sh create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/src/assert.bash create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-11-assert.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-12-assert_equal.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-13-assert_success.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-14-assert_failure.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-15-assert_output.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-16-refute_output.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-17-assert_line.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-18-refute_line.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/50-assert-19-refute.bats create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-assert/test/test_helper.bash create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/.travis.yml create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/CHANGELOG.md create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/LICENSE create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/README.md create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/load.bash create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/package.json create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/script/install-bats.sh create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/src/error.bash create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/src/lang.bash create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/src/output.bash create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-10-batslib_err.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-11-batslib_count_lines.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-12-batslib_is_single_line.bats create mode 100755 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 create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-14-batslib_print_kv_single.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-15-batslib_print_kv_multi.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-16-batslib_print_kv_single_or_multi.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-17-batslib_prefix.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-18-batslib_mark.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/50-output-19-batslib_decorate.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/51-error-10-fail.bats create mode 100755 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/52-lang-10-batslib_is_caller.bats create mode 100644 06-automation/06-02-tools-bats/src/test/bats/test_helper/bats-support/test/test_helper.bash create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/DONATIONS.md create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/LICENSE create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/Makefile create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/NEWS create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/README.md create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/.inputs/zsh-autosuggestions.zsh create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/.inputs/zsh-syntax-highlighting.zsh create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/zsh-autosuggestions.zsh.adoc create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/zsh-autosuggestions.zsh.pdf create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/zsh-syntax-highlighting.zsh.adoc create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/examples/zsh-syntax-highlighting.zsh.pdf create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/run-tree-convert.mod create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/token-types.mod create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-detect.main create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-detect.preamble create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-process-buffer create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-to-adoc.main create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-to-adoc.preamble create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-transform.main create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-transform.preamble create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd-trim-indent create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd.main create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/src/zsd.preamble create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/Makefile create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/cross-calls1.zsh.adoc create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/cross-calls2.zsh.adoc create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/bodies/cross-calls1.zsh create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/bodies/cross-calls1.zsh.comments create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/bodies/cross-calls2.zsh create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/bodies/cross-calls2.zsh.comments create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/call_tree.zsd create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/extended/cross-calls1.zsh create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/extended/cross-calls2.zsh create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/functions/cross-calls1.zsh/myfun1 create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/functions/cross-calls1.zsh/myfun2 create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/functions/cross-calls2.zsh/myfun1 create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/functions/cross-calls2.zsh/myfun3 create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/rev_call_tree.zsd create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/trees/cross-calls1.zsh/Script_Body_.tree create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls/data/trees/cross-calls2.zsh/Script_Body_.tree create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls1.zsh create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/cross-calls2.zsh create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested.zsh create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/bodies/nested.zsh create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/bodies/nested.zsh.comments create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/call_tree.zsd create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/dummy create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test1 create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test2 create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test3 create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test4 create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test5 create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/descriptions/nested.zsh/test6 create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/extended/nested.zsh create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/dummy create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test1 create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test2 create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test3 create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test4 create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test5 create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/functions/nested.zsh/test6 create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/rev_call_tree.zsd create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test1.tree create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test2.tree create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test3.tree create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test4.tree create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test5.tree create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/data/trees/nested.zsh/test6.tree create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/nested/nested.zsh.adoc create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh.zsh create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/autoload/real-zsyh.zsh/add-zsh-hook create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/autoload/real-zsyh.zsh/is-at-least create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/bodies/real-zsyh.zsh create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/bodies/real-zsyh.zsh.comments create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/call_tree.zsd create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_add_highlight create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_apply_zle_highlight create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_bind_widgets create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_buffer_modified create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_call_widget create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_cursor_moved create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_load_highlighters create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/descriptions/real-zsyh.zsh/_zsh_highlight_preexec_hook create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/extended/real-zsyh.zsh create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/Script_Body_/autoload create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/Script_Body_/eval create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/Script_Body_/unalias create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/Script_Body_/zmodload create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight/eval create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight/type create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_bind_widgets/eval create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_bind_widgets/zle create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_bind_widgets/zmodload create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_call_widget/zle create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_load_highlighters/eval create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/_zsh_highlight_load_highlighters/type create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/add-zsh-hook/autoload create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/features/real-zsyh.zsh/add-zsh-hook/getopts create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_add_highlight create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_apply_zle_highlight create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_bind_widgets create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_buffer_modified create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_call_widget create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_cursor_moved create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_load_highlighters create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/functions/real-zsyh.zsh/_zsh_highlight_preexec_hook create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/hooks/real-zsyh.zsh/_zsh_highlight_preexec_hook create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/rev_call_tree.zsd create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/trees/real-zsyh.zsh/Script_Body_.tree create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/trees/real-zsyh.zsh/_zsh_highlight.tree create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/data/trees/real-zsyh.zsh/_zsh_highlight_call_widget.tree create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/test/real-zsyh/real-zsyh.zsh.adoc create mode 100644 06-automation/06-02-tools-bats/src/test/docs_helper/zshelldoc/zsd.config create mode 100755 06-automation/06-02-tools-bats/tools/build-helper.sh create mode 100644 06-automation/06-02-tools-invoke/README.adoc create mode 100644 06-automation/06-02-tools-invoke/smarttesting/__init__.py create mode 100644 06-automation/06-02-tools-invoke/tasks.py create mode 100644 06-automation/06-02-tools-invoke/tests/__init__.py create mode 100644 06-automation/06-02-tools-invoke/tests/passing_test.py create mode 100644 06-automation/06-02-tools-pybuilder/.gitignore create mode 100644 06-automation/06-02-tools-pybuilder/README.adoc create mode 100644 06-automation/06-02-tools-pybuilder/build.py create mode 100644 06-automation/06-02-tools-pybuilder/pyproject.toml create mode 100644 06-automation/06-02-tools-pybuilder/setup.py create mode 100644 06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/docs/.gitkeep create mode 100644 06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/scripts/__init__.py create mode 100644 06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/smarttesting/__init__.py create mode 100644 06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/smarttesting/hello_world.py create mode 100644 06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/tests/__init__.py create mode 100644 06-automation/06-02-tools-pybuilder/smarttesting_pyb_demo/tests/hello_world_tests.py create mode 100644 06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/scripts/__init__.py create mode 100644 06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/smarttesting/__init__.py create mode 100644 06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/smarttesting/hello_world.py create mode 100644 06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/tests/__init__.py create mode 100644 06-automation/06-02-tools-pybuilder/target/dist/smarttesting_pyb_demo-1.0.dev0/tests/hello_world_tests.py create mode 100644 06-automation/06-02-tools-pybuilder/target/reports/TEST-hello_world_tests.TestHelloWorld-20230915210537.xml create mode 100644 06-automation/06-02-tools-pybuilder/target/reports/unittest create mode 100644 06-automation/06-02-tools-pybuilder/target/reports/unittest.json create mode 100644 06-automation/06-03-sca/README.adoc create mode 100644 06-automation/06-03-sca/smarttesting/__init__.py create mode 100644 06-automation/06-03-sca/smarttesting/customer/__init__.py create mode 100644 06-automation/06-03-sca/smarttesting/customer/customer.py create mode 100644 06-automation/06-03-sca/smarttesting/customer/person.py create mode 100644 06-automation/06-03-sca/smarttesting/message.py create mode 100644 06-automation/06-03-sca/smarttesting/module_a/__init__.py create mode 100644 06-automation/06-03-sca/smarttesting/module_a/class_a.py create mode 100644 06-automation/06-03-sca/smarttesting/module_a/my_popo.py create mode 100644 06-automation/06-03-sca/smarttesting/module_b/__init__.py create mode 100644 06-automation/06-03-sca/smarttesting/module_b/class_b.py create mode 100644 06-automation/06-03-sca/smarttesting/module_b/my_popo.py create mode 100644 06-automation/06-03-sca/smarttesting/module_c/__init__.py create mode 100644 06-automation/06-03-sca/smarttesting/module_c/class_c.py create mode 100644 06-automation/06-03-sca/smarttesting/serialization.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/__init__.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/customer/__init__.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/customer/bik_verification_service.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/customer/customer_verification.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/customer/customer_verification_result.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/customer/customer_verifier.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/customer/fraud_alert_task.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/customer/fraud_detected_handler.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/customer/module.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/customer/verification/__init__.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/customer/verification/age.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/customer/verification/identification_number.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/customer/verification/module.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/customer/verification_repository.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/customer/verified_person.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/customer/verified_person_dto.py create mode 100644 06-automation/06-03-sca/smarttesting/verifier/verification.py create mode 100644 06-automation/06-03-sca/tests/__init__.py create mode 100644 06-automation/06-03-sca/tests/module_a/__init__.py create mode 100644 06-automation/06-03-sca/tests/module_a/test_popo.py create mode 100644 06-automation/06-03-sca/tests/verifier/__init__.py create mode 100644 06-automation/06-03-sca/tests/verifier/customer/__init__.py create mode 100644 06-automation/06-03-sca/tests/verifier/customer/bik_verification_service_tests.py create mode 100644 06-automation/README.adoc 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 0000000000000000000000000000000000000000..e0132f4a0661de9c5094becec03f95f7635e827d GIT binary patch literal 140775 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%0D;=B|Jni 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..28e8e005fdaee3e4cd91744d1cc29a8d9dd35a6b GIT binary patch literal 72152 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%0A7G-~IREnht@ynz5;_h$Pk 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 literal 0 HcmV?d00001 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.