From c962e32912723f715f425367a592dc1dbfa0bfa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chastanet?= Date: Mon, 11 Mar 2024 23:47:04 +0100 Subject: [PATCH] Command enhancement Facade calls function on invalid action command's short/long description overridable by callback function tag: 2.3.2 # Breaking changes # Bug fixes - Ability to use a function to override command short/long description # Compiler changes N/A # Binaries changes - Facade call defaultFacadeAction (if exists) if invalid action requested # Updated Bash framework functions N/A # New Bash framework functions N/A # Documentation N/A # Validation/Tooling - upgraded megalinter to version 3.10.0 - pre-commit go back from prettier v4.0.0-alpha.8 to v4.0.0-alpha.4 because of the error "No files matching the given patterns were found" --- .pre-commit-config.yaml | 6 +- .pre-commit-hooks.yaml | 6 +- bin/installFacadeExample | 15 +++- bin/megalinter | 12 ++-- .../Facade/generateFacadeChoiceScript.sh | 9 ++- ...entFunctionFound.choiceScript.expected.txt | 8 ++- ...eScript.oneImplementDirective.expected.txt | 8 ++- ...eScript.twoImplementDirective.expected.txt | 8 ++- src/Options/generateCommand.bats | 36 ++++++++++ src/Options/templates/command.help.tpl | 26 ++++--- .../generateCommand.case11.expected.help | 8 +++ .../testsData/generateCommand.case11.sh | 69 +++++++++++++++++++ src/_binaries/options/command.megalinter.tpl | 2 +- 13 files changed, 181 insertions(+), 32 deletions(-) create mode 100644 src/Options/testsData/generateCommand.case11.expected.help create mode 100755 src/Options/testsData/generateCommand.case11.sh diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 91e1c225..7468126a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -47,7 +47,7 @@ repos: ) - repo: https://github.com/pre-commit/mirrors-prettier - rev: v4.0.0-alpha.8 + rev: v4.0.0-alpha.4 hooks: - id: prettier exclude: | @@ -57,7 +57,7 @@ repos: ) - repo: https://github.com/fchastanet/bash-tools-framework - rev: 2.3.1 + rev: 2.3.2 hooks: - id: fixShebangExecutionBit - id: fixShebangExecutionBitGithubActions @@ -128,7 +128,7 @@ repos: stages: [manual] - repo: https://github.com/fchastanet/bash-tools-framework - rev: 2.3.1 + rev: 2.3.2 hooks: - id: megalinterCheckVersion - id: megalinterGithubAction diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index ca063468..3a9b539a 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -127,7 +127,7 @@ args: [ --image, - 'oxsecurity/megalinter-terraform:v7.9.0', + 'oxsecurity/megalinter-terraform:v7.10.0', --check-megalinter-version, ] pass_filenames: false @@ -144,7 +144,7 @@ args: [ --image, - 'oxsecurity/megalinter-terraform:v7.9.0', + 'oxsecurity/megalinter-terraform:v7.10.0', --config-file, '.mega-linter-light.yml', --fix, @@ -163,7 +163,7 @@ args: [ --image, - 'oxsecurity/megalinter-terraform:v7.9.0', + 'oxsecurity/megalinter-terraform:v7.10.0', --config-file, '.mega-linter-githubAction.yml', --fix, diff --git a/bin/installFacadeExample b/bin/installFacadeExample index 7d1a3361..b4316551 100755 --- a/bin/installFacadeExample +++ b/bin/installFacadeExample @@ -100,6 +100,13 @@ cleanOnExit() { } trap cleanOnExit EXIT HUP QUIT ABRT TERM +# @description checks if function name provided exists +# @arg $1 functionName:String +# @exitcode 1 if function name doesn't exist +Assert::functionExists() { + declare -F "$1" >/dev/null +} + # @description Log namespace provides 2 kind of functions # - Log::display* allows to display given message with # given display level @@ -490,8 +497,12 @@ case ${action} in install "$@" ;; *) - Log::displayError "invalid action requested: ${action}" - exit 1 + if Assert::functionExists defaultFacadeAction; then + defaultFacadeAction "$1" "$@" + else + Log::displayError "invalid action requested: ${action}" + exit 1 + fi ;; esac exit 0 diff --git a/bin/megalinter b/bin/megalinter index 6d56f03b..b0b0cbe9 100755 --- a/bin/megalinter +++ b/bin/megalinter @@ -1076,7 +1076,7 @@ megalinterCommand() { optionIncremental="0" local -i options_parse_optionParsedCountOptionIncremental ((options_parse_optionParsedCountOptionIncremental = 0)) || true - optionMegalinterImage="oxsecurity/megalinter-terraform:v7.9.0" + optionMegalinterImage="oxsecurity/megalinter-terraform:v7.10.0" local -i options_parse_optionParsedCountOptionMegalinterImage ((options_parse_optionParsedCountOptionMegalinterImage = 0)) || true optionCheckMegalinterVersion="0" @@ -1493,11 +1493,11 @@ megalinterCommand() { # shellcheck disable=SC2054 helpArray=(Specify\ docker\ megalinter\ image\ name\ to\ use) echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" - echo ' Default value: oxsecurity/megalinter-terraform:v7.9.0' + echo ' Default value: oxsecurity/megalinter-terraform:v7.10.0' echo -e " ${__HELP_OPTION_COLOR}--check-megalinter-version${__HELP_NORMAL} {single}" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Check\ if\ new\ version\ of\ megalinter\ is\ available\ \(compared\ to\ oxsecurity/megalinter-terraform:v7.9.0\)\ and\ exit\ 1\ if\ yes\ and\ display\ new\ version\ number.) + helpArray=(Check\ if\ new\ version\ of\ megalinter\ is\ available\ \(compared\ to\ oxsecurity/megalinter-terraform:v7.10.0\)\ and\ exit\ 1\ if\ yes\ and\ display\ new\ version\ number.) echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--config-file ${__HELP_NORMAL} {single}" local -a helpArray @@ -1582,7 +1582,7 @@ megalinterCommand() { echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" echo ' Possible values: OFF|ERR|ERROR|WARN|WARNING|INFO|DEBUG|TRACE' echo -e """ -megalinter image oxsecurity/megalinter-terraform:v7.9.0 will be used. +megalinter image oxsecurity/megalinter-terraform:v7.10.0 will be used. optionally you can provide a list of files to run megalinter on this mode is incompatible with --incremental option""" @@ -1607,7 +1607,7 @@ megalinter image oxsecurity/megalinter-terraform:v7.9.0 will be used. } declare copyrightBeginYear="2022" declare optionMegalinterConfigFile=".mega-linter.yml" -declare optionMegalinterImage="oxsecurity/megalinter-terraform:v7.9.0" +declare optionMegalinterImage="oxsecurity/megalinter-terraform:v7.10.0" declare -a megalinterArgs=() unknownArg() { if [[ -f "$1" ]]; then @@ -1665,7 +1665,7 @@ optionFixCallback() { optionVersionCallback() { echo -e "${__HELP_TITLE_COLOR}${SCRIPT_NAME} version:${__RESET_COLOR} 1.0" - echo -e "${__HELP_TITLE_COLOR}megalinter image Version:${__RESET_COLOR} oxsecurity/megalinter-terraform:v7.9.0" + echo -e "${__HELP_TITLE_COLOR}megalinter image Version:${__RESET_COLOR} oxsecurity/megalinter-terraform:v7.10.0" exit 0 } diff --git a/src/Compiler/Facade/generateFacadeChoiceScript.sh b/src/Compiler/Facade/generateFacadeChoiceScript.sh index 069583af..0feb5a4b 100755 --- a/src/Compiler/Facade/generateFacadeChoiceScript.sh +++ b/src/Compiler/Facade/generateFacadeChoiceScript.sh @@ -53,8 +53,13 @@ Compiler::Facade::generateFacadeChoiceScript() { printf " ;;\n" done <<<"${interfacesFunctionsStr}" printf " *)\n" - printf $" Log::displayError \"invalid action requested: \${action}\"\n" - printf " exit 1\n" + printf ' if Assert::functionExists defaultFacadeAction; then\n' + # shellcheck disable=SC2016 + printf ' defaultFacadeAction "$1" "$@"\n' + printf ' else\n' + printf $" Log::displayError \"invalid action requested: \${action}\"\n" + printf " exit 1\n" + printf ' fi\n' printf " ;;\n" printf 'esac\n' printf 'exit 0\n' diff --git a/src/Compiler/Facade/testsData/facadeWithImplementImplementFunctionFound.choiceScript.expected.txt b/src/Compiler/Facade/testsData/facadeWithImplementImplementFunctionFound.choiceScript.expected.txt index e63567ad..5f8f53a9 100644 --- a/src/Compiler/Facade/testsData/facadeWithImplementImplementFunctionFound.choiceScript.expected.txt +++ b/src/Compiler/Facade/testsData/facadeWithImplementImplementFunctionFound.choiceScript.expected.txt @@ -5,8 +5,12 @@ case ${action} in install "$@" ;; *) - Log::displayError "invalid action requested: ${action}" - exit 1 + if Assert::functionExists defaultFacadeAction; then + defaultFacadeAction "$1" "$@" + else + Log::displayError "invalid action requested: ${action}" + exit 1 + fi ;; esac exit 0 diff --git a/src/Compiler/Facade/testsData/generateFacadeChoiceScript.oneImplementDirective.expected.txt b/src/Compiler/Facade/testsData/generateFacadeChoiceScript.oneImplementDirective.expected.txt index e63567ad..5f8f53a9 100644 --- a/src/Compiler/Facade/testsData/generateFacadeChoiceScript.oneImplementDirective.expected.txt +++ b/src/Compiler/Facade/testsData/generateFacadeChoiceScript.oneImplementDirective.expected.txt @@ -5,8 +5,12 @@ case ${action} in install "$@" ;; *) - Log::displayError "invalid action requested: ${action}" - exit 1 + if Assert::functionExists defaultFacadeAction; then + defaultFacadeAction "$1" "$@" + else + Log::displayError "invalid action requested: ${action}" + exit 1 + fi ;; esac exit 0 diff --git a/src/Compiler/Facade/testsData/generateFacadeChoiceScript.twoImplementDirective.expected.txt b/src/Compiler/Facade/testsData/generateFacadeChoiceScript.twoImplementDirective.expected.txt index 329c96e2..be3409b3 100644 --- a/src/Compiler/Facade/testsData/generateFacadeChoiceScript.twoImplementDirective.expected.txt +++ b/src/Compiler/Facade/testsData/generateFacadeChoiceScript.twoImplementDirective.expected.txt @@ -11,8 +11,12 @@ case ${action} in breakOnTestFailure "$@" ;; *) - Log::displayError "invalid action requested: ${action}" - exit 1 + if Assert::functionExists defaultFacadeAction; then + defaultFacadeAction "$1" "$@" + else + Log::displayError "invalid action requested: ${action}" + exit 1 + fi ;; esac exit 0 diff --git a/src/Options/generateCommand.bats b/src/Options/generateCommand.bats index 9edaa728..abdbe907 100755 --- a/src/Options/generateCommand.bats +++ b/src/Options/generateCommand.bats @@ -714,3 +714,39 @@ function Options::generateCommand::case10::parseArgsCallbackOverrideDefaultBehav assert_line --index 1 "everyArgumentCallback '' file2" assert_line --index 2 "everyArgumentCallback '' file3" } + +function Options::generateCommand::case11 { #@test + local optionFile + local status=0 + helpFunction() { + echo "help generated by function" + } + export -f helpFunction + longHelpFunction() { + echo "long help generated by function" + } + export -f longHelpFunction + local optionFile + optionFile="$(Options::generateOption --variable-type String --help "file" \ + --variable-name "file" --alt "--file" --alt "-f")" || return 1 + Options::sourceFunction "${optionFile}" + + Options::generateCommand --help "helpFunction" \ + --long-description "longHelpFunction" "${optionFile}" >"${BATS_TEST_TMPDIR}/result" 2>&1 || status=$? + + testCommand "generateCommand.case11.sh" "Options::command" +} + +function Options::generateCommand::case11::help { #@test + source "${BATS_TEST_DIRNAME}/testsData/generateCommand.case11.sh" + helpFunction() { + echo "help generated by function" + } + export -f helpFunction + longHelpFunction() { + echo "long help generated by function" + } + export -f longHelpFunction + run Options::command help + checkCommandResult "generateCommand.case11.expected.help" +} diff --git a/src/Options/templates/command.help.tpl b/src/Options/templates/command.help.tpl index 26fda2a5..9ad3a2bd 100644 --- a/src/Options/templates/command.help.tpl +++ b/src/Options/templates/command.help.tpl @@ -1,5 +1,14 @@ -echo -e "$(Array::wrap2 " " 80 0 <%% echo -e '"${__HELP_TITLE_COLOR}DESCRIPTION:${__RESET_COLOR}"' %> "<% ${help} %>")" -% echo ' echo' +% +description='${__HELP_TITLE_COLOR}DESCRIPTION:${__RESET_COLOR}' +if [[ $(type -t "${help}") = "function" ]]; then + echo " Array::wrap2 ' ' 80 0 \"<% ${description} %>\" \"\$(${help})\"" +else +% +echo -e "$(Array::wrap2 " " 80 0 "${__HELP_TITLE_COLOR}DESCRIPTION:${__RESET_COLOR}" "<% ${help} %>")" +% +fi +echo ' echo' +% %# ------------------------------------------ %# usage section @@ -61,14 +70,13 @@ fi %# ------------------------------------------ %# longDescription section %# ------------------------------------------ -% -if [[ -n "${longDescription}" ]]; then -% -%# removing last empty line +% if [[ -n "${longDescription}" ]]; then +% if [[ $(type -t "${longDescription}") == "function" ]]; then +Array::wrap2 ' ' 76 0 "$(<% ${longDescription} %>)" +% else echo -e """<%% echo "${longDescription}" | sed -E -e '${/^$/d;}' %>""" -% -fi -% +% fi +% fi %# ------------------------------------------ %# version section %# ------------------------------------------ diff --git a/src/Options/testsData/generateCommand.case11.expected.help b/src/Options/testsData/generateCommand.case11.expected.help new file mode 100644 index 00000000..8871fe64 --- /dev/null +++ b/src/Options/testsData/generateCommand.case11.expected.help @@ -0,0 +1,8 @@ +<% DESCRIPTION: %> help generated by function +USAGE: test [OPTIONS] +USAGE: test [--file|-f ] + +OPTIONS: + --file, -f  {single} + file +long help generated by function diff --git a/src/Options/testsData/generateCommand.case11.sh b/src/Options/testsData/generateCommand.case11.sh new file mode 100755 index 00000000..072a1789 --- /dev/null +++ b/src/Options/testsData/generateCommand.case11.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +Options::command() { + local options_parse_cmd="$1" + shift || true + + if [[ "${options_parse_cmd}" = "parse" ]]; then + local -i options_parse_optionParsedCountFile + ((options_parse_optionParsedCountFile = 0)) || true + # shellcheck disable=SC2034 + local -i options_parse_parsedArgIndex=0 + while (($# > 0)); do + local options_parse_arg="$1" + local argOptDefaultBehavior=0 + case "${options_parse_arg}" in + # Option 1/1 + # Option file --file|-f variableType String min 0 max 1 authorizedValues '' regexp '' + --file | -f) + shift + if (($# == 0)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - a value needs to be specified" + return 1 + fi + if ((options_parse_optionParsedCountFile >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountFile)) + # shellcheck disable=SC2034 + file="$1" + ;; + -*) + if [[ "${argOptDefaultBehavior}" = "0" ]]; then + Log::displayError "Command ${SCRIPT_NAME} - Invalid option ${options_parse_arg}" + return 1 + fi + ;; + *) + if [[ "${argOptDefaultBehavior}" = "0" ]]; then + Log::displayError "Command ${SCRIPT_NAME} - Argument - too much arguments provided" + return 1 + fi + ;; + esac + shift || true + done + Log::displayDebug "Command ${SCRIPT_NAME} - parse arguments: ${BASH_FRAMEWORK_ARGV[*]}" + Log::displayDebug "Command ${SCRIPT_NAME} - parse filtered arguments: ${BASH_FRAMEWORK_ARGV_FILTERED[*]}" + elif [[ "${options_parse_cmd}" = "help" ]]; then + Array::wrap2 ' ' 80 0 "<% ${__HELP_TITLE_COLOR}DESCRIPTION:${__RESET_COLOR} %>" "$(helpFunction)" + echo + + echo -e "$(Array::wrap2 " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" "${SCRIPT_NAME}" "[OPTIONS]")" + echo -e "$(Array::wrap2 " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" \ + "${SCRIPT_NAME}" \ + "[--file|-f ]")" + echo + echo -e "${__HELP_TITLE_COLOR}OPTIONS:${__RESET_COLOR}" + echo -e " ${__HELP_OPTION_COLOR}--file${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-f ${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(file) + echo -e " $(Array::wrap2 " " 76 4 "${helpArray[@]}")" + Array::wrap2 ' ' 76 0 "$(longHelpFunction)" + else + Log::displayError "Command ${SCRIPT_NAME} - Option command invalid: '${options_parse_cmd}'" + return 1 + fi +} diff --git a/src/_binaries/options/command.megalinter.tpl b/src/_binaries/options/command.megalinter.tpl index 16568099..77775d30 100644 --- a/src/_binaries/options/command.megalinter.tpl +++ b/src/_binaries/options/command.megalinter.tpl @@ -1,6 +1,6 @@ % declare defaultMegalinterConfigFile=".mega-linter.yml" -declare defaultMegalinterImage=oxsecurity/megalinter-terraform:v7.9.0 +declare defaultMegalinterImage=oxsecurity/megalinter-terraform:v7.10.0 declare versionNumber="1.0" declare commandFunctionName="megalinterCommand" declare help="run megalinter over this repository."