diff --git a/.cspell.json b/.cspell.json index d0672c46..d89c1761 100644 --- a/.cspell.json +++ b/.cspell.json @@ -19,7 +19,8 @@ ".jscpd.json", ".mega-linter.yml", ".shellcheckrc", - ".env" + ".env", + "**/*.help.txt" ], "noConfigSearch": true, "words": ["megalinter", "oxsecurity"], diff --git a/.cspell/bash.txt b/.cspell/bash.txt index 13bab35c..2bed087a 100644 --- a/.cspell/bash.txt +++ b/.cspell/bash.txt @@ -112,3 +112,7 @@ Chastanet Scrasnups datetime gensub +hlocalhost +uuser +ppassword +DELIMS diff --git a/.cspell/softwares.txt b/.cspell/softwares.txt index 69b80d74..dc0199f7 100644 --- a/.cspell/softwares.txt +++ b/.cspell/softwares.txt @@ -149,3 +149,8 @@ docsify htmlhintrc gitleaks nojekyll +RUBOCOP +TRIVY +KICS +TRUFFLEHOG +GRYPE diff --git a/.framework-config b/.framework-config index d2f5d861..fca7f907 100755 --- a/.framework-config +++ b/.framework-config @@ -2,16 +2,16 @@ # shellcheck disable=SC2034 # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^namespace::functions$|^Functions::myFunction$|^IMPORT::dir::file$|^Acquire::ForceIPv4$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|IMPORT::dir::file|Acquire::ForceIPv4)$' # describe the files that do not contain function to be imported NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|^install$|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^conf)" # describe the files that are allowed to not have an associated bats file -BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|^install$|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$)" +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|^install$|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" # describe the files that are allowed to not have a function matching the filename FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^conf/|^bin/|^\.framework-config$|^build.sh$|\.tpl$|testsData/binaryFile$" # Source directories FRAMEWORK_SRC_DIRS=( - "${BASH_TOOLS_ROOT_DIR}/src" + "${FRAMEWORK_ROOT_DIR}/src" "${FRAMEWORK_ROOT_DIR}/vendor/bash-tools-framework/src" ) diff --git a/.jscpd.json b/.jscpd.json index 1b3115dc..ed84a6cf 100644 --- a/.jscpd.json +++ b/.jscpd.json @@ -8,10 +8,12 @@ "**/.history/**", "**/bin/**", "**/backup/**", + "**/logs/**", "**/megalinter-reports/**", "conf/localAppData/Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState/originalSettings.json", "vendor/bash-tools-framework/**", "vendor/bash-tools-framework/.cspell/*", + "src/_binaries/DbImport/dbImportProfile.options.tpl", "testsData/**", "vendor/bats*/**", "pages/README.md", diff --git a/.mega-linter-light.yml b/.mega-linter-light.yml new file mode 100644 index 00000000..4378a2da --- /dev/null +++ b/.mega-linter-light.yml @@ -0,0 +1,17 @@ +--- +EXTENDS: + - .mega-linter.yml +DISABLE_LINTERS: + - JAVASCRIPT_ES + - JAVASCRIPT_PRETTIER + - RUBY_RUBOCOP + - REPOSITORY_CHECKOV + - REPOSITORY_TRIVY + - REPOSITORY_KICS + - SPELL_VALE + - SPELL_LYCHEE + - SPELL_PROSELINT + # REPOSITORY_TRUFFLEHOG disabled because too slow + - REPOSITORY_TRUFFLEHOG + # REPOSITORY_GRYPE disabled because too slow + - REPOSITORY_GRYPE diff --git a/.mega-linter.yml b/.mega-linter.yml index d88ca481..f6b0033e 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -14,12 +14,23 @@ DISABLE_LINTERS: - TERRAFORM_CHECKOV - REPOSITORY_CHECKOV - REPOSITORY_TRIVY + - REPOSITORY_KICS + - SPELL_VALE + - SPELL_LYCHEE + - SPELL_PROSELINT # DISABLE_ERRORS: true # Uncomment if you want MegaLinter to detect errors but not block CI to pass EXCLUDED_DIRECTORIES: - '.history' - '.git' - '.idea' -FILTER_REGEX_EXCLUDE: (\.git/|\.history/|\.idea/) + - 'logs' +FILTER_REGEX_EXCLUDE: | + (?x)( + \.git/| + \.history/| + \.idea/| + ^vendor/ + ) FILEIO_REPORTER: false PRE_COMMANDS: - command: env @@ -27,21 +38,43 @@ PRE_COMMANDS: POST_COMMANDS: # FIX files set as root user # HOST_USER_ID and HOST_GROUP_ID set in package.json - - command: find . -user 0 -exec chown ${HOST_USER_ID}:${HOST_GROUP_ID} {} ';' + - command: | + if [[ "{HOST_USER_ID:-0}" != "0" && "{HOST_GROUP_ID:-0}" != "0" ]]; then + find . -user 0 -exec chown ${HOST_USER_ID}:${HOST_GROUP_ID} {} ';' + fi cwd: 'workspace' + + # remove files generated by cspell + - command: find . -name '*megalinter_file_names_cspell.txt' -delete + cwd: 'workspace' + PRINT_ALPACA: false SHOW_ELAPSED_TIME: true # Linters configurations -BASH_SHELLCHECK_FILTER_REGEX_EXCLUDE: (vendor/.*) +BASH_SHELLCHECK_FILTER_REGEX_EXCLUDE: | + (?x)( + ^vendor| + /testsData/ + ) BASH_SHELLCHECK_ARGUMENTS: --source-path=/tmp/lint - BASH_SHFMT_ARGUMENTS: -i 2 -ci -BASH_SHFMT_FILTER_REGEX_EXCLUDE: .\.zsh$|/\.zshrc|^src/.*/testsData/.*$|^Gemfile.lock$ +BASH_SHFMT_FILTER_REGEX_EXCLUDE: .\.zsh$|/\.zshrc|/testsData/|^Gemfile.lock$ CREDENTIALS_SECRETLINT_CONFIG_FILE: .secretlintrc.yml -EDITORCONFIG_EDITORCONFIG_CHECKER_FILTER_REGEX_EXCLUDE: \.git|^src/.*/testsData/.*$|^Gemfile.lock$ +EDITORCONFIG_EDITORCONFIG_CHECKER_FILTER_REGEX_EXCLUDE: | + (?x)( + \.git/| + /testsData/.*\.(txt|help)| + ^manualTests/data/| + ^src/Log/testsData| + ^Gemfile.lock$| + bin/bash-tpl| + ^doc/guides/Options/generate.*\.md$| + ^pages/Commands.md + ^.*-megalinter_file_names_cspell.txt + ) GIT_GIT_DIFF_PRE_COMMANDS: - command: git config --global core.autocrlf input @@ -50,14 +83,23 @@ GIT_GIT_DIFF_PRE_COMMANDS: continue_if_failed: false - command: git config --global core.whitespace cr-at-eol,-trailing-space continue_if_failed: false + - command: git config --global core.excludesfile .gitignore + continue_if_failed: false + - command: git config --global --add safe.directory .git + continue_if_failed: false IGNORE_GITIGNORED_FILES: true +IGNORE_GENERATED_FILES: true JAVASCRIPT_DEFAULT_STYLE: prettier JAVASCRIPT_ES_CONFIG_FILE: .eslintrc.js JAVASCRIPT_ES_FILTER_REGEX_EXCLUDE: (report/) -JSON_JSONLINT_FILTER_REGEX_EXCLUDE: (\.vscode/settings\.json|conf/\.vscode/settings\.json|report|.vscode/launch.json) +JSON_JSONLINT_FILTER_REGEX_EXCLUDE: | + (?x)( + ^\.vscode/(settings|launch)\.json| + ^conf/\.vscode/settings\.json + ) MARKDOWN_MARKDOWN_LINK_CHECK_FILTER_REGEX_EXCLUDE: (report) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8f0c0f15..23d6e2d5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: always_run: true require_serial: true fail_fast: true - verbose: true + stages: [commit] - repo: local hooks: @@ -18,46 +18,63 @@ repos: entry: bash -c './bin/findShebangFiles chmod +x' language: system always_run: true - require_serial: true fail_fast: true - verbose: true + stages: [commit] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: mixed-line-ending + stages: [commit] - id: end-of-file-fixer + stages: [commit] - id: check-executables-have-shebangs + stages: [commit] - id: check-shebang-scripts-are-executable + stages: [commit] - id: check-xml + stages: [commit] - id: check-yaml + stages: [commit] - id: forbid-new-submodules + stages: [commit] - id: mixed-line-ending args: [--fix=lf] + stages: [commit] - id: check-json exclude: | (?x)^( conf\/.vscode\/settings.json| .vscode\/launch.json )$ + stages: [commit] - repo: https://github.com/jumanjihouse/pre-commit-hooks rev: 3.0.0 hooks: - id: shfmt args: [-i, '2', -ci] - exclude: /testsData/ + exclude: | + (?x)( + /testsData/| + ^bin/| + ^conf/dbScripts/| + ^install + ) + stages: [commit] # Check both committed and uncommitted files for git conflict # markers and whitespace errors according to core.whitespace # and conflict-marker-size configuration in a git repo. - id: git-check exclude: /testsData/ + stages: [commit] - repo: https://github.com/pre-commit/mirrors-prettier rev: v3.0.3 hooks: - id: prettier + stages: [commit] - repo: local hooks: @@ -66,21 +83,18 @@ repos: entry: bash -c './bin/shellcheckLint --staged -f tty' language: system always_run: true - require_serial: true fail_fast: true - verbose: true stages: [commit] - repo: local hooks: - id: frameworkLint name: frameworkLint - entry: bash -c './bin/frameworkLint --verbose' + entry: | + bash -c './bin/frameworkLint --verbose --expected-warnings-count 6' language: system always_run: true - require_serial: true fail_fast: true - verbose: true stages: [commit] - repo: local @@ -92,7 +106,7 @@ repos: always_run: true require_serial: true fail_fast: true - verbose: true + stages: [commit] - repo: local hooks: @@ -103,7 +117,7 @@ repos: always_run: true require_serial: true fail_fast: true - verbose: true + stages: [push] - repo: local hooks: @@ -114,15 +128,42 @@ repos: always_run: true require_serial: true fail_fast: true - verbose: true + stages: [commit] + + - repo: local + hooks: + - id: megalinter-check-version + name: megalinter-check-version + language: system + entry: ./bin/megalinter + args: + [ + --image, + 'oxsecurity/megalinter-terraform:v7.5.0', + --check-megalinter-version, + ] + pass_filenames: false + always_run: true + fail_fast: true + stages: [push] - repo: local hooks: - id: megalinter name: megalinter - entry: bash -c './bin/megalinter --incremental --fix' language: system - always_run: true + entry: ./bin/megalinter + args: + [ + --image, + 'oxsecurity/megalinter-terraform:v7.5.0', + --config-file, + '.mega-linter-light.yml', + --fix, + ] + pass_filenames: false require_serial: true + always_run: true fail_fast: true verbose: true + stages: [push] diff --git a/.vscode/settings.json b/.vscode/settings.json index dc4096ad..43444ef5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,6 +18,9 @@ "files.exclude": { "**/.git": true }, + "workbench.editorAssociations": { + "*.md": "default" + }, "explorer.autoReveal": false, "markdown.extension.toc.omittedFromToc": { "*.md": ["# %%"] diff --git a/Commands.tmpl.md b/Commands.tmpl.md index d9bcca4f..899cd2c1 100644 --- a/Commands.tmpl.md +++ b/Commands.tmpl.md @@ -14,7 +14,7 @@ - [1.11. .github/workflows/buildBinFiles](#111-githubworkflowsbuildbinfiles) - [1.12. bin/test](#112-bintest) - [1.13. bin/runBuildContainer](#113-binrunbuildcontainer) - - [1.14. bin/buildPushDockerImages](#114-binbuildpushdockerimages) + - [1.14. bin/buildPushDockerImage](#114-binbuildpushdockerimage) - [2. Converter and Generator tools](#2-converter-and-generator-tools) - [2.1. bin/generateShellDoc](#21-bingenerateshelldoc) - [2.2. bin/mysql2puml](#22-binmysql2puml) @@ -141,12 +141,12 @@ imported from bash-tools-framework @@@runBuildContainer_help@@@ ``` -### 1.14. bin/buildPushDockerImages +### 1.14. bin/buildPushDockerImage imported from bash-tools-framework ```text -@@@buildPushDockerImages_help@@@ +@@@buildPushDockerImage_help@@@ ``` ## 2. Converter and Generator tools diff --git a/README.md b/README.md index 923b6d9b..a4366c2f 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,10 @@ - [2. Installation/Configuration](#2-installationconfiguration) - [3. Development Environment](#3-development-environment) - [3.1. build dependencies](#31-build-dependencies) - - [3.2. UT](#32-ut) - - [3.3. auto generated bash doc](#33-auto-generated-bash-doc) - - [3.4. github page](#34-github-page) + - [3.2. Precommit hook](#32-precommit-hook) + - [3.3. UT](#33-ut) + - [3.4. auto generated bash doc](#34-auto-generated-bash-doc) + - [3.5. github page](#35-github-page) - [4. Acknowledgements](#4-acknowledgements) ## 1. Excerpt @@ -162,7 +163,19 @@ is created in vendor dir. - `vendor/.batsInstalled` You can remove these files to force the update of the libraries, or just wait 24 hours that the timeout expires. -### 3.2. UT +### 3.2. Precommit hook + +This repository uses pre-commit software to ensure every commits respects a set +of rules specified by the `.pre-commit-config.yaml` file. It supposes pre-commit +software is [installed](https://pre-commit.com/#install) in your environment. + +You also have to execute the following command to enable it: + +```bash +pre-commit install --hook-type pre-commit --hook-type pre-push +``` + +### 3.3. UT All the commands are unit tested, you can run the unit tests using the following command @@ -189,7 +202,7 @@ VENDOR="ubuntu" BASH_TAR_VERSION=5.1 BASH_IMAGE=ubuntu:20.04 \ SKIP_BUILD=1 SKIP_USER=1 ./bin/test -r src -j 2 ``` -### 3.3. auto generated bash doc +### 3.4. auto generated bash doc generated by running @@ -197,7 +210,7 @@ generated by running ./bin/doc ``` -### 3.4. github page +### 3.5. github page The web page uses [Docsify](https://docsify.js.org/) to generate a static web site. diff --git a/TODO.md b/TODO.md index 69925afd..b1cdd78f 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,15 @@ # Todo +- pre-commit migration using bash-tools framework hooks +- fix last 2 failing tests +- try to use longDescription using something like '"${longDescription}"' + - so declare all variables in .sh file instead of options file +- bash-tools-framework + + - migrate the same way scripts + - use same help bats script + - replace bash-tpl + - report megalinter changes yml + github from framework to this repo - get rid of install command - each command should call a similar function before running diff --git a/bin/cli b/bin/cli index 32c86b3b..8629fe9b 100755 --- a/bin/cli +++ b/bin/cli @@ -47,6 +47,7 @@ declare -a BASH_FRAMEWORK_ARGV=("$@") declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") # @see https://unix.stackexchange.com/a/386856 +# shellcheck disable=SC2317 interruptManagement() { # restore SIGINT handler trap - INT @@ -76,6 +77,7 @@ TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX) export TMPDIR # temp dir cleaning +# shellcheck disable=SC2317 cleanOnExit() { if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" @@ -134,7 +136,7 @@ Array::wrap() { local -i argNoAnsiLength=0 while (($# > 0)); do - argNoAnsi="$(echo "${arg}" | Filters::removeAnsiCodes)" + argNoAnsi="$(echo "${arg%%*( )}" | Filters::removeAnsiCodes)" ((argNoAnsiLength = ${#argNoAnsi})) || true if (($# < 1 && argNoAnsiLength == 0)); then break @@ -157,7 +159,7 @@ Array::wrap() { if ((currentLineLength == 0 && firstLine == 0)); then echo -n "${indentStr}" fi - echo -e -n "${arg}" + echo -e -n "${arg}" | sed 's/[\t ]*$//g' needEcho="1" ((currentLineLength += argNoAnsiLength)) ((glueLength = ${#glue})) || true @@ -176,7 +178,7 @@ Array::wrap() { fi local -i length ((length = maxLineLength - currentLineLength)) || true - echo -e "${arg:0:${length}}" + echo -e "${arg:0:${length}}" | sed 's/[\t ]*$//g' ((currentLineLength = 0)) || true ((glueLength = 0)) || true arg="${arg:${length}}" @@ -184,7 +186,7 @@ Array::wrap() { else # arg cannot be stored on a whole line, so we add it on next line as a whole echo - echo -e -n "${indentStr}${arg}" + echo -e -n "${indentStr}${arg}" | sed 's/[\t ]*$//g' ((glueLength = ${#glue})) || true ((currentLineLength = argNoAnsiLength)) arg="" # allows to go to next arg @@ -516,6 +518,15 @@ Log::displayInfo() { Log::logInfo "$1" "${type}" } +# @description Display message using warning color (yellow) +# @arg $1 message:String the message to display +Log::displayWarning() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then + echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 + fi + Log::logWarning "$1" +} + # @description Display message using error color (red) and exit immediately with error status 1 # @arg $1 message:String the message to display Log::fatal() { @@ -597,6 +608,7 @@ UI::drawLine() { # @set __HELP_EXAMPLE String to remove # @set __HELP_TITLE String to remove # @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 UI::theme() { local theme="${1-default}" if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then @@ -612,46 +624,46 @@ UI::theme() { ;; esac if [[ "${theme}" = "default" ]]; then - export BASH_FRAMEWORK_THEME="default" + BASH_FRAMEWORK_THEME="default" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __SKIPPED_COLOR='\e[33m' # Yellow - export __DEBUG_COLOR='\e[37m' # Grey - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __HELP_TITLE_COLOR="\e[1;37m" # Bold - export __HELP_OPTION_COLOR="\e[1;34m" # Blue + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color + __RESET_COLOR='\e[0m' # Reset Color # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" + __HELP_TITLE="$(echo -e "\e[1;37m")" # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" + __HELP_NORMAL="$(echo -e "\033[0m")" else - export BASH_FRAMEWORK_THEME="noColor" + BASH_FRAMEWORK_THEME="noColor" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __DEBUG_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __HELP_TITLE_COLOR='' - export __HELP_OPTION_COLOR='' + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' fi } @@ -668,8 +680,8 @@ Compiler::Facade::requireCommandBinDir() { # @description check if tty (interactive mode) is active # @noargs # @exitcode 1 if tty not active -# @environment NON_INTERACTIVE if 1 consider as not interactive even if environement is interactive -# @environment INTERACTIVE if 1 consider as interactive even if environement is not interactive +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive # @stderr diagnostic information + help if second argument is provided Assert::tty() { if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then @@ -878,15 +890,6 @@ Log::displaySkipped() { Log::logSkipped "$1" } -# @description Display message using warning color (yellow) -# @arg $1 message:String the message to display -Log::displayWarning() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then - echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 - fi - Log::logWarning "$1" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -944,6 +947,14 @@ Log::logMessage() { fi } +# @description log message to file +# @arg $1 message:String the message to display +Log::logWarning() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then + Log::logMessage "${2:-WARNING}" "$1" + fi +} + # @description To be called before logging in the log file # @arg $1 file:string log file name # @arg $2 maxLogFilesCount:int maximum number of log files @@ -1050,14 +1061,6 @@ Log::logSkipped() { fi } -# @description log message to file -# @arg $1 message:String the message to display -Log::logWarning() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then - Log::logMessage "${2:-WARNING}" "$1" - fi -} - # @description ensure command realpath is available # @exitcode 1 if realpath command not available # @stderr diagnostics information is displayed @@ -1107,16 +1110,29 @@ Log::requireLoad Compiler::Facade::requireCommandBinDir # @require Compiler::Facade::requireCommandBinDir +# shellcheck disable=SC2034 + +# constants +declare defaultUserArg="root" +declare -a defaultCommandArg=("//bin/sh") +declare PROFILES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/cliProfiles" +declare HOME_PROFILES_DIR="${HOME}/.bash-tools/cliProfiles" + +# option values +declare containerArg="default" +declare finalUserArg="${defaultUserArg}" +declare finalCommandArg=("${defaultCommandArg[@]}") + +# other values +declare copyrightBeginYear="2020" declare -a BASH_FRAMEWORK_ARGV_FILTERED=() copyrightCallback() { - local years - years="$(date +%Y)" - if [[ -n "${copyrightBeginYear}" && "${copyrightBeginYear}" != "${years}" ]]; then - years="${copyrightBeginYear}-${years}" + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" fi - echo "Copyright (c) ${years} François Chastanet" + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" } # shellcheck disable=SC2317 # if function is overridden @@ -1290,6 +1306,29 @@ optionBashFrameworkConfigCallback() { fi } +defaultFrameworkConfig="$( + cat <<'EOF' +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$" +# Source directories +FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" +) + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +EOF +)" + commandOptionParseFinished() { if [[ -z "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then BASH_FRAMEWORK_ENV_FILES=() @@ -1309,8 +1348,16 @@ commandOptionParseFinished() { # shellcheck disable=SC2034 BASH_FRAMEWORK_CONFIG_FILE="" # shellcheck source=/.framework-config - Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || - Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config file" + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || { + # load default template framework config + if [[ ! -f "${PERSISTENT_TMPDIR}/.framework-config" ]]; then + echo "${defaultFrameworkConfig}" > "${PERSISTENT_TMPDIR}/.framework-config" + fi + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${PERSISTENT_TMPDIR}" || { + Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config.default file" + } + Log::displayWarning "Command ${SCRIPT_NAME} - Load default .framework-config file - ${PERSISTENT_TMPDIR}/.framework-config" + } fi if [[ "${optionConfig}" = "1" ]]; then @@ -1363,6 +1410,7 @@ cliCommand() { ((options_parse_argParsedCountUserArg = 0)) || true local -i options_parse_argParsedCountCommandArg ((options_parse_argParsedCountCommandArg = 0)) || true + # shellcheck disable=SC2034 local -i options_parse_parsedArgIndex=0 while (($# > 0)); do local options_parse_arg="$1" @@ -1381,12 +1429,14 @@ cliCommand() { return 1 fi ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 optionBashFrameworkConfig="$1" optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" ;; # Option 2/14 # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' --config) + # shellcheck disable=SC2034 optionConfig="1" if ((options_parse_optionParsedCountOptionConfig >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1397,6 +1447,7 @@ cliCommand() { # Option 3/14 # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' --verbose | -v) + # shellcheck disable=SC2034 optionInfoVerbose="1" if ((options_parse_optionParsedCountOptionInfoVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1409,6 +1460,7 @@ cliCommand() { # Option 4/14 # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vv) + # shellcheck disable=SC2034 optionDebugVerbose="1" if ((options_parse_optionParsedCountOptionDebugVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1421,6 +1473,7 @@ cliCommand() { # Option 5/14 # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vvv) + # shellcheck disable=SC2034 optionTraceVerbose="1" if ((options_parse_optionParsedCountOptionTraceVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1446,6 +1499,7 @@ cliCommand() { # Option 7/14 # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' --no-color) + # shellcheck disable=SC2034 optionNoColor="1" if ((options_parse_optionParsedCountOptionNoColor >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1472,6 +1526,7 @@ cliCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 optionTheme="$1" optionThemeCallback "${options_parse_arg}" "${optionTheme}" updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" @@ -1479,6 +1534,7 @@ cliCommand() { # Option 9/14 # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' --help | -h) + # shellcheck disable=SC2034 optionHelp="1" if ((options_parse_optionParsedCountOptionHelp >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1490,6 +1546,7 @@ cliCommand() { # Option 10/14 # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' --version) + # shellcheck disable=SC2034 optionVersion="1" if ((options_parse_optionParsedCountOptionVersion >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1501,6 +1558,7 @@ cliCommand() { # Option 11/14 # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' --quiet | -q) + # shellcheck disable=SC2034 optionQuiet="1" if ((options_parse_optionParsedCountOptionQuiet >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1527,6 +1585,7 @@ cliCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 optionLogLevel="$1" optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" @@ -1544,6 +1603,7 @@ cliCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 optionLogFile="$1" optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" @@ -1565,6 +1625,7 @@ cliCommand() { return 1 fi ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 optionDisplayLevel="$1" optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" @@ -1584,6 +1645,7 @@ cliCommand() { return 1 fi ((++options_parse_argParsedCountContainerArg)) + # shellcheck disable=SC2034 containerArg="${options_parse_arg}" # Argument 2/3 # Argument userArg min 0 max 1 authorizedValues '' regexp '' @@ -1593,6 +1655,7 @@ cliCommand() { return 1 fi ((++options_parse_argParsedCountUserArg)) + # shellcheck disable=SC2034 userArg="${options_parse_arg}" # Argument 3/3 # Argument commandArg min 0 max 1 authorizedValues '' regexp '' @@ -1602,6 +1665,7 @@ cliCommand() { return 1 fi ((++options_parse_argParsedCountCommandArg)) + # shellcheck disable=SC2034 commandArg="${options_parse_arg}" else unknownOption "${options_parse_arg}" @@ -1625,67 +1689,90 @@ cliCommand() { echo echo -e "${__HELP_TITLE_COLOR}ARGUMENTS:${__RESET_COLOR}" echo -e " [${__HELP_OPTION_COLOR}container${__HELP_NORMAL} {single}]" - echo -e " $(Array::wrap ' ' 76 4 $(containerArgHelpCallback))" + local -a helpArray + # shellcheck disable=SC2054,SC2206 + mapfile -t helpArray < <(containerArgHelpCallback) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " [${__HELP_OPTION_COLOR}user${__HELP_NORMAL} {single}]" - echo -e " $(Array::wrap ' ' 76 4 $(userArgHelpCallback))" + local -a helpArray + # shellcheck disable=SC2054,SC2206 + mapfile -t helpArray < <(userArgHelpCallback) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " [${__HELP_OPTION_COLOR}commandArg${__HELP_NORMAL} {single}]" - echo -e " $(Array::wrap ' ' 76 4 $(commandArgHelpCallback))" + local -a helpArray + # shellcheck disable=SC2054,SC2206 + mapfile -t helpArray < <(commandArgHelpCallback) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(use\ alternate\ bash\ framework\ configuration.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ configuration) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} (optional)" + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Load\ the\ specified\ env\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(choose\ color\ theme\ \(default\,\ default-force\ or\ noColor\)\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ this\ command\ help) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Print\ version\ information\ and\ quit) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(set\ display\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e """ @@ -1699,7 +1786,7 @@ ${containers} ${__HELP_TITLE}EXAMPLES:${__HELP_EXAMPLE} to connect to mysql container in bash mode with user mysql - ${SCRIPT_NAME} mysql mysql "//bin/bash" + ${SCRIPT_NAME} mysql mysql /bin/bash to connect to web container with user root ${SCRIPT_NAME} web root ${__HELP_NORMAL} @@ -1730,16 +1817,6 @@ variables ${__HELP_OPTION_COLOR}finalUserArg${__HELP_NORMAL}, ${__HELP_OPTION_CO fi } -# default values -declare containerArg="default" -declare finalUserArg="root" -declare finalCommandArg=("//bin/sh") -declare copyrightBeginYear="2020" - -# constants -PROFILES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/cliProfiles" -HOME_PROFILES_DIR="${HOME}/.bash-tools/cliProfiles" - containerArgHelpCallback() { Conf::load "cliProfiles" "default" echo "container should be the name of a profile from profile list," @@ -1790,7 +1867,6 @@ unknownOption() { cliCommand parse "${BASH_FRAMEWORK_ARGV[@]}" run() { - # Internal function that can be used in conf profiles to load the dsn file loadDsn() { local dsn="$1" diff --git a/bin/dbImport b/bin/dbImport index 28f12ae5..5515e90a 100755 --- a/bin/dbImport +++ b/bin/dbImport @@ -47,6 +47,7 @@ declare -a BASH_FRAMEWORK_ARGV=("$@") declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") # @see https://unix.stackexchange.com/a/386856 +# shellcheck disable=SC2317 interruptManagement() { # restore SIGINT handler trap - INT @@ -76,6 +77,7 @@ TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX) export TMPDIR # temp dir cleaning +# shellcheck disable=SC2317 cleanOnExit() { if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" @@ -150,7 +152,7 @@ Array::wrap() { local -i argNoAnsiLength=0 while (($# > 0)); do - argNoAnsi="$(echo "${arg}" | Filters::removeAnsiCodes)" + argNoAnsi="$(echo "${arg%%*( )}" | Filters::removeAnsiCodes)" ((argNoAnsiLength = ${#argNoAnsi})) || true if (($# < 1 && argNoAnsiLength == 0)); then break @@ -173,7 +175,7 @@ Array::wrap() { if ((currentLineLength == 0 && firstLine == 0)); then echo -n "${indentStr}" fi - echo -e -n "${arg}" + echo -e -n "${arg}" | sed 's/[\t ]*$//g' needEcho="1" ((currentLineLength += argNoAnsiLength)) ((glueLength = ${#glue})) || true @@ -192,7 +194,7 @@ Array::wrap() { fi local -i length ((length = maxLineLength - currentLineLength)) || true - echo -e "${arg:0:${length}}" + echo -e "${arg:0:${length}}" | sed 's/[\t ]*$//g' ((currentLineLength = 0)) || true ((glueLength = 0)) || true arg="${arg:${length}}" @@ -200,7 +202,7 @@ Array::wrap() { else # arg cannot be stored on a whole line, so we add it on next line as a whole echo - echo -e -n "${indentStr}${arg}" + echo -e -n "${indentStr}${arg}" | sed 's/[\t ]*$//g' ((glueLength = ${#glue})) || true ((currentLineLength = argNoAnsiLength)) arg="" # allows to go to next arg @@ -637,6 +639,15 @@ Log::displayInfo() { Log::logInfo "$1" "${type}" } +# @description Display message using warning color (yellow) +# @arg $1 message:String the message to display +Log::displayWarning() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then + echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 + fi + Log::logWarning "$1" +} + # @description Display message using error color (red) and exit immediately with error status 1 # @arg $1 message:String the message to display Log::fatal() { @@ -718,6 +729,7 @@ UI::drawLine() { # @set __HELP_EXAMPLE String to remove # @set __HELP_TITLE String to remove # @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 UI::theme() { local theme="${1-default}" if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then @@ -733,46 +745,46 @@ UI::theme() { ;; esac if [[ "${theme}" = "default" ]]; then - export BASH_FRAMEWORK_THEME="default" + BASH_FRAMEWORK_THEME="default" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __SKIPPED_COLOR='\e[33m' # Yellow - export __DEBUG_COLOR='\e[37m' # Grey - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __HELP_TITLE_COLOR="\e[1;37m" # Bold - export __HELP_OPTION_COLOR="\e[1;34m" # Blue + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color + __RESET_COLOR='\e[0m' # Reset Color # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" + __HELP_TITLE="$(echo -e "\e[1;37m")" # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" + __HELP_NORMAL="$(echo -e "\033[0m")" else - export BASH_FRAMEWORK_THEME="noColor" + BASH_FRAMEWORK_THEME="noColor" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __DEBUG_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __HELP_TITLE_COLOR='' - export __HELP_OPTION_COLOR='' + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' fi } @@ -836,8 +848,8 @@ Linux::requireExecutedAsUser() { # @description check if tty (interactive mode) is active # @noargs # @exitcode 1 if tty not active -# @environment NON_INTERACTIVE if 1 consider as not interactive even if environement is interactive -# @environment INTERACTIVE if 1 consider as interactive even if environement is not interactive +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive # @stderr diagnostic information + help if second argument is provided Assert::tty() { if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then @@ -1097,15 +1109,6 @@ Log::displaySkipped() { Log::logSkipped "$1" } -# @description Display message using warning color (yellow) -# @arg $1 message:String the message to display -Log::displayWarning() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then - echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 - fi - Log::logWarning "$1" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -1163,6 +1166,14 @@ Log::logMessage() { fi } +# @description log message to file +# @arg $1 message:String the message to display +Log::logWarning() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then + Log::logMessage "${2:-WARNING}" "$1" + fi +} + # @description To be called before logging in the log file # @arg $1 file:string log file name # @arg $2 maxLogFilesCount:int maximum number of log files @@ -1303,14 +1314,6 @@ Log::logSkipped() { fi } -# @description log message to file -# @arg $1 message:String the message to display -Log::logWarning() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then - Log::logMessage "${2:-WARNING}" "$1" - fi -} - # @description ensure command realpath is available # @exitcode 1 if realpath command not available # @stderr diagnostics information is displayed @@ -1345,21 +1348,35 @@ Compiler::Facade::requireCommandBinDir Linux::requireExecutedAsUser # @require Compiler::Facade::requireCommandBinDir +# shellcheck disable=SC2034 -defaultFromDsnHelp="$(echo \ - "dsn to use for source database" $'\n' \ - "this option is incompatible with -a|--from-aws option" \ -)" +# default values +declare optionFromAws="" +declare optionSkipSchema="0" +declare targetDbName="" +declare fromDbName="" +declare optionProfile="default" +declare optionTables="" +declare profileCommandFile="" +declare optionTargetDsn="" +declare optionCharacterSet="" +declare defaultTargetCharacterSet="" + +# other configuration +declare copyrightBeginYear="2020" +declare TIMEFORMAT='time spent : %3R' +declare DB_IMPORT_DUMP_DIR=${DB_IMPORT_DUMP_DIR%/} +declare PROFILES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/dbImportProfiles" +declare HOME_PROFILES_DIR="${HOME}/.bash-tools/dbImportProfiles" +declare DOWNLOAD_DUMP=0 declare -a BASH_FRAMEWORK_ARGV_FILTERED=() copyrightCallback() { - local years - years="$(date +%Y)" - if [[ -n "${copyrightBeginYear}" && "${copyrightBeginYear}" != "${years}" ]]; then - years="${copyrightBeginYear}-${years}" + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" fi - echo "Copyright (c) ${years} François Chastanet" + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" } # shellcheck disable=SC2317 # if function is overridden @@ -1533,6 +1550,29 @@ optionBashFrameworkConfigCallback() { fi } +defaultFrameworkConfig="$( + cat <<'EOF' +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$" +# Source directories +FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" +) + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +EOF +)" + commandOptionParseFinished() { if [[ -z "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then BASH_FRAMEWORK_ENV_FILES=() @@ -1552,8 +1592,16 @@ commandOptionParseFinished() { # shellcheck disable=SC2034 BASH_FRAMEWORK_CONFIG_FILE="" # shellcheck source=/.framework-config - Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || - Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config file" + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || { + # load default template framework config + if [[ ! -f "${PERSISTENT_TMPDIR}/.framework-config" ]]; then + echo "${defaultFrameworkConfig}" > "${PERSISTENT_TMPDIR}/.framework-config" + fi + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${PERSISTENT_TMPDIR}" || { + Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config.default file" + } + Log::displayWarning "Command ${SCRIPT_NAME} - Load default .framework-config file - ${PERSISTENT_TMPDIR}/.framework-config" + } fi if [[ "${optionConfig}" = "1" ]]; then @@ -1567,11 +1615,11 @@ declare optionFromDsn="" # default values declare optionProfile="default" declare optionTables="" -declare profileCommand="" +declare profileCommandFile="" profileOptionHelpCallback() { echo "the name of the profile to use in order to include or exclude tables" - echo "(if not specified ${HOME_PROFILES_DIR}/default.sh is used if exists otherwise ${PROFILES_DIR}/default.sh)" + echo "(if not specified in default.sh from 'User profiles directory' if exists or 'Default profiles directory')" } optionTablesCallback() { @@ -1581,9 +1629,9 @@ optionTablesCallback() { } profileOptionCallback() { - local -a profilesList - readarray -t profilesList < <(Conf::getMergedList "dbImportProfiles" "sh" "" || true) - if ! Array::contains "$2" "${profilesList[@]}"; then + local -a profilesArray + readarray -t profilesArray < <(Conf::getMergedList "dbImportProfiles" "sh" "" || true) + if ! Array::contains "$2" "${profilesArray[@]}"; then Log::displayError "${SCRIPT_NAME} - invalid profile '$2' provided" return 1 fi @@ -1597,7 +1645,7 @@ initProfileCommandCallback() { local profileMsgInfo # shellcheck disable=SC2154 if [[ "${optionProfile}" = 'default' && -n "${optionTables}" ]]; then - profileCommand=$(Framework::createTempFile "profileCmd.XXXXXXXXXXXX") + profileCommandFile=$(Framework::createTempFile "profileCmd.XXXXXXXXXXXX") profileMsgInfo="only ${optionTables} will be imported" ( echo '#!/usr/bin/env bash' @@ -1607,17 +1655,17 @@ initProfileCommandCallback() { # tables option not specified, we will import all tables of the profile echo 'cat' fi - ) >"${profileCommand}" + ) >"${profileCommandFile}" else - profileCommand="$(Conf::getAbsoluteFile "dbImportProfiles" "${optionProfile}" "sh")" || exit 1 - profileMsgInfo="Using profile ${profileCommand}" + profileCommandFile="$(Conf::getAbsoluteFile "dbImportProfiles" "${optionProfile}" "sh")" || exit 1 + profileMsgInfo="Using profile ${profileCommandFile}" fi - chmod +x "${profileCommand}" + chmod +x "${profileCommandFile}" Log::displayInfo "${profileMsgInfo}" } -declare optionTargetDsn="default.local" # old TARGET_DSN -declare optionCharacterSet="" # old CHARACTER_SET +declare optionTargetDsn="default.local" +declare optionCharacterSet="" declare defaultTargetCharacterSet="utf8" initializeDefaultTargetMysqlOptions() { @@ -1637,7 +1685,7 @@ initializeDefaultTargetMysqlOptions() { fi } -declare optionCollationName="" # old COLLATION_NAME +declare optionCollationName="" declare defaultTargetCollationName="utf8_general_ci" dbImportCommand() { @@ -1700,6 +1748,7 @@ dbImportCommand() { ((options_parse_argParsedCountFromDbName = 0)) || true local -i options_parse_argParsedCountTargetDbName ((options_parse_argParsedCountTargetDbName = 0)) || true + # shellcheck disable=SC2034 local -i options_parse_parsedArgIndex=0 while (($# > 0)); do local options_parse_arg="$1" @@ -1718,6 +1767,7 @@ dbImportCommand() { return 1 fi ((++options_parse_optionParsedCountOptionProfile)) + # shellcheck disable=SC2034 optionProfile="$1" profileOptionCallback "${options_parse_arg}" "${optionProfile}" ;; @@ -1734,6 +1784,7 @@ dbImportCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTables)) + # shellcheck disable=SC2034 optionTables="$1" optionTablesCallback "${options_parse_arg}" "${optionTables}" ;; @@ -1750,11 +1801,13 @@ dbImportCommand() { return 1 fi ((++options_parse_optionParsedCountOptionFromDsn)) + # shellcheck disable=SC2034 optionFromDsn="$1" ;; # Option 4/22 # Option optionSkipSchema --skip-schema|-s variableType Boolean min 0 max 1 authorizedValues '' regexp '' --skip-schema | -s) + # shellcheck disable=SC2034 optionSkipSchema="1" if ((options_parse_optionParsedCountOptionSkipSchema >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1775,6 +1828,7 @@ dbImportCommand() { return 1 fi ((++options_parse_optionParsedCountOptionFromAws)) + # shellcheck disable=SC2034 optionFromAws="$1" ;; # Option 6/22 @@ -1790,6 +1844,7 @@ dbImportCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTargetDsn)) + # shellcheck disable=SC2034 optionTargetDsn="$1" ;; # Option 7/22 @@ -1805,6 +1860,7 @@ dbImportCommand() { return 1 fi ((++options_parse_optionParsedCountOptionCharacterSet)) + # shellcheck disable=SC2034 optionCharacterSet="$1" ;; # Option 8/22 @@ -1820,6 +1876,7 @@ dbImportCommand() { return 1 fi ((++options_parse_optionParsedCountOptionCollationName)) + # shellcheck disable=SC2034 optionCollationName="$1" ;; # Option 9/22 @@ -1835,12 +1892,14 @@ dbImportCommand() { return 1 fi ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 optionBashFrameworkConfig="$1" optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" ;; # Option 10/22 # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' --config) + # shellcheck disable=SC2034 optionConfig="1" if ((options_parse_optionParsedCountOptionConfig >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1851,6 +1910,7 @@ dbImportCommand() { # Option 11/22 # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' --verbose | -v) + # shellcheck disable=SC2034 optionInfoVerbose="1" if ((options_parse_optionParsedCountOptionInfoVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1863,6 +1923,7 @@ dbImportCommand() { # Option 12/22 # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vv) + # shellcheck disable=SC2034 optionDebugVerbose="1" if ((options_parse_optionParsedCountOptionDebugVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1875,6 +1936,7 @@ dbImportCommand() { # Option 13/22 # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vvv) + # shellcheck disable=SC2034 optionTraceVerbose="1" if ((options_parse_optionParsedCountOptionTraceVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1900,6 +1962,7 @@ dbImportCommand() { # Option 15/22 # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' --no-color) + # shellcheck disable=SC2034 optionNoColor="1" if ((options_parse_optionParsedCountOptionNoColor >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1926,6 +1989,7 @@ dbImportCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 optionTheme="$1" optionThemeCallback "${options_parse_arg}" "${optionTheme}" updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" @@ -1933,6 +1997,7 @@ dbImportCommand() { # Option 17/22 # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' --help | -h) + # shellcheck disable=SC2034 optionHelp="1" if ((options_parse_optionParsedCountOptionHelp >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1944,6 +2009,7 @@ dbImportCommand() { # Option 18/22 # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' --version) + # shellcheck disable=SC2034 optionVersion="1" if ((options_parse_optionParsedCountOptionVersion >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1955,6 +2021,7 @@ dbImportCommand() { # Option 19/22 # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' --quiet | -q) + # shellcheck disable=SC2034 optionQuiet="1" if ((options_parse_optionParsedCountOptionQuiet >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1981,6 +2048,7 @@ dbImportCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 optionLogLevel="$1" optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" @@ -1998,6 +2066,7 @@ dbImportCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 optionLogFile="$1" optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" @@ -2019,6 +2088,7 @@ dbImportCommand() { return 1 fi ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 optionDisplayLevel="$1" optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" @@ -2041,6 +2111,7 @@ dbImportCommand() { return 1 fi ((++options_parse_argParsedCountFromDbName)) + # shellcheck disable=SC2034 fromDbName="${options_parse_arg}" # Argument 2/2 # Argument targetDbName min 0 max 1 authorizedValues '' regexp '' @@ -2050,6 +2121,7 @@ dbImportCommand() { return 1 fi ((++options_parse_argParsedCountTargetDbName)) + # shellcheck disable=SC2034 targetDbName="${options_parse_arg}" else if [[ "${argOptDefaultBehavior}" = "0" ]]; then @@ -2078,109 +2150,135 @@ dbImportCommand() { echo -e "$(Array::wrap " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" "${SCRIPT_NAME}" "[OPTIONS]" "[ARGUMENTS]")" echo -e "$(Array::wrap " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" \ "${SCRIPT_NAME}" \ - "[--profile|-p ]" "[--tables ]" "[--from-dsn|-f ]" "[--skip-schema|-s]" "[--from-aws|-a ]" "[--target-dsn|-t ]" "[--character-set|-c ]" "[--collation-name|-o ]" "[--bash-framework-config ]" "[--config]" "[--verbose|-v]" "[-vv]" "[-vvv]" "[--env-file ]" "[--no-color]" "[--theme ]" "[--help|-h]" "[--version]" "[--quiet|-q]" "[--log-level ]" "[--log-file ]" "[--display-level ]")" + "[--profile|-p ]" "[--tables ]" "[--from-dsn|-f ]" "[--skip-schema|-s]" "[--from-aws|-a ]" "[--target-dsn|-t ]" "[--character-set|-c ]" "[--collation-name|-o ]" "[--bash-framework-config ]" "[--config]" "[--verbose|-v]" "[-vv]" "[-vvv]" "[--env-file ]" "[--no-color]" "[--theme ]" "[--help|-h]" "[--version]" "[--quiet|-q]" "[--log-level ]" "[--log-file ]" "[--display-level ]")" echo echo -e "${__HELP_TITLE_COLOR}ARGUMENTS:${__RESET_COLOR}" echo -e " ${__HELP_OPTION_COLOR}fromDbName${__HELP_NORMAL} {single} (mandatory)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(the\ name\ of\ the\ source/remote\ database) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " [${__HELP_OPTION_COLOR}targetDbName${__HELP_NORMAL} {single}]" local -a helpArray + # shellcheck disable=SC2054 helpArray=(the\ name\ of\ the\ target\ database\,\ use\ fromDbName\(without\ extension\)\ if\ not\ provided) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}PROFILE OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--profile${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-p ${__HELP_NORMAL} (optional) (at most 1 times)" - echo -e " $(Array::wrap ' ' 76 4 $(profileOptionHelpCallback))" - printf " %b\n" "${__HELP_OPTION_COLOR}--tables ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--profile${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-p ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054,SC2206 + mapfile -t helpArray < <(profileOptionHelpCallback) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--tables ${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 helpArray=(import\ only\ table\ specified\ in\ the\ list.\ \ If\ aws\ mode\,\ ignore\ profile\ option) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}FROM OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--from-dsn${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-f ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--from-dsn${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-f ${__HELP_NORMAL} {single}" local -a helpArray - helpArray=(target\ mysql\ server) + # shellcheck disable=SC2054 + helpArray=($'dsn to use for source database\n\\n this option is incompatible with -a|--from-aws option') echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--skip-schema${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-s${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--skip-schema${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-s${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(avoid\ to\ import\ the\ schema) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--from-aws${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-a ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--from-aws${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-a ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(db\ dump\ will\ be\ downloaded\ from\ s3\ instead\ of\ using\ remote\ db.\ The\ value\ \\ is\ the\ name\ of\ the\ file\ without\ s3\ location\ \(Only\ .gz\ or\ tar.gz\ file\ are\ supported\).\ This\ option\ is\ incompatible\ with\ -f\|--from-dsn\ option) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}TARGET OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--target-dsn${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-t ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--target-dsn${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-t ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(dsn\ to\ use\ for\ target\ database\ \(Default:\ default.local\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--character-set${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-c ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--character-set${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-c ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(change\ the\ character\ set\ used\ during\ database\ creation\ \(default\ value:\ utf8\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--collation-name${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-o ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--collation-name${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-o ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(change\ the\ collation\ name\ used\ during\ database\ creation\ \(default\ value:\ utf8_general_ci\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(use\ alternate\ bash\ framework\ configuration.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ configuration) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} (optional)" + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Load\ the\ specified\ env\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(choose\ color\ theme\ \(default\,\ default-force\ or\ noColor\)\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ this\ command\ help) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Print\ version\ information\ and\ quit) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(set\ display\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e """ @@ -2225,20 +2323,6 @@ ${__HELP_EXAMPLE}TODO${__HELP_NORMAL}""" fi } -# default values -declare optionFromAws="" -declare optionSkipSchema="0" -declare targetDbName="" -declare fromDbName="" - -# other configuration -declare copyrightBeginYear="2020" -declare TIMEFORMAT='time spent : %3R' -declare DB_IMPORT_DUMP_DIR=${DB_IMPORT_DUMP_DIR%/} -declare PROFILES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/dbImportProfiles" -declare HOME_PROFILES_DIR="${HOME}/.bash-tools/dbImportProfiles" -declare DOWNLOAD_DUMP=0 - optionHelpCallback() { local profilesList="" local dsnList="" @@ -2280,6 +2364,8 @@ dbImportCommandCallback() { fi } +dbImportCommand parse "${BASH_FRAMEWORK_ARGV[@]}" + # dump header/footer read -r -d '\0' DUMP_HEADER <<-EOM SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS = 0; @@ -2304,8 +2390,6 @@ GROUP BY table_schema EOF )" -dbImportCommand parse "${BASH_FRAMEWORK_ARGV[@]}" - # @require Linux::requireExecutedAsUser run() { @@ -2323,6 +2407,7 @@ run() { # shellcheck disable=SC2154 Database::newInstance dbTargetDatabase "${optionTargetDsn}" + # shellcheck disable=SC2154 Database::setQueryOptions dbTargetDatabase "${dbTargetDatabase[QUERY_OPTIONS]} --connect-timeout=5" Log::displayInfo "Using target dsn ${dbTargetDatabase['DSN_FILE']}" if [[ -z "${optionFromAws}" ]]; then @@ -2356,7 +2441,7 @@ run() { Log::displayInfo "local dump ${remoteDbDumpTempFile} already exists, avoid download" fi - Log::displayInfo "tables list will calculated using profile ${optionProfile} => ${profileCommand}" + Log::displayInfo "tables list will calculated using profile ${optionProfile} => ${profileCommandFile}" SECONDS=0 if [[ "${downloadDump}" = "1" ]]; then Log::displayInfo "Download dump" @@ -2386,7 +2471,7 @@ run() { local listTables local listTablesDumpSize local listTablesDump - listTables="$(Database::query dbFromInstance "show tables" "${fromDbName}" | ${profileCommand} | sort)" + listTables="$(Database::query dbFromInstance "show tables" "${fromDbName}" | ${profileCommandFile} | sort)" # shellcheck disable=SC2034 # used by DUMP_SIZE_QUERY listTablesDumpSize="$(echo "${listTables}" | awk -v d="," -v q="'" '{s=(NR==1?s:s d)q $0 q}END{print s }')" listTablesDump=$(echo "${listTables}" | awk -v d=" " -v q="" '{s=(NR==1?s:s d)q $0 q}END{print s }') @@ -2432,13 +2517,13 @@ run() { # shellcheck disable=SC2154 local targetCollationName="${optionCollationName:-${defaultTargetCollationName}}" # shellcheck disable=SC2154 - local taregtCharacterSet="${optionCharacterSet:-${defaultTargetCharacterSet}}" + local targetCharacterSet="${optionCharacterSet:-${defaultTargetCharacterSet}}" # shellcheck disable=SC2154 Log::displayInfo "create target database ${targetDbName} if needed" #shellcheck disable=SC2016 Database::query dbTargetDatabase \ - "$(printf 'CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET "%s" COLLATE "%s"' "${targetDbName}" "${taregtCharacterSet}" "${targetCollationName}")" + "$(printf 'CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET "%s" COLLATE "%s"' "${targetDbName}" "${targetCharacterSet}" "${targetCollationName}")" if [[ -z "${optionFromAws}" ]]; then Database::setQueryOptions dbTargetDatabase "${dbTargetDatabase['DB_IMPORT_OPTIONS']}" @@ -2456,13 +2541,13 @@ run() { fi Log::displayInfo "import remote to local from file ${remoteDbDumpTempFile}" local -a dbImportStreamOptions=( - --profile "${optionProfile}" \ - --target-dsn "${optionTargetDsn}" \ - --character-set "${taregtCharacterSet}" \ + --profile "${optionProfile}" + --target-dsn "${optionTargetDsn}" + --character-set "${targetCharacterSet}" ) if [[ -n "${optionTables:-}" ]]; then dbImportStreamOptions+=( - --tables "${optionTables}" \ + --tables "${optionTables}" ) fi time ( diff --git a/bin/dbImportProfile b/bin/dbImportProfile index 00f80c00..2147859a 100755 --- a/bin/dbImportProfile +++ b/bin/dbImportProfile @@ -47,6 +47,7 @@ declare -a BASH_FRAMEWORK_ARGV=("$@") declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") # @see https://unix.stackexchange.com/a/386856 +# shellcheck disable=SC2317 interruptManagement() { # restore SIGINT handler trap - INT @@ -76,6 +77,7 @@ TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX) export TMPDIR # temp dir cleaning +# shellcheck disable=SC2317 cleanOnExit() { if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" @@ -134,7 +136,7 @@ Array::wrap() { local -i argNoAnsiLength=0 while (($# > 0)); do - argNoAnsi="$(echo "${arg}" | Filters::removeAnsiCodes)" + argNoAnsi="$(echo "${arg%%*( )}" | Filters::removeAnsiCodes)" ((argNoAnsiLength = ${#argNoAnsi})) || true if (($# < 1 && argNoAnsiLength == 0)); then break @@ -157,7 +159,7 @@ Array::wrap() { if ((currentLineLength == 0 && firstLine == 0)); then echo -n "${indentStr}" fi - echo -e -n "${arg}" + echo -e -n "${arg}" | sed 's/[\t ]*$//g' needEcho="1" ((currentLineLength += argNoAnsiLength)) ((glueLength = ${#glue})) || true @@ -176,7 +178,7 @@ Array::wrap() { fi local -i length ((length = maxLineLength - currentLineLength)) || true - echo -e "${arg:0:${length}}" + echo -e "${arg:0:${length}}" | sed 's/[\t ]*$//g' ((currentLineLength = 0)) || true ((glueLength = 0)) || true arg="${arg:${length}}" @@ -184,7 +186,7 @@ Array::wrap() { else # arg cannot be stored on a whole line, so we add it on next line as a whole echo - echo -e -n "${indentStr}${arg}" + echo -e -n "${indentStr}${arg}" | sed 's/[\t ]*$//g' ((glueLength = ${#glue})) || true ((currentLineLength = argNoAnsiLength)) arg="" # allows to go to next arg @@ -484,6 +486,15 @@ Log::displayInfo() { Log::logInfo "$1" "${type}" } +# @description Display message using warning color (yellow) +# @arg $1 message:String the message to display +Log::displayWarning() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then + echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 + fi + Log::logWarning "$1" +} + # @description Display message using error color (red) and exit immediately with error status 1 # @arg $1 message:String the message to display Log::fatal() { @@ -565,6 +576,7 @@ UI::drawLine() { # @set __HELP_EXAMPLE String to remove # @set __HELP_TITLE String to remove # @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 UI::theme() { local theme="${1-default}" if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then @@ -580,46 +592,46 @@ UI::theme() { ;; esac if [[ "${theme}" = "default" ]]; then - export BASH_FRAMEWORK_THEME="default" + BASH_FRAMEWORK_THEME="default" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __SKIPPED_COLOR='\e[33m' # Yellow - export __DEBUG_COLOR='\e[37m' # Grey - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __HELP_TITLE_COLOR="\e[1;37m" # Bold - export __HELP_OPTION_COLOR="\e[1;34m" # Blue + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color + __RESET_COLOR='\e[0m' # Reset Color # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" + __HELP_TITLE="$(echo -e "\e[1;37m")" # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" + __HELP_NORMAL="$(echo -e "\033[0m")" else - export BASH_FRAMEWORK_THEME="noColor" + BASH_FRAMEWORK_THEME="noColor" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __DEBUG_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __HELP_TITLE_COLOR='' - export __HELP_OPTION_COLOR='' + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' fi } @@ -645,8 +657,8 @@ Linux::requireExecutedAsUser() { # @description check if tty (interactive mode) is active # @noargs # @exitcode 1 if tty not active -# @environment NON_INTERACTIVE if 1 consider as not interactive even if environement is interactive -# @environment INTERACTIVE if 1 consider as interactive even if environement is not interactive +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive # @stderr diagnostic information + help if second argument is provided Assert::tty() { if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then @@ -1012,6 +1024,14 @@ Log::logMessage() { fi } +# @description log message to file +# @arg $1 message:String the message to display +Log::logWarning() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then + Log::logMessage "${2:-WARNING}" "$1" + fi +} + # @description To be called before logging in the log file # @arg $1 file:string log file name # @arg $2 maxLogFilesCount:int maximum number of log files @@ -1122,15 +1142,6 @@ Framework::createTempFile() { mktemp -p "${TMPDIR:-/tmp}" -t "${1:-}.XXXXXXXXXXXX" } -# @description Display message using warning color (yellow) -# @arg $1 message:String the message to display -Log::displayWarning() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then - echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 - fi - Log::logWarning "$1" -} - # @description log message to file # @arg $1 message:String the message to display Log::logSkipped() { @@ -1155,14 +1166,6 @@ Array::contains() { return 1 } -# @description log message to file -# @arg $1 message:String the message to display -Log::logWarning() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then - Log::logMessage "${2:-WARNING}" "$1" - fi -} - # @description ensure command realpath is available # @exitcode 1 if realpath command not available # @stderr diagnostics information is displayed @@ -1197,16 +1200,26 @@ Compiler::Facade::requireCommandBinDir Linux::requireExecutedAsUser # @require Compiler::Facade::requireCommandBinDir +# shellcheck disable=SC2034 + +# default values +declare optionProfile="" +declare fromDbName="" +declare optionFromDsn="default.remote" +declare optionRatio=70 + +# other configuration +declare copyrightBeginYear="2020" +declare PROFILES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/dbImportProfiles" +declare HOME_PROFILES_DIR="${HOME}/.bash-tools/dbImportProfiles" declare -a BASH_FRAMEWORK_ARGV_FILTERED=() copyrightCallback() { - local years - years="$(date +%Y)" - if [[ -n "${copyrightBeginYear}" && "${copyrightBeginYear}" != "${years}" ]]; then - years="${copyrightBeginYear}-${years}" + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" fi - echo "Copyright (c) ${years} François Chastanet" + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" } # shellcheck disable=SC2317 # if function is overridden @@ -1380,6 +1393,29 @@ optionBashFrameworkConfigCallback() { fi } +defaultFrameworkConfig="$( + cat <<'EOF' +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$" +# Source directories +FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" +) + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +EOF +)" + commandOptionParseFinished() { if [[ -z "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then BASH_FRAMEWORK_ENV_FILES=() @@ -1399,8 +1435,16 @@ commandOptionParseFinished() { # shellcheck disable=SC2034 BASH_FRAMEWORK_CONFIG_FILE="" # shellcheck source=/.framework-config - Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || - Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config file" + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || { + # load default template framework config + if [[ ! -f "${PERSISTENT_TMPDIR}/.framework-config" ]]; then + echo "${defaultFrameworkConfig}" > "${PERSISTENT_TMPDIR}/.framework-config" + fi + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${PERSISTENT_TMPDIR}" || { + Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config.default file" + } + Log::displayWarning "Command ${SCRIPT_NAME} - Load default .framework-config file - ${PERSISTENT_TMPDIR}/.framework-config" + } fi if [[ "${optionConfig}" = "1" ]]; then @@ -1455,6 +1499,7 @@ dbImportProfileCommand() { ((options_parse_optionParsedCountOptionDisplayLevel = 0)) || true local -i options_parse_argParsedCountFromDbName ((options_parse_argParsedCountFromDbName = 0)) || true + # shellcheck disable=SC2034 local -i options_parse_parsedArgIndex=0 while (($# > 0)); do local options_parse_arg="$1" @@ -1473,6 +1518,7 @@ dbImportProfileCommand() { return 1 fi ((++options_parse_optionParsedCountOptionProfile)) + # shellcheck disable=SC2034 optionProfile="$1" ;; # Option 2/17 @@ -1488,6 +1534,7 @@ dbImportProfileCommand() { return 1 fi ((++options_parse_optionParsedCountOptionFromDsn)) + # shellcheck disable=SC2034 optionFromDsn="$1" ;; # Option 3/17 @@ -1503,6 +1550,7 @@ dbImportProfileCommand() { return 1 fi ((++options_parse_optionParsedCountOptionRatio)) + # shellcheck disable=SC2034 optionRatio="$1" ;; # Option 4/17 @@ -1518,12 +1566,14 @@ dbImportProfileCommand() { return 1 fi ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 optionBashFrameworkConfig="$1" optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" ;; # Option 5/17 # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' --config) + # shellcheck disable=SC2034 optionConfig="1" if ((options_parse_optionParsedCountOptionConfig >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1534,6 +1584,7 @@ dbImportProfileCommand() { # Option 6/17 # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' --verbose | -v) + # shellcheck disable=SC2034 optionInfoVerbose="1" if ((options_parse_optionParsedCountOptionInfoVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1546,6 +1597,7 @@ dbImportProfileCommand() { # Option 7/17 # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vv) + # shellcheck disable=SC2034 optionDebugVerbose="1" if ((options_parse_optionParsedCountOptionDebugVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1558,6 +1610,7 @@ dbImportProfileCommand() { # Option 8/17 # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vvv) + # shellcheck disable=SC2034 optionTraceVerbose="1" if ((options_parse_optionParsedCountOptionTraceVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1583,6 +1636,7 @@ dbImportProfileCommand() { # Option 10/17 # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' --no-color) + # shellcheck disable=SC2034 optionNoColor="1" if ((options_parse_optionParsedCountOptionNoColor >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1609,6 +1663,7 @@ dbImportProfileCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 optionTheme="$1" optionThemeCallback "${options_parse_arg}" "${optionTheme}" updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" @@ -1616,6 +1671,7 @@ dbImportProfileCommand() { # Option 12/17 # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' --help | -h) + # shellcheck disable=SC2034 optionHelp="1" if ((options_parse_optionParsedCountOptionHelp >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1627,6 +1683,7 @@ dbImportProfileCommand() { # Option 13/17 # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' --version) + # shellcheck disable=SC2034 optionVersion="1" if ((options_parse_optionParsedCountOptionVersion >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1638,6 +1695,7 @@ dbImportProfileCommand() { # Option 14/17 # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' --quiet | -q) + # shellcheck disable=SC2034 optionQuiet="1" if ((options_parse_optionParsedCountOptionQuiet >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1664,6 +1722,7 @@ dbImportProfileCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 optionLogLevel="$1" optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" @@ -1681,6 +1740,7 @@ dbImportProfileCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 optionLogFile="$1" optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" @@ -1702,6 +1762,7 @@ dbImportProfileCommand() { return 1 fi ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 optionDisplayLevel="$1" optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" @@ -1724,6 +1785,7 @@ dbImportProfileCommand() { return 1 fi ((++options_parse_argParsedCountFromDbName)) + # shellcheck disable=SC2034 fromDbName="${options_parse_arg}" else if [[ "${argOptDefaultBehavior}" = "0" ]]; then @@ -1756,78 +1818,96 @@ dbImportProfileCommand() { echo -e "${__HELP_TITLE_COLOR}ARGUMENTS:${__RESET_COLOR}" echo -e " ${__HELP_OPTION_COLOR}fromDbName${__HELP_NORMAL} {single} (mandatory)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(the\ name\ of\ the\ source/remote\ database) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--profile${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-p ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--profile${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-p ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(the\ name\ of\ the\ profile\ to\ write\ in\ profiles\ directory.\ \ If\ not\ provided\,\ the\ file\ name\ pattern\ will\ be\ \'auto_\_\.sh\') echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--from-dsn${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-f ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--from-dsn${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-f ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(dsn\ to\ use\ for\ source\ database\ \(Default:\ default.remote\)\ if\ not\ provided\,\ the\ file\ name\ pattern\ will\ be\ \'auto_\_\.sh\') echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--ratio${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-r ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--ratio${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-r ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(define\ the\ ratio\ to\ use\ \(0\ to\ 100%\ -\ default\ 70\).\ \ 0\ means\ profile\ will\ filter\ out\ all\ the\ tables.\ \ 100\ means\ profile\ will\ keep\ all\ the\ tables.\ \ Eg:\ 70\ means\ that\ tables\ with\ size\(table+index\)\ that\ are\ greater\ that\ 70%\ of\ the\ max\ table\ size\ will\ be\ excluded.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(use\ alternate\ bash\ framework\ configuration.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ configuration) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} (optional)" + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Load\ the\ specified\ env\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(choose\ color\ theme\ \(default\,\ default-force\ or\ noColor\)\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ this\ command\ help) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Print\ version\ information\ and\ quit) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(set\ display\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e """ @@ -1863,17 +1943,6 @@ ${dsnList}""" fi } -# default values -declare optionProfile="" -declare fromDbName="" # old FROM_DB -declare optionFromDsn="default.remote" # old FROM_DSN -declare optionRatio=70 # old RATIO - -# other configuration -declare copyrightBeginYear="2020" -declare PROFILES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/dbImportProfiles" -declare HOME_PROFILES_DIR="${HOME}/.bash-tools/dbImportProfiles" - optionHelpCallback() { local profilesList="" local dsnList="" @@ -1902,7 +1971,8 @@ dbImportProfileCommandCallback() { fi } -# shellcheck disable=SC2154 +dbImportProfileCommand parse "${BASH_FRAMEWORK_ARGV[@]}" + read -r -d '' QUERY < 0)); do - argNoAnsi="$(echo "${arg}" | Filters::removeAnsiCodes)" + argNoAnsi="$(echo "${arg%%*( )}" | Filters::removeAnsiCodes)" ((argNoAnsiLength = ${#argNoAnsi})) || true if (($# < 1 && argNoAnsiLength == 0)); then break @@ -173,7 +175,7 @@ Array::wrap() { if ((currentLineLength == 0 && firstLine == 0)); then echo -n "${indentStr}" fi - echo -e -n "${arg}" + echo -e -n "${arg}" | sed 's/[\t ]*$//g' needEcho="1" ((currentLineLength += argNoAnsiLength)) ((glueLength = ${#glue})) || true @@ -192,7 +194,7 @@ Array::wrap() { fi local -i length ((length = maxLineLength - currentLineLength)) || true - echo -e "${arg:0:${length}}" + echo -e "${arg:0:${length}}" | sed 's/[\t ]*$//g' ((currentLineLength = 0)) || true ((glueLength = 0)) || true arg="${arg:${length}}" @@ -200,7 +202,7 @@ Array::wrap() { else # arg cannot be stored on a whole line, so we add it on next line as a whole echo - echo -e -n "${indentStr}${arg}" + echo -e -n "${indentStr}${arg}" | sed 's/[\t ]*$//g' ((glueLength = ${#glue})) || true ((currentLineLength = argNoAnsiLength)) arg="" # allows to go to next arg @@ -547,6 +549,15 @@ Log::displayInfo() { Log::logInfo "$1" "${type}" } +# @description Display message using warning color (yellow) +# @arg $1 message:String the message to display +Log::displayWarning() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then + echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 + fi + Log::logWarning "$1" +} + # @description Display message using error color (red) and exit immediately with error status 1 # @arg $1 message:String the message to display Log::fatal() { @@ -628,6 +639,7 @@ UI::drawLine() { # @set __HELP_EXAMPLE String to remove # @set __HELP_TITLE String to remove # @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 UI::theme() { local theme="${1-default}" if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then @@ -643,46 +655,46 @@ UI::theme() { ;; esac if [[ "${theme}" = "default" ]]; then - export BASH_FRAMEWORK_THEME="default" + BASH_FRAMEWORK_THEME="default" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __SKIPPED_COLOR='\e[33m' # Yellow - export __DEBUG_COLOR='\e[37m' # Grey - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __HELP_TITLE_COLOR="\e[1;37m" # Bold - export __HELP_OPTION_COLOR="\e[1;34m" # Blue + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color + __RESET_COLOR='\e[0m' # Reset Color # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" + __HELP_TITLE="$(echo -e "\e[1;37m")" # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" + __HELP_NORMAL="$(echo -e "\033[0m")" else - export BASH_FRAMEWORK_THEME="noColor" + BASH_FRAMEWORK_THEME="noColor" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __DEBUG_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __HELP_TITLE_COLOR='' - export __HELP_OPTION_COLOR='' + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' fi } @@ -746,8 +758,8 @@ Linux::requireExecutedAsUser() { # @description check if tty (interactive mode) is active # @noargs # @exitcode 1 if tty not active -# @environment NON_INTERACTIVE if 1 consider as not interactive even if environement is interactive -# @environment INTERACTIVE if 1 consider as interactive even if environement is not interactive +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive # @stderr diagnostic information + help if second argument is provided Assert::tty() { if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then @@ -1007,15 +1019,6 @@ Log::displaySkipped() { Log::logSkipped "$1" } -# @description Display message using warning color (yellow) -# @arg $1 message:String the message to display -Log::displayWarning() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then - echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 - fi - Log::logWarning "$1" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -1073,6 +1076,14 @@ Log::logMessage() { fi } +# @description log message to file +# @arg $1 message:String the message to display +Log::logWarning() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then + Log::logMessage "${2:-WARNING}" "$1" + fi +} + # @description To be called before logging in the log file # @arg $1 file:string log file name # @arg $2 maxLogFilesCount:int maximum number of log files @@ -1213,14 +1224,6 @@ Log::logSkipped() { fi } -# @description log message to file -# @arg $1 message:String the message to display -Log::logWarning() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then - Log::logMessage "${2:-WARNING}" "$1" - fi -} - # @description ensure command realpath is available # @exitcode 1 if realpath command not available # @stderr diagnostics information is displayed @@ -1255,17 +1258,29 @@ Compiler::Facade::requireCommandBinDir Linux::requireExecutedAsUser # @require Compiler::Facade::requireCommandBinDir -# shellcheck disable=SC2154 +# shellcheck disable=SC2034 + +# default values +declare optionProfile="" +declare argTargetDbName="" +declare argDumpFile="" +declare optionTargetDsn="" +declare optionCharacterSet="" +declare defaultTargetCharacterSet="" +declare profileCommandFile="" + +# other configuration +declare copyrightBeginYear="2020" +declare PROFILES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/dbImportProfiles" +declare HOME_PROFILES_DIR="${HOME}/.bash-tools/dbImportProfiles" declare -a BASH_FRAMEWORK_ARGV_FILTERED=() copyrightCallback() { - local years - years="$(date +%Y)" - if [[ -n "${copyrightBeginYear}" && "${copyrightBeginYear}" != "${years}" ]]; then - years="${copyrightBeginYear}-${years}" + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" fi - echo "Copyright (c) ${years} François Chastanet" + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" } # shellcheck disable=SC2317 # if function is overridden @@ -1439,6 +1454,29 @@ optionBashFrameworkConfigCallback() { fi } +defaultFrameworkConfig="$( + cat <<'EOF' +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$" +# Source directories +FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" +) + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +EOF +)" + commandOptionParseFinished() { if [[ -z "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then BASH_FRAMEWORK_ENV_FILES=() @@ -1458,8 +1496,16 @@ commandOptionParseFinished() { # shellcheck disable=SC2034 BASH_FRAMEWORK_CONFIG_FILE="" # shellcheck source=/.framework-config - Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || - Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config file" + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || { + # load default template framework config + if [[ ! -f "${PERSISTENT_TMPDIR}/.framework-config" ]]; then + echo "${defaultFrameworkConfig}" > "${PERSISTENT_TMPDIR}/.framework-config" + fi + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${PERSISTENT_TMPDIR}" || { + Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config.default file" + } + Log::displayWarning "Command ${SCRIPT_NAME} - Load default .framework-config file - ${PERSISTENT_TMPDIR}/.framework-config" + } fi if [[ "${optionConfig}" = "1" ]]; then @@ -1470,11 +1516,11 @@ commandOptionParseFinished() { # default values declare optionProfile="default" declare optionTables="" -declare profileCommand="" +declare profileCommandFile="" profileOptionHelpCallback() { echo "the name of the profile to use in order to include or exclude tables" - echo "(if not specified ${HOME_PROFILES_DIR}/default.sh is used if exists otherwise ${PROFILES_DIR}/default.sh)" + echo "(if not specified in default.sh from 'User profiles directory' if exists or 'Default profiles directory')" } optionTablesCallback() { @@ -1484,9 +1530,9 @@ optionTablesCallback() { } profileOptionCallback() { - local -a profilesList - readarray -t profilesList < <(Conf::getMergedList "dbImportProfiles" "sh" "" || true) - if ! Array::contains "$2" "${profilesList[@]}"; then + local -a profilesArray + readarray -t profilesArray < <(Conf::getMergedList "dbImportProfiles" "sh" "" || true) + if ! Array::contains "$2" "${profilesArray[@]}"; then Log::displayError "${SCRIPT_NAME} - invalid profile '$2' provided" return 1 fi @@ -1500,7 +1546,7 @@ initProfileCommandCallback() { local profileMsgInfo # shellcheck disable=SC2154 if [[ "${optionProfile}" = 'default' && -n "${optionTables}" ]]; then - profileCommand=$(Framework::createTempFile "profileCmd.XXXXXXXXXXXX") + profileCommandFile=$(Framework::createTempFile "profileCmd.XXXXXXXXXXXX") profileMsgInfo="only ${optionTables} will be imported" ( echo '#!/usr/bin/env bash' @@ -1510,17 +1556,17 @@ initProfileCommandCallback() { # tables option not specified, we will import all tables of the profile echo 'cat' fi - ) >"${profileCommand}" + ) >"${profileCommandFile}" else - profileCommand="$(Conf::getAbsoluteFile "dbImportProfiles" "${optionProfile}" "sh")" || exit 1 - profileMsgInfo="Using profile ${profileCommand}" + profileCommandFile="$(Conf::getAbsoluteFile "dbImportProfiles" "${optionProfile}" "sh")" || exit 1 + profileMsgInfo="Using profile ${profileCommandFile}" fi - chmod +x "${profileCommand}" + chmod +x "${profileCommandFile}" Log::displayInfo "${profileMsgInfo}" } -declare optionTargetDsn="default.local" # old TARGET_DSN -declare optionCharacterSet="" # old CHARACTER_SET +declare optionTargetDsn="default.local" +declare optionCharacterSet="" declare defaultTargetCharacterSet="utf8" initializeDefaultTargetMysqlOptions() { @@ -1591,6 +1637,7 @@ dbImportStreamCommand() { ((options_parse_argParsedCountArgDumpFile = 0)) || true local -i options_parse_argParsedCountArgTargetDbName ((options_parse_argParsedCountArgTargetDbName = 0)) || true + # shellcheck disable=SC2034 local -i options_parse_parsedArgIndex=0 while (($# > 0)); do local options_parse_arg="$1" @@ -1609,6 +1656,7 @@ dbImportStreamCommand() { return 1 fi ((++options_parse_optionParsedCountOptionProfile)) + # shellcheck disable=SC2034 optionProfile="$1" profileOptionCallback "${options_parse_arg}" "${optionProfile}" ;; @@ -1625,6 +1673,7 @@ dbImportStreamCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTables)) + # shellcheck disable=SC2034 optionTables="$1" optionTablesCallback "${options_parse_arg}" "${optionTables}" ;; @@ -1641,6 +1690,7 @@ dbImportStreamCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTargetDsn)) + # shellcheck disable=SC2034 optionTargetDsn="$1" ;; # Option 4/18 @@ -1656,6 +1706,7 @@ dbImportStreamCommand() { return 1 fi ((++options_parse_optionParsedCountOptionCharacterSet)) + # shellcheck disable=SC2034 optionCharacterSet="$1" ;; # Option 5/18 @@ -1671,12 +1722,14 @@ dbImportStreamCommand() { return 1 fi ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 optionBashFrameworkConfig="$1" optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" ;; # Option 6/18 # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' --config) + # shellcheck disable=SC2034 optionConfig="1" if ((options_parse_optionParsedCountOptionConfig >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1687,6 +1740,7 @@ dbImportStreamCommand() { # Option 7/18 # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' --verbose | -v) + # shellcheck disable=SC2034 optionInfoVerbose="1" if ((options_parse_optionParsedCountOptionInfoVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1699,6 +1753,7 @@ dbImportStreamCommand() { # Option 8/18 # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vv) + # shellcheck disable=SC2034 optionDebugVerbose="1" if ((options_parse_optionParsedCountOptionDebugVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1711,6 +1766,7 @@ dbImportStreamCommand() { # Option 9/18 # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vvv) + # shellcheck disable=SC2034 optionTraceVerbose="1" if ((options_parse_optionParsedCountOptionTraceVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1736,6 +1792,7 @@ dbImportStreamCommand() { # Option 11/18 # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' --no-color) + # shellcheck disable=SC2034 optionNoColor="1" if ((options_parse_optionParsedCountOptionNoColor >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1762,6 +1819,7 @@ dbImportStreamCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 optionTheme="$1" optionThemeCallback "${options_parse_arg}" "${optionTheme}" updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" @@ -1769,6 +1827,7 @@ dbImportStreamCommand() { # Option 13/18 # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' --help | -h) + # shellcheck disable=SC2034 optionHelp="1" if ((options_parse_optionParsedCountOptionHelp >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1780,6 +1839,7 @@ dbImportStreamCommand() { # Option 14/18 # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' --version) + # shellcheck disable=SC2034 optionVersion="1" if ((options_parse_optionParsedCountOptionVersion >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1791,6 +1851,7 @@ dbImportStreamCommand() { # Option 15/18 # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' --quiet | -q) + # shellcheck disable=SC2034 optionQuiet="1" if ((options_parse_optionParsedCountOptionQuiet >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1817,6 +1878,7 @@ dbImportStreamCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 optionLogLevel="$1" optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" @@ -1834,6 +1896,7 @@ dbImportStreamCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 optionLogFile="$1" optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" @@ -1855,6 +1918,7 @@ dbImportStreamCommand() { return 1 fi ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 optionDisplayLevel="$1" optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" @@ -1877,6 +1941,7 @@ dbImportStreamCommand() { return 1 fi ((++options_parse_argParsedCountArgDumpFile)) + # shellcheck disable=SC2034 argDumpFile="${options_parse_arg}" # Argument 2/2 # Argument argTargetDbName min 1 max 1 authorizedValues '' regexp '' @@ -1886,6 +1951,7 @@ dbImportStreamCommand() { return 1 fi ((++options_parse_argParsedCountArgTargetDbName)) + # shellcheck disable=SC2034 argTargetDbName="${options_parse_arg}" else if [[ "${argOptDefaultBehavior}" = "0" ]]; then @@ -1918,91 +1984,113 @@ dbImportStreamCommand() { echo -e "$(Array::wrap " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" "${SCRIPT_NAME}" "[OPTIONS]" "[ARGUMENTS]")" echo -e "$(Array::wrap " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" \ "${SCRIPT_NAME}" \ - "[--profile|-p ]" "[--tables ]" "[--target-dsn|-t ]" "[--character-set|-c ]" "[--bash-framework-config ]" "[--config]" "[--verbose|-v]" "[-vv]" "[-vvv]" "[--env-file ]" "[--no-color]" "[--theme ]" "[--help|-h]" "[--version]" "[--quiet|-q]" "[--log-level ]" "[--log-file ]" "[--display-level ]")" + "[--profile|-p ]" "[--tables ]" "[--target-dsn|-t ]" "[--character-set|-c ]" "[--bash-framework-config ]" "[--config]" "[--verbose|-v]" "[-vv]" "[-vvv]" "[--env-file ]" "[--no-color]" "[--theme ]" "[--help|-h]" "[--version]" "[--quiet|-q]" "[--log-level ]" "[--log-file ]" "[--display-level ]")" echo echo -e "${__HELP_TITLE_COLOR}ARGUMENTS:${__RESET_COLOR}" echo -e " ${__HELP_OPTION_COLOR}argDumpFile${__HELP_NORMAL} {single} (mandatory)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(the\ of\ the\ file\ that\ will\ be\ streamed\ through\ mysql) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}argTargetDbName${__HELP_NORMAL} {single} (mandatory)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(the\ name\ of\ the\ mysql\ target\ database) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}PROFILE OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--profile${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-p ${__HELP_NORMAL} (optional) (at most 1 times)" - echo -e " $(Array::wrap ' ' 76 4 $(profileOptionHelpCallback))" - printf " %b\n" "${__HELP_OPTION_COLOR}--tables ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--profile${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-p ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054,SC2206 + mapfile -t helpArray < <(profileOptionHelpCallback) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--tables ${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 helpArray=(import\ only\ table\ specified\ in\ the\ list.\ \ If\ aws\ mode\,\ ignore\ profile\ option) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}TARGET OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--target-dsn${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-t ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--target-dsn${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-t ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(dsn\ to\ use\ for\ target\ database\ \(Default:\ default.local\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--character-set${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-c ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--character-set${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-c ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(change\ the\ character\ set\ used\ during\ database\ creation\ \(default\ value:\ utf8\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(use\ alternate\ bash\ framework\ configuration.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ configuration) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} (optional)" + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Load\ the\ specified\ env\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(choose\ color\ theme\ \(default\,\ default-force\ or\ noColor\)\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ this\ command\ help) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Print\ version\ information\ and\ quit) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(set\ display\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e """ @@ -2038,15 +2126,6 @@ ${dsnList}""" fi } -# default values -declare optionProfile="" -declare argTargetDbName="" - -# other configuration -declare copyrightBeginYear="2020" -declare PROFILES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/dbImportProfiles" -declare HOME_PROFILES_DIR="${HOME}/.bash-tools/dbImportProfiles" - optionHelpCallback() { local profilesList="" local dsnList="" @@ -2066,6 +2145,9 @@ dbImportStreamCommandCallback() { fi } +dbImportStreamCommand parse "${BASH_FRAMEWORK_ARGV[@]}" + +declare awkScript awkScript="$( cat <<'EOF' BEGIN{ @@ -2110,8 +2192,6 @@ BEGIN{ EOF )" -dbImportStreamCommand parse "${BASH_FRAMEWORK_ARGV[@]}" - # @require Linux::requireExecutedAsUser run() { @@ -2124,15 +2204,13 @@ run() { # create db instances declare -Agx dbTargetInstance - # shellcheck disable=SC2154 Database::newInstance dbTargetInstance "${optionTargetDsn}" - Database::setQueryOptions dbTargetInstance "${dbTargetDatabase[QUERY_OPTIONS]} --connect-timeout=5" + Database::setQueryOptions dbTargetInstance "${dbTargetInstance[QUERY_OPTIONS]} --connect-timeout=5" Log::displayInfo "Using target dsn ${dbTargetInstance['DSN_FILE']}" initializeDefaultTargetMysqlOptions dbTargetInstance "${argTargetDbName}" # TODO character set should be retrieved from dump files if possible - # shellcheck disable=SC2154 declare remoteCharacterSet="${optionCharacterSet:-${defaultRemoteCharacterSet}}" # shellcheck disable=2086 @@ -2148,13 +2226,13 @@ run() { if [[ "${status}" -eq "141" ]]; then true; else exit "${status}"; fi ) | awk \ - -v PROFILE_COMMAND="${profileCommand}" \ + -v PROFILE_COMMAND="${profileCommandFile}" \ -v CHARACTER_SET="${remoteCharacterSet}" \ --source "${awkScript}" \ - | mysql \ - "--defaults-extra-file=${dbTargetInstance['AUTH_FILE']}" \ - ${dbTargetInstance['DB_IMPORT_OPTIONS']} \ - "${argTargetDbName}" || exit $? + "--defaults-extra-file=${dbTargetInstance['AUTH_FILE']}" \ + ${dbTargetInstance['DB_IMPORT_OPTIONS']} \ + "${argTargetDbName}" || exit $? } if [[ "${BASH_FRAMEWORK_QUIET_MODE:-0}" = "1" ]]; then diff --git a/bin/dbQueryAllDatabases b/bin/dbQueryAllDatabases index a684350d..7b5231f4 100755 --- a/bin/dbQueryAllDatabases +++ b/bin/dbQueryAllDatabases @@ -48,6 +48,7 @@ declare -a BASH_FRAMEWORK_ARGV=("$@") declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") # @see https://unix.stackexchange.com/a/386856 +# shellcheck disable=SC2317 interruptManagement() { # restore SIGINT handler trap - INT @@ -77,6 +78,7 @@ TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX) export TMPDIR # temp dir cleaning +# shellcheck disable=SC2317 cleanOnExit() { if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" @@ -135,7 +137,7 @@ Array::wrap() { local -i argNoAnsiLength=0 while (($# > 0)); do - argNoAnsi="$(echo "${arg}" | Filters::removeAnsiCodes)" + argNoAnsi="$(echo "${arg%%*( )}" | Filters::removeAnsiCodes)" ((argNoAnsiLength = ${#argNoAnsi})) || true if (($# < 1 && argNoAnsiLength == 0)); then break @@ -158,7 +160,7 @@ Array::wrap() { if ((currentLineLength == 0 && firstLine == 0)); then echo -n "${indentStr}" fi - echo -e -n "${arg}" + echo -e -n "${arg}" | sed 's/[\t ]*$//g' needEcho="1" ((currentLineLength += argNoAnsiLength)) ((glueLength = ${#glue})) || true @@ -177,7 +179,7 @@ Array::wrap() { fi local -i length ((length = maxLineLength - currentLineLength)) || true - echo -e "${arg:0:${length}}" + echo -e "${arg:0:${length}}" | sed 's/[\t ]*$//g' ((currentLineLength = 0)) || true ((glueLength = 0)) || true arg="${arg:${length}}" @@ -185,7 +187,7 @@ Array::wrap() { else # arg cannot be stored on a whole line, so we add it on next line as a whole echo - echo -e -n "${indentStr}${arg}" + echo -e -n "${indentStr}${arg}" | sed 's/[\t ]*$//g' ((glueLength = ${#glue})) || true ((currentLineLength = argNoAnsiLength)) arg="" # allows to go to next arg @@ -530,6 +532,15 @@ Log::displayInfo() { Log::logInfo "$1" "${type}" } +# @description Display message using warning color (yellow) +# @arg $1 message:String the message to display +Log::displayWarning() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then + echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 + fi + Log::logWarning "$1" +} + # @description Display message using error color (red) and exit immediately with error status 1 # @arg $1 message:String the message to display Log::fatal() { @@ -611,6 +622,7 @@ UI::drawLine() { # @set __HELP_EXAMPLE String to remove # @set __HELP_TITLE String to remove # @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 UI::theme() { local theme="${1-default}" if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then @@ -626,46 +638,46 @@ UI::theme() { ;; esac if [[ "${theme}" = "default" ]]; then - export BASH_FRAMEWORK_THEME="default" + BASH_FRAMEWORK_THEME="default" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __SKIPPED_COLOR='\e[33m' # Yellow - export __DEBUG_COLOR='\e[37m' # Grey - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __HELP_TITLE_COLOR="\e[1;37m" # Bold - export __HELP_OPTION_COLOR="\e[1;34m" # Blue + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color + __RESET_COLOR='\e[0m' # Reset Color # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" + __HELP_TITLE="$(echo -e "\e[1;37m")" # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" + __HELP_NORMAL="$(echo -e "\033[0m")" else - export BASH_FRAMEWORK_THEME="noColor" + BASH_FRAMEWORK_THEME="noColor" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __DEBUG_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __HELP_TITLE_COLOR='' - export __HELP_OPTION_COLOR='' + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' fi } @@ -743,8 +755,8 @@ Linux::requireExecutedAsUser() { # @description check if tty (interactive mode) is active # @noargs # @exitcode 1 if tty not active -# @environment NON_INTERACTIVE if 1 consider as not interactive even if environement is interactive -# @environment INTERACTIVE if 1 consider as interactive even if environement is not interactive +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive # @stderr diagnostic information + help if second argument is provided Assert::tty() { if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then @@ -1043,15 +1055,6 @@ Log::displaySkipped() { Log::logSkipped "$1" } -# @description Display message using warning color (yellow) -# @arg $1 message:String the message to display -Log::displayWarning() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then - echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 - fi - Log::logWarning "$1" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -1109,6 +1112,14 @@ Log::logMessage() { fi } +# @description log message to file +# @arg $1 message:String the message to display +Log::logWarning() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then + Log::logMessage "${2:-WARNING}" "$1" + fi +} + # @description To be called before logging in the log file # @arg $1 file:string log file name # @arg $2 maxLogFilesCount:int maximum number of log files @@ -1257,14 +1268,6 @@ Log::logSkipped() { fi } -# @description log message to file -# @arg $1 message:String the message to display -Log::logWarning() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then - Log::logMessage "${2:-WARNING}" "$1" - fi -} - # @description ensure command realpath is available # @exitcode 1 if realpath command not available # @stderr diagnostics information is displayed @@ -1292,8 +1295,8 @@ Array::contains() { # @require Compiler::Embed::requireEmbedBinDir -declare -gx embed_function_DbQueryOneDatabase="${PERSISTENT_TMPDIR:-/tmp}/bin/12401fa8f30e1b476a84ecd9daf36aae/dbQueryOneDatabase" -declare -gx encoded_binary_file_DbQueryOneDatabase="IyEvdXNyL2Jpbi9lbnYgYmFzaAojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgR0VORVJBVEVEIEZBQ0FERSBGUk9NIGh0dHBzOi8vZ2l0aHViLmNvbS9mY2hhc3RhbmV0L2Jhc2gtdG9vbHMvdHJlZS9tYXN0ZXIvLi4vYmFzaC1kZXYtZW52L3ZlbmRvci9iYXNoLXRvb2xzLWZyYW1ld29yay9zcmMvQ29tcGlsZXIvRW1iZWQvZW1iZWRGcmFtZXdvcmtGdW5jdGlvbi5iaW5GaWxlLnRwbAojIERPIE5PVCBFRElUIElUCiMgQGdlbmVyYXRlZAojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjI4OCxTQzIwMzQKIyBCSU5fRklMRT0ke0JJTl9GSUxFfQojIEZBQ0FERQoKIyBlbnN1cmUgdGhhdCBubyB1c2VyIGFsaWFzZXMgY291bGQgaW50ZXJmZXJlIHdpdGgKIyBjb21tYW5kcyB1c2VkIGluIHRoaXMgc2NyaXB0CnVuYWxpYXMgLWEgfHwgdHJ1ZQpzaG9wdCAtdSBleHBhbmRfYWxpYXNlcwoKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0CigoZmFpbHVyZXMgPSAwKSkgfHwgdHJ1ZQoKIyBCYXNoIHdpbGwgcmVtZW1iZXIgJiByZXR1cm4gdGhlIGhpZ2hlc3QgZXhpdCBjb2RlIGluIGEgY2hhaW4gb2YgcGlwZXMuCiMgVGhpcyB3YXkgeW91IGNhbiBjYXRjaCB0aGUgZXJyb3IgaW5zaWRlIHBpcGVzLCBlLmcuIG15c3FsZHVtcCB8IGd6aXAKc2V0IC1vIHBpcGVmYWlsCnNldCAtbyBlcnJleGl0CgojIENvbW1hbmQgU3Vic3RpdHV0aW9uIGNhbiBpbmhlcml0IGVycmV4aXQgb3B0aW9uIHNpbmNlIGJhc2ggdjQuNApzaG9wdCAtcyBpbmhlcml0X2VycmV4aXQgfHwgdHJ1ZQoKIyBhIGxvZyBpcyBnZW5lcmF0ZWQgd2hlbiBhIGNvbW1hbmQgZmFpbHMKc2V0IC1vIGVycnRyYWNlCgojIHVzZSBudWxsZ2xvYiBzbyB0aGF0IChmaWxlKi5waHApIHdpbGwgcmV0dXJuIGFuIGVtcHR5IGFycmF5IGlmIG5vIGZpbGUgbWF0Y2hlcyB0aGUgd2lsZGNhcmQKc2hvcHQgLXMgbnVsbGdsb2IKCiMgZW5zdXJlIHJlZ2V4cCBhcmUgaW50ZXJwcmV0ZWQgd2l0aG91dCBhY2NlbnR1YXRlZCBjaGFyYWN0ZXJzCmV4cG9ydCBMQ19BTEw9UE9TSVgKCmV4cG9ydCBURVJNPXh0ZXJtLTI1NmNvbG9yCgojIGF2b2lkIGludGVyYWN0aXZlIGluc3RhbGwKZXhwb3J0IERFQklBTl9GUk9OVEVORD1ub25pbnRlcmFjdGl2ZQpleHBvcnQgREVCQ09ORl9OT05JTlRFUkFDVElWRV9TRUVOPXRydWUKCiMgc3RvcmUgY29tbWFuZCBhcmd1bWVudHMgZm9yIGxhdGVyIHVzYWdlCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNApkZWNsYXJlIC1hIEJBU0hfRlJBTUVXT1JLX0FSR1Y9KCIkQCIpCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNApkZWNsYXJlIC1hIE9SSUdJTkFMX0JBU0hfRlJBTUVXT1JLX0FSR1Y9KCIkQCIpCgojIEBzZWUgaHR0cHM6Ly91bml4LnN0YWNrZXhjaGFuZ2UuY29tL2EvMzg2ODU2CmludGVycnVwdE1hbmFnZW1lbnQoKSB7CiAgIyByZXN0b3JlIFNJR0lOVCBoYW5kbGVyCiAgdHJhcCAtIElOVAogICMgZW5zdXJlIHRoYXQgQ3RybC1DIGlzIHRyYXBwZWQgYnkgdGhpcyBzY3JpcHQgYW5kIG5vdCBieSBzdWIgcHJvY2VzcwogICMgcmVwb3J0IHRvIHRoZSBwYXJlbnQgdGhhdCB3ZSBoYXZlIGluZGVlZCBiZWVuIGludGVycnVwdGVkCiAga2lsbCAtcyBJTlQgIiQkIgp9CnRyYXAgaW50ZXJydXB0TWFuYWdlbWVudCBJTlQKU0NSSVBUX05BTUU9JHswIyMqL30KUkVBTF9TQ1JJUFRfRklMRT0iJChyZWFkbGluayAtZSAiJChyZWFscGF0aCAiJHtCQVNIX1NPVVJDRVswXX0iKSIpIgpDVVJSRU5UX0RJUj0iJChjZCAiJChyZWFkbGluayAtZSAiJHtSRUFMX1NDUklQVF9GSUxFJS8qfSIpIiAmJiBwd2QgLVApIgoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgVGVtcCBkaXIgbWFuYWdlbWVudAojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKCktFRVBfVEVNUF9GSUxFUz0iJHtLRUVQX1RFTVBfRklMRVM6LTB9IgpleHBvcnQgS0VFUF9URU1QX0ZJTEVTCgojIFBFUlNJU1RFTlRfVE1QRElSIGlzIG5vdCBkZWxldGVkIGJ5IHRyYXBzClBFUlNJU1RFTlRfVE1QRElSPSIke1RNUERJUjotL3RtcH0vYmFzaC1mcmFtZXdvcmsiCmV4cG9ydCBQRVJTSVNURU5UX1RNUERJUgpta2RpciAtcCAiJHtQRVJTSVNURU5UX1RNUERJUn0iCgojIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIwMzQKVE1QRElSPSIkKG1rdGVtcCAtZCAtcCAiJHtQRVJTSVNURU5UX1RNUERJUjotL3RtcH0iIC10IGJhc2gtZnJhbWV3b3JrLSQkLVhYWFhYWCkiCmV4cG9ydCBUTVBESVIKCiMgdGVtcCBkaXIgY2xlYW5pbmcKY2xlYW5PbkV4aXQoKSB7CiAgaWYgW1sgIiR7S0VFUF9URU1QX0ZJTEVTOi0wfSIgPSAiMSIgXV07IHRoZW4KICAgIExvZzo6ZGlzcGxheUluZm8gIktFRVBfVEVNUF9GSUxFUz0xIHRlbXAgZmlsZXMga2VwdCBoZXJlICcke1RNUERJUn0nIgogIGVsaWYgW1sgLW4gIiR7VE1QRElSK3h4eH0iIF1dOyB0aGVuCiAgICBMb2c6OmRpc3BsYXlEZWJ1ZyAiS0VFUF9URU1QX0ZJTEVTPTAgcmVtb3ZpbmcgdGVtcCBmaWxlcyAnJHtUTVBESVJ9JyIKICAgIHJtIC1SZiAiJHtUTVBESVI6LS90bXAvZmFrZX0iID4vZGV2L251bGwgMj4mMQogIGZpCn0KdHJhcCBjbGVhbk9uRXhpdCBFWElUIEhVUCBRVUlUIEFCUlQgVEVSTQoKIyBAcmVxdWlyZSBMaW51eDo6cmVxdWlyZUV4ZWN1dGVkQXNVc2VyCkRiOjpxdWVyeU9uZURhdGFiYXNlKCkgewogICMgcXVlcnksIGRzbkZpbGUgYW5kIG9wdGlvblNlcGFyYXRvciBhcmUgcGFzc2VkIHZpYSBleHBvcnQKICBsb2NhbCBkYj0iJDEiCgogIGxvY2FsIC1BIGRiSW5zdGFuY2UKICBEYXRhYmFzZTo6bmV3SW5zdGFuY2UgZGJJbnN0YW5jZSAiJHtvcHRpb25Gcm9tRHNufSIKICBEYXRhYmFzZTo6c2V0UXVlcnlPcHRpb25zIGRiSW5zdGFuY2UgIiR7ZGJJbnN0YW5jZVtRVUVSWV9PUFRJT05TXX0gLS1jb25uZWN0LXRpbWVvdXQ9NSIKCiAgIyBpZGVudGlmeSBjb2x1bW5zIGhlYWRlcgogIGVjaG8gLW4gIkBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAIgogIERhdGFiYXNlOjpza2lwQ29sdW1uTmFtZXMgZGJJbnN0YW5jZSAwCgogICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjE1NAogIGlmICEgRGF0YWJhc2U6OnF1ZXJ5IGRiSW5zdGFuY2UgIiR7cXVlcnl9IiAiJHtkYn0iIHwgc2VkICJzL1x0LyR7b3B0aW9uU2VwYXJhdG9yfS9nIjsgdGhlbgogICAgTG9nOjpmYXRhbCAiZGF0YWJhc2UgJHtkYn0gZXJyb3IiIDE+JjIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBMb2cgbmFtZXNwYWNlIHByb3ZpZGVzIDIga2luZCBvZiBmdW5jdGlvbnMKIyAtIExvZzo6ZGlzcGxheSogYWxsb3dzIHRvIGRpc3BsYXkgZ2l2ZW4gbWVzc2FnZSB3aXRoCiMgICBnaXZlbiBkaXNwbGF5IGxldmVsCiMgLSBMb2c6OmxvZyogYWxsb3dzIHRvIGxvZyBnaXZlbiBtZXNzYWdlIHdpdGgKIyAgIGdpdmVuIGxvZyBsZXZlbAojIExvZzo6ZGlzcGxheSogZnVuY3Rpb25zIGF1dG9tYXRpY2FsbHkgbG9nIHRoZSBtZXNzYWdlIHRvbwojIEBzZWUgRW52OjpyZXF1aXJlTG9hZCB0byBsb2FkIHRoZSBkaXNwbGF5IGFuZCBsb2cgbGV2ZWwgZnJvbSAuZW52IGZpbGUKCiMgQGRlc2NyaXB0aW9uIGxvZyBsZXZlbCBvZmYKZXhwb3J0IF9fTEVWRUxfT0ZGPTAKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIGVycm9yCmV4cG9ydCBfX0xFVkVMX0VSUk9SPTEKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIHdhcm5pbmcKZXhwb3J0IF9fTEVWRUxfV0FSTklORz0yCiMgQGRlc2NyaXB0aW9uIGxvZyBsZXZlbCBpbmZvCmV4cG9ydCBfX0xFVkVMX0lORk89MwojIEBkZXNjcmlwdGlvbiBsb2cgbGV2ZWwgc3VjY2VzcwpleHBvcnQgX19MRVZFTF9TVUNDRVNTPTMKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIGRlYnVnCmV4cG9ydCBfX0xFVkVMX0RFQlVHPTQKCiMgQGRlc2NyaXB0aW9uIHZlcmJvc2UgbGV2ZWwgb2ZmCmV4cG9ydCBfX1ZFUkJPU0VfTEVWRUxfT0ZGPTAKIyBAZGVzY3JpcHRpb24gdmVyYm9zZSBsZXZlbCBpbmZvCmV4cG9ydCBfX1ZFUkJPU0VfTEVWRUxfSU5GTz0xCiMgQGRlc2NyaXB0aW9uIHZlcmJvc2UgbGV2ZWwgaW5mbwpleHBvcnQgX19WRVJCT1NFX0xFVkVMX0RFQlVHPTIKIyBAZGVzY3JpcHRpb24gdmVyYm9zZSBsZXZlbCBpbmZvCmV4cG9ydCBfX1ZFUkJPU0VfTEVWRUxfVFJBQ0U9MwoKIyBAZGVzY3JpcHRpb24gRGlzcGxheSBtZXNzYWdlIHVzaW5nIGRlYnVnIGNvbG9yIChncmV5KQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlEZWJ1ZygpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUwgPj0gX19MRVZFTF9ERUJVRykpOyB0aGVuCiAgICBlY2hvIC1lICIke19fREVCVUdfQ09MT1J9REVCVUcgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBmaQogIExvZzo6bG9nRGVidWcgIiQxIgp9CgojIEBkZXNjcmlwdGlvbiBEaXNwbGF5IG1lc3NhZ2UgdXNpbmcgaW5mbyBjb2xvciAoYmcgbGlnaHQgYmx1ZS9mZyB3aGl0ZSkKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpkaXNwbGF5SW5mbygpIHsKICBsb2NhbCB0eXBlPSIkezI6LUlORk99IgogIGlmICgoQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCA+PSBfX0xFVkVMX0lORk8pKTsgdGhlbgogICAgZWNobyAtZSAiJHtfX0lORk9fQ09MT1J9JHt0eXBlfSAgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBmaQogIExvZzo6bG9nSW5mbyAiJDEiICIke3R5cGV9Igp9CgojIEBkZXNjcmlwdGlvbiBlbnN1cmUgQ09NTUFORF9CSU5fRElSIGVudiB2YXIgaXMgc2V0CiMgYW5kIFBBVEggY29ycmVjdGx5IHByZXBhcmVkCiMgQG5vYXJncwojIEBzZXQgQ09NTUFORF9CSU5fRElSIHN0cmluZyB0aGUgZGlyZWN0b3J5IHdoZXJlIHRvIGZpbmQgdGhpcyBjb21tYW5kCiMgQHNldCBQQVRIIHN0cmluZyBhZGQgZGlyZWN0b3J5IHdoZXJlIHRvIGZpbmQgdGhpcyBjb21tYW5kIGJpbmFyeQpDb21waWxlcjo6RmFjYWRlOjpyZXF1aXJlQ29tbWFuZEJpbkRpcigpIHsKICBDT01NQU5EX0JJTl9ESVI9IiR7Q1VSUkVOVF9ESVJ9IgogIEVudjo6cGF0aFByZXBlbmQgIiR7Q09NTUFORF9CSU5fRElSfSIKfQoKIyBAZGVzY3JpcHRpb24gY3JlYXRlIGEgbmV3IGRiIGluc3RhbmNlCiMgUmV0dXJucyBpbW1lZGlhdGVseSBpZiB0aGUgaW5zdGFuY2UgaXMgYWxyZWFkeSBpbml0aWFsaXplZAojCiMgQGFyZyAkMSBpbnN0YW5jZU5ld0luc3RhbmNlOiZNYXA8U3RyaW5nLFN0cmluZz4gKHBhc3NlZCBieSByZWZlcmVuY2UpIGRhdGFiYXNlIGluc3RhbmNlIHRvIHVzZQojIEBhcmcgJDIgZHNuOlN0cmluZyBkc24gcHJvZmlsZSAtIGxvYWQgdGhlIGRzbi5lbnYgcHJvZmlsZSBkZWR1Y2VkIHVzaW5nIHJ1bGVzIGRlZmluZWQgaW4gQ29uZjo6Z2V0QWJzb2x1dGVGaWxlCiMKIyBAZXhhbXBsZQojICAgZGVjbGFyZSAtQWd4IGRiSW5zdGFuY2UKIyAgIERhdGFiYXNlOjpuZXdJbnN0YW5jZSBkYkluc3RhbmNlICJkZWZhdWx0LmxvY2FsIgojCiMgQGV4aXRjb2RlIDEgaWYgZG5zIGZpbGUgbm90IGFibGUgdG8gbG9hZGVkCkRhdGFiYXNlOjpuZXdJbnN0YW5jZSgpIHsKICBsb2NhbCAtbiBpbnN0YW5jZU5ld0luc3RhbmNlPSQxCiAgbG9jYWwgZHNuPSIkMiIKICBsb2NhbCBEU05fRklMRQoKICBpZiBbWyAtdiBpbnN0YW5jZU5ld0luc3RhbmNlWydJTklUSUFMSVpFRCddICYmICIke2luc3RhbmNlTmV3SW5zdGFuY2VbJ0lOSVRJQUxJWkVEJ106LTB9IiA9PSAiMSIgXV07IHRoZW4KICAgIHJldHVybgogIGZpCgogICMgZmluYWwgYXV0aCBmaWxlIGdlbmVyYXRlZCBmcm9tIGRucyBmaWxlCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnQVVUSF9GSUxFJ109IiIKICBpbnN0YW5jZU5ld0luc3RhbmNlWydEU05fRklMRSddPSIiCgogICMgY2hlY2sgZHNuIGZpbGUKICBEU05fRklMRT0iJChDb25mOjpnZXRBYnNvbHV0ZUZpbGUgImRzbiIgIiR7ZHNufSIgImVudiIpIiB8fCByZXR1cm4gMQogIERhdGFiYXNlOjpjaGVja0RzbkZpbGUgIiR7RFNOX0ZJTEV9IiB8fCByZXR1cm4gMQogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0RTTl9GSUxFJ109IiR7RFNOX0ZJTEV9IgoKICAjIHNoZWxsY2hlY2sgc291cmNlPS9zcmMvRGF0YWJhc2UvdGVzdHNEYXRhL2Rzbl92YWxpZC5lbnYKICBzb3VyY2UgIiR7aW5zdGFuY2VOZXdJbnN0YW5jZVsnRFNOX0ZJTEUnXX0iCgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1VTRVInXT0iJHtVU0VSfSIKICBpbnN0YW5jZU5ld0luc3RhbmNlWydQQVNTV09SRCddPSIke1BBU1NXT1JEfSIKICBpbnN0YW5jZU5ld0luc3RhbmNlWydIT1NUTkFNRSddPSIke0hPU1ROQU1FfSIKICBpbnN0YW5jZU5ld0luc3RhbmNlWydQT1JUJ109IiR7UE9SVH0iCgogICMgZ2VuZXJhdGUgYXV0aEZpbGUgZm9yIGVhc3kgYXV0aGVudGljYXRpb24KICBpbnN0YW5jZU5ld0luc3RhbmNlWydBVVRIX0ZJTEUnXT0kKG1rdGVtcCAtcCAiJHtUTVBESVI6LS90bXB9IiAtdCAibXlzcWwuWFhYWFhYWFhYWFhYIikKICAoCiAgICBlY2hvICJbY2xpZW50XSIKICAgIGVjaG8gInVzZXIgPSAke1VTRVJ9IgogICAgZWNobyAicGFzc3dvcmQgPSAke1BBU1NXT1JEfSIKICAgIGVjaG8gImhvc3QgPSAke0hPU1ROQU1FfSIKICAgIGVjaG8gInBvcnQgPSAke1BPUlR9IgogICkgPiIke2luc3RhbmNlTmV3SW5zdGFuY2VbJ0FVVEhfRklMRSddfSIKCiAgIyBzb21lIG9mIHRob3NlIHZhbHVlcyBjYW4gYmUgb3ZlcnJpZGRlbiB1c2luZyB0aGUgZHNuIGZpbGUKICAjIFNLSVBfQ09MVU1OX05BTUVTIGVuYWJsZWQgYnkgZGVmYXVsdAogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1NLSVBfQ09MVU1OX05BTUVTJ109IiR7U0tJUF9DT0xVTU5fTkFNRVM6LTF9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1NTTF9PUFRJT05TJ109IiR7TVlTUUxfU1NMX09QVElPTlM6LS0tc3NsLW1vZGU9RElTQUJMRUR9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1FVRVJZX09QVElPTlMnXT0iJHtNWVNRTF9RVUVSWV9PUFRJT05TOi0tLWJhdGNoIC0tcmF3IC0tZGVmYXVsdC1jaGFyYWN0ZXItc2V0PXV0Zjh9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0RVTVBfT1BUSU9OUyddPSIke01ZU1FMX0RVTVBfT1BUSU9OUzotLS1kZWZhdWx0LWNoYXJhY3Rlci1zZXQ9dXRmOCAtLWNvbXByZXNzIC0taGV4LWJsb2IgLS1yb3V0aW5lcyAtLXRyaWdnZXJzIC0tc2luZ2xlLXRyYW5zYWN0aW9uIC0tc2V0LWd0aWQtcHVyZ2VkPU9GRiAtLWNvbHVtbi1zdGF0aXN0aWNzPTAgJHtpbnN0YW5jZU5ld0luc3RhbmNlWydTU0xfT1BUSU9OUyddfX0iCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnREJfSU1QT1JUX09QVElPTlMnXT0iJHtEQl9JTVBPUlRfT1BUSU9OUzotLS1jb25uZWN0LXRpbWVvdXQ9NSAtLWJhdGNoIC0tcmF3IC0tZGVmYXVsdC1jaGFyYWN0ZXItc2V0PXV0Zjh9IgoKICBpbnN0YW5jZU5ld0luc3RhbmNlWydJTklUSUFMSVpFRCddPTEKfQoKIyBAZGVzY3JpcHRpb24gbXlzcWwgcXVlcnkgb24gYSBnaXZlbiBkYgojIEB3YXJuaW5nIGNvdWxkIHVzZSBRVUVSWV9PUFRJT05TIHZhcmlhYmxlIGZyb20gZHNuIGlmIGRlZmluZWQKIyBAZXhhbXBsZQojICAgY2F0IGZpbGUuc3FsIHwgRGF0YWJhc2U6OnF1ZXJ5IC4uLgojIEBhcmcgJDEgaW5zdGFuY2VRdWVyeTomTWFwPFN0cmluZyxTdHJpbmc+IChwYXNzZWQgYnkgcmVmZXJlbmNlKSBkYXRhYmFzZSBpbnN0YW5jZSB0byB1c2UKIyBAYXJnICQyIHNxbFF1ZXJ5OlN0cmluZyAob3B0aW9uYWwpIHNxbCBxdWVyeSBvciBzcWwgZmlsZSB0byBleGVjdXRlLiBpZiBub3QgcHJvdmlkZWQgb3IgZW1wdHksIHRoZSBjb21tYW5kIGNhbiBiZSBwaXBlZAojIEBhcmcgJDMgZGJOYW1lOlN0cmluZyAob3B0aW9uYWwpIHRoZSBkYiBuYW1lCiMKIyBAZXhpdGNvZGUgbXlzcWwgY29tbWFuZCBzdGF0dXMgY29kZQpEYXRhYmFzZTo6cXVlcnkoKSB7CiAgbG9jYWwgLW4gaW5zdGFuY2VRdWVyeT0kMQogIGxvY2FsIC1hIG15c3FsQ29tbWFuZD0oKQogIGxvY2FsIC1hIHF1ZXJ5T3B0aW9ucwoKICBteXNxbENvbW1hbmQrPShteXNxbCkKICBteXNxbENvbW1hbmQrPSgiLS1kZWZhdWx0cy1leHRyYS1maWxlPSR7aW5zdGFuY2VRdWVyeVsnQVVUSF9GSUxFJ119IikKICBJRlM9JyAnIHJlYWQgLXIgLWEgcXVlcnlPcHRpb25zIDw8PCIke2luc3RhbmNlUXVlcnlbJ1FVRVJZX09QVElPTlMnXX0iCiAgbXlzcWxDb21tYW5kKz0oIiR7cXVlcnlPcHRpb25zW0BdfSIpCiAgaWYgW1sgIiR7aW5zdGFuY2VRdWVyeVsnU0tJUF9DT0xVTU5fTkFNRVMnXX0iID0gIjEiIF1dOyB0aGVuCiAgICBteXNxbENvbW1hbmQrPSgiLXMiICItLXNraXAtY29sdW1uLW5hbWVzIikKICBmaQogICMgYWRkIG9wdGlvbmFsIGRiIG5hbWUKICBpZiBbWyAtbiAiJHszK3h9IiBdXTsgdGhlbgogICAgbXlzcWxDb21tYW5kKz0oIiQzIikKICBmaQogICMgYWRkIG9wdGlvbmFsIHNxbCBxdWVyeQogIGlmIFtbIC1uICIkezIreH0iICYmIC1uICIkMiIgJiYgISAtZiAiJDIiIF1dOyB0aGVuCiAgICBteXNxbENvbW1hbmQrPSgiLWUiKQogICAgbXlzcWxDb21tYW5kKz0oIiQyIikKICBmaQogIExvZzo6ZGlzcGxheURlYnVnICIkKHByaW50ZiAiZXhlY3V0ZSBjb21tYW5kOiAnJXMnIiAiJHtteXNxbENvbW1hbmRbKl19IikiCgogIGlmIFtbIC1mICIkMiIgXV07IHRoZW4KICAgICIke215c3FsQ29tbWFuZFtAXX0iIDwiJDIiCiAgZWxzZQogICAgIiR7bXlzcWxDb21tYW5kW0BdfSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBzZXQgdGhlIGdlbmVyYWwgb3B0aW9ucyB0byB1c2Ugb24gbXlzcWwgY29tbWFuZCB0byBxdWVyeSB0aGUgZGF0YWJhc2UKIyBEaWZmZXJzIHRoYW4gc2V0T3B0aW9ucyBpbiB0aGUgd2F5IHRoYXQgdGhlc2Ugb3B0aW9ucyBjb3VsZCBjaGFuZ2UgZWFjaCB0aW1lCiMKIyBAYXJnICQxIGluc3RhbmNlU2V0UXVlcnlPcHRpb25zOiZNYXA8U3RyaW5nLFN0cmluZz4gKHBhc3NlZCBieSByZWZlcmVuY2UpIGRhdGFiYXNlIGluc3RhbmNlIHRvIHVzZQojIEBhcmcgJDIgb3B0aW9uc0xpc3Q6U3RyaW5nIHF1ZXJ5IG9wdGlvbnMgbGlzdApEYXRhYmFzZTo6c2V0UXVlcnlPcHRpb25zKCkgewogIGxvY2FsIC1uIGluc3RhbmNlU2V0UXVlcnlPcHRpb25zPSQxCiAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0CiAgaW5zdGFuY2VTZXRRdWVyeU9wdGlvbnNbJ1FVRVJZX09QVElPTlMnXT0iJDIiCn0KCiMgQGRlc2NyaXB0aW9uIGJ5IGRlZmF1bHQgd2Ugc2tpcCB0aGUgY29sdW1uIG5hbWVzCiMgYnV0IHNvbWV0aW1lcyB3ZSBuZWVkIGNvbHVtbiBuYW1lcyB0byBkaXNwbGF5IHNvbWUgcmVzdWx0cwojIGRpc2FibGUgdGhpcyBvcHRpb24gdGVtcG9yYXJpbHkgYW5kIHRoZW4gcmVzdG9yZSBpdCB0byB0cnVlCiMKIyBAYXJnICQxIGluc3RhbmNlU2V0UXVlcnlPcHRpb25zOiZNYXA8U3RyaW5nLFN0cmluZz4gKHBhc3NlZCBieSByZWZlcmVuY2UpIGRhdGFiYXNlIGluc3RhbmNlIHRvIHVzZQojIEBhcmcgJDIgc2tpcENvbHVtbk5hbWVzOkJvb2xlYW4gMCB0byBkaXNhYmxlLCAxIHRvIGVuYWJsZSAoaGlkZSBjb2x1bW4gbmFtZXMpCkRhdGFiYXNlOjpza2lwQ29sdW1uTmFtZXMoKSB7CiAgbG9jYWwgLW4gaW5zdGFuY2VTa2lwQ29sdW1uTmFtZXM9JDEKICAjIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIwMzQKICBpbnN0YW5jZVNraXBDb2x1bW5OYW1lc1snU0tJUF9DT0xVTU5fTkFNRVMnXT0iJDIiCn0KCiMgQGRlc2NyaXB0aW9uIHByZXBlbmQgZGlyZWN0b3JpZXMgdG8gdGhlIFBBVEggZW52aXJvbm1lbnQgdmFyaWFibGUKIyBAYXJnICRAIGFyZ3M6U3RyaW5nW10gbGlzdCBvZiBkaXJlY3RvcmllcyB0byBwcmVwZW5kCiMgQHNldCBQQVRIIHVwZGF0ZSBQQVRIIHdpdGggdGhlIGRpcmVjdG9yaWVzIHByZXBlbmRlZApFbnY6OnBhdGhQcmVwZW5kKCkgewogIGxvY2FsIGFyZwogIGZvciBhcmcgaW4gIiRAIjsgZG8KICAgIGlmIFtbIC1kICIke2FyZ30iICYmICI6JHtQQVRIfToiICE9ICoiOiR7YXJnfToiKiBdXTsgdGhlbgogICAgICBQQVRIPSIkKHJlYWxwYXRoICIke2FyZ30iKToke1BBVEh9IgogICAgZmkKICBkb25lCn0KCiMgQGRlc2NyaXB0aW9uIERpc3BsYXkgbWVzc2FnZSB1c2luZyBlcnJvciBjb2xvciAocmVkKSBhbmQgZXhpdCBpbW1lZGlhdGVseSB3aXRoIGVycm9yIHN0YXR1cyAxCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6ZmF0YWwoKSB7CiAgZWNobyAtZSAiJHtfX0VSUk9SX0NPTE9SfUZBVEFMICAgLSAkezF9JHtfX1JFU0VUX0NPTE9SfSIgPiYyCiAgTG9nOjpsb2dGYXRhbCAiJDEiCiAgZXhpdCAxCn0KCiMgQGRlc2NyaXB0aW9uIGxvZyBtZXNzYWdlIHRvIGZpbGUKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpsb2dEZWJ1ZygpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+PSBfX0xFVkVMX0RFQlVHKSk7IHRoZW4KICAgIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1ERUJVR30iICIkMSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBsb2cgbWVzc2FnZSB0byBmaWxlCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6bG9nSW5mbygpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+PSBfX0xFVkVMX0lORk8pKTsgdGhlbgogICAgTG9nOjpsb2dNZXNzYWdlICIkezI6LUlORk99IiAiJDEiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gZW5zdXJlIHJ1bm5pbmcgdXNlciBpcyBub3Qgcm9vdAojIEBleGl0Y29kZSAxIGlmIGN1cnJlbnQgdXNlciBpcyByb290CiMgQHN0ZGVyciBkaWFnbm9zdGljcyBpbmZvcm1hdGlvbiBpcyBkaXNwbGF5ZWQKTGludXg6OnJlcXVpcmVFeGVjdXRlZEFzVXNlcigpIHsKICBpZiBbWyAiJChpZCAtdSkiID0gIjAiIF1dOyB0aGVuCiAgICBMb2c6OmZhdGFsICJ0aGlzIHNjcmlwdCBzaG91bGQgYmUgZXhlY3V0ZWQgYXMgbm9ybWFsIHVzZXIiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gZ2V0IGFic29sdXRlIGNvbmYgZmlsZSBmcm9tIHNwZWNpZmllZCBjb25mIGZvbGRlciBkZWR1Y2VkIHVzaW5nIHRoZXNlIHJ1bGVzCiMgICAqIGZyb20gYWJzb2x1dGUgZmlsZSAoaWdub3JlcyA8Y29uZkZvbGRlcj4gYW5kIDxleHRlbnNpb24+KQojICAgKiByZWxhdGl2ZSB0byB3aGVyZSBzY3JpcHQgaXMgZXhlY3V0ZWQgKGlnbm9yZXMgPGNvbmZGb2xkZXI+IGFuZCA8ZXh0ZW5zaW9uPikKIyAgICogZnJvbSBob21lLy5iYXNoLXRvb2xzLzxjb25mRm9sZGVyPgojICAgKiBmcm9tIGZyYW1ld29yayBjb25mLzxjb25mRm9sZGVyPgojCiMgQGFyZyAkMSBjb25mRm9sZGVyOlN0cmluZyB0aGUgZGlyZWN0b3J5IG5hbWUgKG5vdCB0aGUgcGF0aCkgdG8gbGlzdAojIEBhcmcgJDIgY29uZjpTdHJpbmcgZmlsZSB0byB1c2Ugd2l0aG91dCBleHRlbnNpb24KIyBAYXJnICQzIGV4dGVuc2lvbjpTdHJpbmcgdGhlIGV4dGVuc2lvbiAoLnNoIGJ5IGRlZmF1bHQpCiMKIyBAc3Rkb3V0IGFic29sdXRlIGNvbmYgZmlsZW5hbWUKIyBAZXhpdGNvZGUgMSBpZiBmaWxlIGlzIG5vdCBmb3VuZCBpbiBhbnkgbG9jYXRpb24KQ29uZjo6Z2V0QWJzb2x1dGVGaWxlKCkgewogIGxvY2FsIGNvbmZGb2xkZXI9IiQxIgogIGxvY2FsIGNvbmY9IiQyIgogIGxvY2FsIGV4dGVuc2lvbj0iJHszLS5zaH0iCiAgaWYgW1sgLW4gIiR7ZXh0ZW5zaW9ufSIgJiYgIiR7ZXh0ZW5zaW9uOjA6MX0iICE9ICIuIiBdXTsgdGhlbgogICAgZXh0ZW5zaW9uPSIuJHtleHRlbnNpb259IgogIGZpCgogIHRlc3RBYnMoKSB7CiAgICBsb2NhbCByZXN1bHQKICAgIHJlc3VsdD0iJChyZWFscGF0aCAtZSAiJDEiIDI+L2Rldi9udWxsKSIKICAgICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjE4MQogICAgaWYgW1sgIiQ/IiA9ICIwIiAmJiAtZiAiJHtyZXN1bHR9IiBdXTsgdGhlbgogICAgICBlY2hvICIke3Jlc3VsdH0iCiAgICAgIHJldHVybiAwCiAgICBmaQogICAgcmV0dXJuIDEKICB9CgogICMgY29uZiBpcyBhYnNvbHV0ZSBmaWxlIChpbmNsdWRpbmcgZXh0ZW5zaW9uKQogIHRlc3RBYnMgIiR7Y29uZkZvbGRlcn0ke2V4dGVuc2lvbn0iICYmIHJldHVybiAwCiAgIyBjb25mIGlzIGFic29sdXRlIGZpbGUKICB0ZXN0QWJzICIke2NvbmZGb2xkZXJ9IiAmJiByZXR1cm4gMAogICMgY29uZiBpcyBhYnNvbHV0ZSBmaWxlIChpbmNsdWRpbmcgZXh0ZW5zaW9uKQogIHRlc3RBYnMgIiR7Y29uZn0ke2V4dGVuc2lvbn0iICYmIHJldHVybiAwCiAgIyBjb25mIGlzIGFic29sdXRlIGZpbGUKICB0ZXN0QWJzICIke2NvbmZ9IiAmJiByZXR1cm4gMAoKICAjIHJlbGF0aXZlIHRvIHdoZXJlIHNjcmlwdCBpcyBleGVjdXRlZCAoaW5jbHVkaW5nIGV4dGVuc2lvbikKICBpZiBbWyAtbiAiJHtDVVJSRU5UX0RJUit4eHh9IiBdXTsgdGhlbgogICAgdGVzdEFicyAiJChGaWxlOjpjb25jYXRlbmF0ZVBhdGggIiR7Q1VSUkVOVF9ESVJ9IiAiJHtjb25mRm9sZGVyfSIpLyR7Y29uZn0ke2V4dGVuc2lvbn0iICYmIHJldHVybiAwCiAgZmkKICAjIGZyb20gaG9tZS8uYmFzaC10b29scy88Y29uZkZvbGRlcj4KICB0ZXN0QWJzICIkKEZpbGU6OmNvbmNhdGVuYXRlUGF0aCAiJHtIT01FfS8uYmFzaC10b29scyIgIiR7Y29uZkZvbGRlcn0iKS8ke2NvbmZ9JHtleHRlbnNpb259IiAmJiByZXR1cm4gMAoKICBpZiBbWyAtbiAiJHtGUkFNRVdPUktfUk9PVF9ESVIreHh4fSIgXV07IHRoZW4KICAgICMgZnJvbSBmcmFtZXdvcmsgY29uZi88Y29uZkZvbGRlcj4gKGluY2x1ZGluZyBleHRlbnNpb24pCiAgICB0ZXN0QWJzICIkKEZpbGU6OmNvbmNhdGVuYXRlUGF0aCAiJHtGUkFNRVdPUktfUk9PVF9ESVJ9L2NvbmYiICIke2NvbmZGb2xkZXJ9IikvJHtjb25mfSR7ZXh0ZW5zaW9ufSIgJiYgcmV0dXJuIDAKCiAgICAjIGZyb20gZnJhbWV3b3JrIGNvbmYvPGNvbmZGb2xkZXI+CiAgICB0ZXN0QWJzICIkKEZpbGU6OmNvbmNhdGVuYXRlUGF0aCAiJHtGUkFNRVdPUktfUk9PVF9ESVJ9L2NvbmYiICIke2NvbmZGb2xkZXJ9IikvJHtjb25mfSIgJiYgcmV0dXJuIDAKICBmaQoKICAjIGZpbGUgbm90IGZvdW5kCiAgTG9nOjpkaXNwbGF5RXJyb3IgImNvbmYgZmlsZSAnJHtjb25mfScgbm90IGZvdW5kIgoKICByZXR1cm4gMQp9CgojIEBkZXNjcmlwdGlvbiBjaGVjayBpZiBkc24gZmlsZSBoYXMgYWxsIHRoZSBtYW5kYXRvcnkgdmFyaWFibGVzIHNldAojIE1hbmRhdG9yeSB2YXJpYWJsZXMgYXJlOiBIT1NUTkFNRSwgVVNFUiwgUEFTU1dPUkQsIFBPUlQKIwojIEBhcmcgJDEgZHNuRmlsZU5hbWU6U3RyaW5nIGRzbiBhYnNvbHV0ZSBmaWxlbmFtZQojIEBzZXQgSE9TVE5BTUUgbG9hZGVkIGZyb20gZHNuIGZpbGUKIyBAc2V0IFBPUlQgbG9hZGVkIGZyb20gZHNuIGZpbGUKIyBAc2V0IFVTRVIgbG9hZGVkIGZyb20gZHNuIGZpbGUKIyBAc2V0IFBBU1NXT1JEIGxvYWRlZCBmcm9tIGRzbiBmaWxlCiMgQGV4aXRjb2RlIDAgb24gdmFsaWQgZmlsZQojIEBleGl0Y29kZSAxIGlmIG9uZSBvZiB0aGUgcHJvcGVydGllcyBvZiB0aGUgY29uZiBmaWxlIGlzIGludmFsaWQgb3IgaWYgZmlsZSBub3QgZm91bmQKIyBAc3RkZXJyIGxvZyBvdXRwdXQgaWYgZXJyb3IgZm91bmQgaW4gY29uZiBmaWxlCkRhdGFiYXNlOjpjaGVja0RzbkZpbGUoKSB7CiAgbG9jYWwgZHNuRmlsZU5hbWU9IiQxIgogIGlmIFtbICEgLWYgIiR7ZHNuRmlsZU5hbWV9IiBdXTsgdGhlbgogICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IG5vdCBmb3VuZCIKICAgIHJldHVybiAxCiAgZmkKCiAgKAogICAgdW5zZXQgSE9TVE5BTUUgUE9SVCBQQVNTV09SRCBVU0VSCiAgICAjIHNoZWxsY2hlY2sgc291cmNlPS9zcmMvRGF0YWJhc2UvdGVzdHNEYXRhL2Rzbl92YWxpZC5lbnYKICAgIHNvdXJjZSAiJHtkc25GaWxlTmFtZX0iCiAgICBpZiBbWyAteiAke0hPU1ROQU1FK3h9IF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheUVycm9yICJkc24gZmlsZSAke2RzbkZpbGVOYW1lfSA6IEhPU1ROQU1FIG5vdCBwcm92aWRlZCIKICAgICAgcmV0dXJuIDEKICAgIGZpCiAgICBpZiBbWyAteiAiJHtIT1NUTkFNRX0iIF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheVdhcm5pbmcgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogSE9TVE5BTUUgdmFsdWUgbm90IHByb3ZpZGVkIgogICAgZmkKICAgIGlmIFtbICIke0hPU1ROQU1FfSIgPSAibG9jYWxob3N0IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlXYXJuaW5nICJkc24gZmlsZSAke2RzbkZpbGVOYW1lfSA6IGNoZWNrIHRoYXQgSE9TVE5BTUUgc2hvdWxkIG5vdCBiZSAxMjcuMC4wLjEgaW5zdGVhZCBvZiBsb2NhbGhvc3QiCiAgICBmaQogICAgaWYgW1sgLXogIiR7UE9SVCt4fSIgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogUE9SVCBub3QgcHJvdmlkZWQiCiAgICAgIHJldHVybiAxCiAgICBmaQogICAgaWYgISBbWyAke1BPUlR9ID1+IF5bMC05XSskIF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheUVycm9yICJkc24gZmlsZSAke2RzbkZpbGVOYW1lfSA6IFBPUlQgaW52YWxpZCIKICAgICAgcmV0dXJuIDEKICAgIGZpCiAgICBpZiBbWyAteiAiJHtVU0VSK3h9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlFcnJvciAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gOiBVU0VSIG5vdCBwcm92aWRlZCIKICAgICAgcmV0dXJuIDEKICAgIGZpCiAgICBpZiBbWyAteiAiJHtQQVNTV09SRCt4fSIgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogUEFTU1dPUkQgbm90IHByb3ZpZGVkIgogICAgICByZXR1cm4gMQogICAgZmkKICApCn0KCiMgQGRlc2NyaXB0aW9uIGxvZyBtZXNzYWdlIHRvIGZpbGUKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpsb2dGYXRhbCgpIHsKICBMb2c6OmxvZ01lc3NhZ2UgIiR7MjotRkFUQUx9IiAiJDEiCn0KCiMgQGRlc2NyaXB0aW9uIEludGVybmFsOiBjb21tb24gbG9nIG1lc3NhZ2UKIyBAZXhhbXBsZSB0ZXh0CiMgICBbZGF0ZV18W2xldmVsTXNnXXxtZXNzYWdlCiMKIyBAZXhhbXBsZSB0ZXh0CiMgICAyMDIwLTAxLTE5IDE5OjIwOjIxfEVSUk9SICB8bG9nIGVycm9yCiMgICAyMDIwLTAxLTE5IDE5OjIwOjIxfFNLSVBQRUR8bG9nIHNraXBwZWQKIwojIEBhcmcgJDEgbGV2ZWxNc2c6U3RyaW5nIG1lc3NhZ2UncyBsZXZlbCBkZXNjcmlwdGlvbiAoZWc6IFNUQVRVUywgRVJST1IsIC4uLikKIyBAYXJnICQyIG1zZzpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQojIEBlbnYgQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEUgU3RyaW5nIGxvZyBmaWxlIHRvIHVzZSwgZG8gbm90aGluZyBpZiBlbXB0eQojIEBlbnYgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMIGludCBsb2cgbGV2ZWwgbG9nIG9ubHkgaWYgPiBPRkYgb3IgZmF0YWwgbWVzc2FnZXMKIyBAc3RkZXJyIGRpYWdub3N0aWNzIGluZm9ybWF0aW9uIGlzIGRpc3BsYXllZAojIEByZXF1aXJlIEVudjo6cmVxdWlyZUxvYWQKIyBAcmVxdWlyZSBMb2c6OnJlcXVpcmVMb2FkCkxvZzo6bG9nTWVzc2FnZSgpIHsKICBsb2NhbCBsZXZlbE1zZz0iJDEiCiAgbG9jYWwgbXNnPSIkMiIKICBsb2NhbCBkYXRlCgogIGlmIFtbIC1uICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSIgXV0gJiYgKChCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgPiBfX0xFVkVMX09GRikpOyB0aGVuCiAgICBkYXRlPSIkKGRhdGUgJyslWS0lbS0lZCAlSDolTTolUycpIgogICAgdG91Y2ggIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IgogICAgcHJpbnRmICIlc3wlN3N8JXNcbiIgIiR7ZGF0ZX0iICIke2xldmVsTXNnfSIgIiR7bXNnfSIgPj4iJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gY29uY2F0ZW5hdGUgMiBwYXRocyBhbmQgZW5zdXJlIHRoZSBwYXRoIGlzIGNvcnJlY3QgdXNpbmcgcmVhbHBhdGggLW0KIyBAYXJnICQxIGJhc2VQYXRoOlN0cmluZwojIEBhcmcgJDIgc3ViUGF0aDpTdHJpbmcKIyBAcmVxdWlyZSBMaW51eDo6cmVxdWlyZVJlYWxwYXRoQ29tbWFuZApGaWxlOjpjb25jYXRlbmF0ZVBhdGgoKSB7CiAgbG9jYWwgYmFzZVBhdGg9IiQxIgogIGxvY2FsIHN1YlBhdGg9IiQyIgogIGxvY2FsIGZ1bGxQYXRoPSIke2Jhc2VQYXRoOiske2Jhc2VQYXRofS99JHtzdWJQYXRofSIKCiAgcmVhbHBhdGggLW0gIiR7ZnVsbFBhdGh9IiAyPi9kZXYvbnVsbAp9CgojIEBkZXNjcmlwdGlvbiBEaXNwbGF5IG1lc3NhZ2UgdXNpbmcgZXJyb3IgY29sb3IgKHJlZCkKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpkaXNwbGF5RXJyb3IoKSB7CiAgaWYgKChCQVNIX0ZSQU1FV09SS19ESVNQTEFZX0xFVkVMID49IF9fTEVWRUxfRVJST1IpKTsgdGhlbgogICAgZWNobyAtZSAiJHtfX0VSUk9SX0NPTE9SfUVSUk9SICAgLSAkezF9JHtfX1JFU0VUX0NPTE9SfSIgPiYyCiAgZmkKICBMb2c6OmxvZ0Vycm9yICIkMSIKfQoKIyBAZGVzY3JpcHRpb24gRGlzcGxheSBtZXNzYWdlIHVzaW5nIHdhcm5pbmcgY29sb3IgKHllbGxvdykKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpkaXNwbGF5V2FybmluZygpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUwgPj0gX19MRVZFTF9XQVJOSU5HKSk7IHRoZW4KICAgIGVjaG8gLWUgIiR7X19XQVJOSU5HX0NPTE9SfVdBUk4gICAgLSAkezF9JHtfX1JFU0VUX0NPTE9SfSIgPiYyCiAgZmkKICBMb2c6OmxvZ1dhcm5pbmcgIiQxIgp9CgojIEBkZXNjcmlwdGlvbiBlbnN1cmUgZW52IGZpbGVzIGFyZSBsb2FkZWQKIyBAbm9hcmdzCiMgQGV4aXRjb2RlIDEgaWYgZ2V0T3JkZXJlZENvbmZGaWxlcyBmYWlscwojIEBleGl0Y29kZSAyIGlmIG9uZSBvZiBlbnYgZmlsZXMgZmFpbHMgdG8gbG9hZAojIEBzdGRlcnIgZGlhZ25vc3RpY3MgaW5mb3JtYXRpb24gaXMgZGlzcGxheWVkCkVudjo6cmVxdWlyZUxvYWQoKSB7CiAgbG9jYWwgY29uZmlnRmlsZXNTdHIKICBjb25maWdGaWxlc1N0cj0iJChFbnY6OmdldE9yZGVyZWRDb25mRmlsZXMpIiB8fCByZXR1cm4gMQoKICBsb2NhbCAtYSBjb25maWdGaWxlcwogIHJlYWRhcnJheSAtdCBjb25maWdGaWxlcyA8PDwiJHtjb25maWdGaWxlc1N0cn0iCgogICMgaWYgZW1wdHkgc3RyaW5nLCB0aGVyZSB3aWxsIGJlIG9uZSBlbGVtZW50CiAgaWYgKCgkeyNjb25maWdGaWxlc1tAXX0gPT0gMCkpIHx8IFtbIC16ICIke2NvbmZpZ0ZpbGVzU3RyfSIgXV07IHRoZW4KICAgICMgc2hvdWxkIG5vdCBoYXBwZW4sIGFzIHRoZXJlIGlzIGFsd2F5cyBkZWZhdWx0IGZpbGUKICAgIExvZzo6ZGlzcGxheVNraXBwZWQgIm5vIGVudiBmaWxlIHRvIGxvYWQiCiAgICByZXR1cm4gMAogIGZpCgogIEVudjo6bWVyZ2VDb25mRmlsZXMgIiR7Y29uZmlnRmlsZXNbQF19IiB8fCB7CiAgICBMb2c6OmRpc3BsYXlFcnJvciAid2hpbGUgbG9hZGluZyBjb25maWcgZmlsZXM6ICR7Y29uZmlnRmlsZXNbKl19IgogICAgcmV0dXJuIDIKICB9Cn0KCiMgQGRlc2NyaXB0aW9uIGFjdGl2YXRlIG9yIG5vdCBMb2c6OmRpc3BsYXkqIGFuZCBMb2c6OmxvZyogZnVuY3Rpb25zCiMgYmFzZWQgb24gQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCBhbmQgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMCiMgZW52aXJvbm1lbnQgdmFyaWFibGVzIGxvYWRlZCBieSBFbnY6OnJlcXVpcmVMb2FkCiMgdHJ5IHRvIGNyZWF0ZSBsb2cgZmlsZSBhbmQgcm90YXRlIGl0IGlmIG5lY2Vzc2FyeQojIEBub2FyZ3MKIyBAc2V0IEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCBpbnQgdG8gT0ZGIGxldmVsIGlmIEJBU0hfRlJBTUVXT1JLX0xPR19GSUxFIGlzIGVtcHR5IG9yIG5vdCB3cml0YWJsZQojIEBlbnYgQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCBpbnQKIyBAZW52IEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCBpbnQKIyBAZW52IEJBU0hfRlJBTUVXT1JLX0xPR19GSUxFIFN0cmluZwojIEBlbnYgQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEVfTUFYX1JPVEFUSU9OIGludCBkbyBsb2cgcm90YXRpb24gaWYgPiAwCiMgQGV4aXRjb2RlIDAgYWx3YXlzIHN1Y2Nlc3NmdWwKIyBAc3RkZXJyIGRpYWdub3N0aWNzIGluZm9ybWF0aW9uIGFib3V0IGxvZyBmaWxlIGlzIGRpc3BsYXllZAojIEByZXF1aXJlIEVudjo6cmVxdWlyZUxvYWQKIyBAcmVxdWlyZSBVSTo6cmVxdWlyZVRoZW1lCkxvZzo6cmVxdWlyZUxvYWQoKSB7CiAgaWYgW1sgLXogIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEU6LX0iIF1dOyB0aGVuCiAgICBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUw9JHtfX0xFVkVMX09GRn0KICAgIGV4cG9ydCBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwKICBmaQoKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+IF9fTEVWRUxfT0ZGKSk7IHRoZW4KICAgIGlmIFtbICEgLWYgIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IiBdXTsgdGhlbgogICAgICBpZgogICAgICAgICEgbWtkaXIgLXAgIiQoZGlybmFtZSAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iKSIgMj4vZGV2L251bGwgfHwKICAgICAgICAgICEgdG91Y2ggLS1uby1jcmVhdGUgIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IiAyPi9kZXYvbnVsbAogICAgICB0aGVuCiAgICAgICAgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMPSR7X19MRVZFTF9PRkZ9CiAgICAgICAgZWNobyAtZSAiJHtfX0VSUk9SX0NPTE9SfUVSUk9SICAgLSBGaWxlICR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IGlzIG5vdCB3cml0YWJsZSR7X19SRVNFVF9DT0xPUn0iID4mMgogICAgICBmaQogICAgZWxpZiBbWyAhIC13ICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSIgXV07IHRoZW4KICAgICAgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMPSR7X19MRVZFTF9PRkZ9CiAgICAgIGVjaG8gLWUgIiR7X19FUlJPUl9DT0xPUn1FUlJPUiAgIC0gRmlsZSAke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSBpcyBub3Qgd3JpdGFibGUke19fUkVTRVRfQ09MT1J9IiA+JjIKICAgIGZpCgogIGZpCgogIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMID4gX19MRVZFTF9PRkYpKTsgdGhlbgogICAgIyB3aWxsIGFsd2F5cyBiZSBjcmVhdGVkIGV2ZW4gaWYgbm90IGluIGluZm8gbGV2ZWwKICAgIExvZzo6bG9nTWVzc2FnZSAiSU5GTyIgIkxvZ2dpbmcgdG8gZmlsZSAke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSAtIExvZyBsZXZlbCAke0JBU0hfRlJBTUVXT1JLX0xPR19MRVZFTH0iCiAgICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19GSUxFX01BWF9ST1RBVElPTiA+IDApKTsgdGhlbgogICAgICBMb2c6OnJvdGF0ZSAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFX01BWF9ST1RBVElPTn0iCiAgICBmaQogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGdldCBsaXN0IG9mIGVudiBmaWxlcyB0byBsb2FkCiMgaW4gb3JkZXIgdG8gbWFrZSB0aGVtIGF2YWlsYWJsZSBmb3IgRW52OjpyZXF1aXJlTG9hZAojIEBlbnYgQkFTSF9GUkFNRVdPUktfRU5WX0ZJTEVTIFN0cmluZ1tdIGxpc3Qgb2YgZW52IGZpbGVzIHRoYXQgc2hvdWxkIGJlIGxvYWRlZAojIEBleGl0Y29kZSAxIGlmIG9uZSBvZiB0aGUgZW52IGZpbGUgY2Fubm90IGJlIGdlbmVyYXRlZAojIEBleGl0Y29kZSAyIGlmIG9uZSBvZiB0aGUgZW52IGZpbGUgaXMgbm90IGEgZmlsZSBvciByZWFkYWJsZQojIEBzdGRvdXQgdGhlIGVudiBmaWxlcyBhc2tlZCB0byBiZSBsb2FkZWQKIyBAc3RkZXJyIGRpYWdub3N0aWMgaW5mb3JtYXRpb24gb24gZmFpbHVyZQojIEBzZWUgaHR0cHM6Ly9naXRodWIuY29tL2ZjaGFzdGFuZXQvYmFzaC10b29scy1mcmFtZXdvcmsvYmxvYi9tYXN0ZXIvRnJhbWV3b3JrRG9jLm1kI2NvbmZpZ19maWxlX29yZGVyCkVudjo6Z2V0T3JkZXJlZENvbmZGaWxlcygpIHsKICBsb2NhbCAtYSBjb25maWdGaWxlcz0oKQoKICBpZiBbWyAtbiAiJHtCQVNIX0ZSQU1FV09SS19FTlZfRklMRVNbMF0rMX0iIF1dOyB0aGVuCiAgICAjIEJBU0hfRlJBTUVXT1JLX0VOVl9GSUxFUyBpcyBhbiBhcnJheQogICAgY29uZmlnRmlsZXMrPSgiJHtCQVNIX0ZSQU1FV09SS19FTlZfRklMRVNbQF19IikKICBmaQoKICBsb2NhbCBkZWZhdWx0RW52RmlsZQogIGRlZmF1bHRFbnZGaWxlPSIkKEVudjo6Y3JlYXRlRGVmYXVsdEVudkZpbGUpIiB8fCByZXR1cm4gMQogIGNvbmZpZ0ZpbGVzKz0oIiR7ZGVmYXVsdEVudkZpbGV9IikKCiAgbG9jYWwgZmlsZQogIGZvciBmaWxlIGluICIke2NvbmZpZ0ZpbGVzW0BdfSI7IGRvCiAgICBpZiBbWyAhIC1mICIke2ZpbGV9IiB8fCAhIC1yICIke2ZpbGV9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlFcnJvciAiT25lIG9mIHRoZSBjb25maWcgZmlsZSBpcyBub3QgYXZhaWxhYmxlICcke2ZpbGV9JyIKICAgICAgcmV0dXJuIDIKICAgIGZpCiAgICBlY2hvICIke2ZpbGV9IgogIGRvbmUKfQoKIyBAZGVzY3JpcHRpb24gbWVyZ2UgYW5kIGxvYWQgY29uZiBmaWxlcyBzcGVjaWZpZWQgYXMgYXJndW1lbnQKIyAtIGZpbGVzIGFyZSBjbGVhbmVkIGZyb20gYXkgY29tbWVudAojIC0gbWlzc2luZyBxdW90ZXMgYWZ0ZXIgcHJvcGVydHkgPSBzaWduIGFyZSBhZGRlZCBhdXRvbWF0aWNhbGx5CiMgLSBhdXRvbWF0aWMgcmVtb3ZlIG9mIGFsbCB3aGl0ZXNwYWNlIGJlZm9yZSBhbmQgYWZ0ZXIgZGVjbGFyYXRpb25zCiMgLSBiYXNoIGFycmF5cyBhcmUgbm90IHN1cHBvcnRlZAojIC0gaWYgYSB2YXJpYWJsZSBpcyBkZWNsYXJlZCBpbiBmaXJzdCBmaWxlIGFuZCBvdmVycmlkZGVuIGxhdGVyIG9uCiMgICBpbiB0aGUgc2FtZSBmaWxlIG9yIGluIHN1YnNlcXVlbnQgZmlsZXMsIHRob3NlIG92ZXJsb2FkcyB3aWxsIGJlCiMgICBpZ25vcmVkCiMgQHdhcm5pbmcgaWYgYW4gZXJyb3Igb2NjdXJzIHdoaWxlIGxvYWRpbmcgb25lIG9mIHRoZSBjb25maWcgZmlsZSwgZXhpdCBjb2RlIDMgYnV0IGVudmlyb25tZW50IGNvdWxkIGJlIHBhcnRpYWxseSBsb2FkZWQKIyBAYXJnICRAIGFyZ3M6U3RyaW5nW10gbGlzdCBvZiBjb25maWd1cmF0aW9uIGZpbGVzIHRvIGxvYWQgaW4gb3JkZXIKIyBAc2V0IGVudlZhcnMgU3RyaW5nIHdpbGwgc2V0IGluIGVudmlyb25tZW50IGFsbCB0aGUgdmFyaWFibGVzIHRoYXQgaGF2ZSBiZWVuIGRlY2xhcmVkIGluIHRoZSBjb25maWcgZmlsZXMKIyBAZW52IGVudlZhcnMgU3RyaW5nIHRoZSBlbnYgdmFyaWFibGVzIG9mIHRoZSBjdXJyZW50IHNjcmlwdCBjb3VsZCBiZSB1c2VkIHRvIGludGVycHJldCB2YXJpYWJsZXMgZHVyaW5nIGNvbmZpZyBmaWxlcyBwYXJzaW5nCiMgQGV4aXRjb2RlIDAgaWYgbm8gY29uZmlnIGZpbGVzIHByb3ZpZGVkIG9yIGxvYWQgY29tcGxldGVkIHN1Y2Nlc3NmdWxseQojIEBleGl0Y29kZSAxIGlmIGVycm9yIG9jY3VycmVkIGR1cmluZyBwYXJzaW5nIHRoZSBjb25maWcgZmlsZXMgKGZpbGUgbm90IGZvdW5kLCBncmVwLCBhd2sgb3Igc2VkIGVycm9yKQojIEBleGl0Y29kZSAyIGlmIHRlbXBvcmFyeSBmaWxlIGNhbm5vdCBiZSBjcmVhdGVkCiMgQGV4aXRjb2RlIDMgaWYgYW4gZXJyb3Igb2NjdXJyZWQgZHVyaW5nIGNvbmZpZyBmaWxlIHNvdXJjaW5nCiMgQHN0ZGVyciBkaWFnbm9zdGljcyBpbmZvcm1hdGlvbiBpcyBkaXNwbGF5ZWQKIyBAc2VlIGxhcmdlbHkgaW5zcGlyZWQgYnV0IG1vZGlmaWVkIGZyb20gaHR0cHM6Ly9vcGVuc291cmNlLmNvbS9hcnRpY2xlLzIxLzUvcHJvY2Vzc2luZy1jb25maWd1cmF0aW9uLWZpbGVzLXNoZWxsCkVudjo6bWVyZ2VDb25mRmlsZXMoKSB7CiAgbG9jYWwgLWEgY29uZmlnRmlsZUxpc3Q9KCIkQCIpCgogIGlmICgoJHsjY29uZmlnRmlsZUxpc3RbQF19ID09IDApKTsgdGhlbgogICAgcmV0dXJuIDAKICBmaQoKICBsb2NhbCBjb21iaW5lZENvbmZpZ0ZpbGUKICBjb21iaW5lZENvbmZpZ0ZpbGU9IiQoRnJhbWV3b3JrOjpjcmVhdGVUZW1wRmlsZSAibWVyZ2VDb25mRmlsZXMiKSIgfHwgcmV0dXJuIDIKCiAgKAogICAgIyByZW1vdmVzIGFueSB0cmFpbGluZyB3aGl0ZXNwYWNlIGZyb20gZWFjaCBmaWxlLCBpZiBhbnkKICAgICMgdGhpcyBpcyBhYnNvbHV0ZWx5IHJlcXVpcmVkIHdoZW4gaW1wb3J0aW5nIGludG8gQ29uZmlnTWFwcwogICAgIyBwdXQgcXVvdGVzIGFyb3VuZCB2YWx1ZXMKICAgIHNlZCAtRSAtZSAkJ3MvXHMqJC8vIDsgL14kL2QgOyAvXiMuKiQvZCA7IHMvPShbXiJcJ10uKikkLz0iXFwxIi8nICIke2NvbmZpZ0ZpbGVMaXN0W0BdfSIgfAogICAgICAjIHJlbW92ZSBhbGwgY29tbWVudCBsaW5lcwogICAgICBGaWx0ZXJzOjpjb21tZW50TGluZXMgfAogICAgICAjIGl0ZXJhdGVzIG92ZXIgZWFjaCBmaWxlIGFuZCBwcmludHMgKGRlZmF1bHQgYXdrIGJlaGF2aW9yKQogICAgICAjIGVhY2ggdW5pcXVlIGxpbmU7IG9ubHkgdGFrZXMgZmlyc3QgdmFsdWUgYW5kIGlnbm9yZXMgZHVwbGljYXRlcwogICAgICBhd2sgLUY9ICchbGluZVskMV0rKycKICApID4iJHtjb21iaW5lZENvbmZpZ0ZpbGV9IiB8fCByZXR1cm4gMQoKICAjIGhhdmUgdG8gZXhwb3J0IGV2ZXJ5dGhpbmcsIGFuZCBzb3VyY2UgaXQgdHdpY2U6CiAgIyAxKSBmaXJzdCBzb3VyY2UgaXMgdG8gcmVhbGl6ZSB2YXJpYWJsZXMKICAjIDIpIHNlY29uZCB0aW1lIGlzIHRvIHJlYWxpemUgcmVmZXJlbmNlcwogIHNldCAtbyBhbGxleHBvcnQKICAjIHNoZWxsY2hlY2sgc291cmNlPS5mcmFtZXdvcmstY29uZmlnCiAgc291cmNlICIke2NvbWJpbmVkQ29uZmlnRmlsZX0iIHx8IHJldHVybiAzCiAgIyBzaGVsbGNoZWNrIHNvdXJjZT0uZnJhbWV3b3JrLWNvbmZpZwogIHNvdXJjZSAiJHtjb21iaW5lZENvbmZpZ0ZpbGV9IiB8fCByZXR1cm4gMwogIHNldCArbyBhbGxleHBvcnQKfQoKIyBAZGVzY3JpcHRpb24gRGlzcGxheSBtZXNzYWdlIHVzaW5nIHNraXAgY29sb3IgKHllbGxvdykKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpkaXNwbGF5U2tpcHBlZCgpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUwgPj0gX19MRVZFTF9JTkZPKSk7IHRoZW4KICAgIGVjaG8gLWUgIiR7X19TS0lQUEVEX0NPTE9SfVNLSVBQRUQgLSAkezF9JHtfX1JFU0VUX0NPTE9SfSIgPiYyCiAgZmkKICBMb2c6OmxvZ1NraXBwZWQgIiQxIgp9CgojIEBkZXNjcmlwdGlvbiBsb2cgbWVzc2FnZSB0byBmaWxlCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6bG9nRXJyb3IoKSB7CiAgaWYgKChCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgPj0gX19MRVZFTF9FUlJPUikpOyB0aGVuCiAgICBMb2c6OmxvZ01lc3NhZ2UgIiR7MjotRVJST1J9IiAiJDEiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gbG9nIG1lc3NhZ2UgdG8gZmlsZQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmxvZ1dhcm5pbmcoKSB7CiAgaWYgKChCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgPj0gX19MRVZFTF9XQVJOSU5HKSk7IHRoZW4KICAgIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1XQVJOSU5HfSIgIiQxIgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIFRvIGJlIGNhbGxlZCBiZWZvcmUgbG9nZ2luZyBpbiB0aGUgbG9nIGZpbGUKIyBAYXJnICQxIGZpbGU6c3RyaW5nIGxvZyBmaWxlIG5hbWUKIyBAYXJnICQyIG1heExvZ0ZpbGVzQ291bnQ6aW50IG1heGltdW0gbnVtYmVyIG9mIGxvZyBmaWxlcwpMb2c6OnJvdGF0ZSgpIHsKICBsb2NhbCBmaWxlPSIkMSIKICBsb2NhbCBtYXhMb2dGaWxlc0NvdW50PSIkezI6LTV9IgoKICBpZiBbWyAhIC1mICIke2ZpbGV9IiBdXTsgdGhlbgogICAgTG9nOjpkaXNwbGF5U2tpcHBlZCAiTG9nIGZpbGUgJHtmaWxlfSBkb2Vzbid0IGV4aXN0IHlldCIKICAgIHJldHVybiAwCiAgZmkKICBmb3IgaSBpbiAkKHNlcSAkKChtYXhMb2dGaWxlc0NvdW50IC0gMSkpIC0xIDEpOyBkbwogICAgTG9nOjpkaXNwbGF5SW5mbyAiTG9nIHJvdGF0aW9uICR7ZmlsZX0uJHtpfSB0byAke2ZpbGV9LiQoKGkgKyAxKSkiCiAgICBtdiAiJHtmaWxlfS4ieyIke2l9IiwiJCgoaSArIDEpKSJ9ICY+L2Rldi9udWxsIHx8IHRydWUKICBkb25lCiAgaWYgY3AgIiR7ZmlsZX0iICIke2ZpbGV9LjEiICY+L2Rldi9udWxsOyB0aGVuCiAgICBlY2hvID4iJHtmaWxlfSIgIyByZXNldCBsb2cgZmlsZQogICAgTG9nOjpkaXNwbGF5SW5mbyAiTG9nIHJvdGF0aW9uICR7ZmlsZX0gdG8gJHtmaWxlfS4xIgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGVuc3VyZSBjb21tYW5kIHJlYWxwYXRoIGlzIGF2YWlsYWJsZQojIEBleGl0Y29kZSAxIGlmIHJlYWxwYXRoIGNvbW1hbmQgbm90IGF2YWlsYWJsZQojIEBzdGRlcnIgZGlhZ25vc3RpY3MgaW5mb3JtYXRpb24gaXMgZGlzcGxheWVkCkxpbnV4OjpyZXF1aXJlUmVhbHBhdGhDb21tYW5kKCkgewogIEFzc2VydDo6Y29tbWFuZEV4aXN0cyByZWFscGF0aAp9CgojIEBkZXNjcmlwdGlvbiBsb2FkIGNvbG9yIHRoZW1lCiMgQG5vYXJncwojIEBlbnYgQkFTSF9GUkFNRVdPUktfVEhFTUUgU3RyaW5nIHRoZW1lIHRvIHVzZQojIEBleGl0Y29kZSAwIGFsd2F5cyBzdWNjZXNzZnVsClVJOjpyZXF1aXJlVGhlbWUoKSB7CiAgVUk6OnRoZW1lICIke0JBU0hfRlJBTUVXT1JLX1RIRU1FLWRlZmF1bHR9Igp9CgojIEBkZXNjcmlwdGlvbiBjaGVjayBpZiBjb21tYW5kIHNwZWNpZmllZCBleGlzdHMgb3IgcmV0dXJuIDEKIyB3aXRoIGVycm9yIGFuZCBtZXNzYWdlIGlmIG5vdAojCiMgQGFyZyAkMSBjb21tYW5kTmFtZTpTdHJpbmcgb24gd2hpY2ggZXhpc3RlbmNlIG11c3QgYmUgY2hlY2tlZAojIEBhcmcgJDIgaGVscElmTm90RXhpc3RzOlN0cmluZyBhIGhlbHAgY29tbWFuZCB0byBkaXNwbGF5IGlmIHRoZSBjb21tYW5kIGRvZXMgbm90IGV4aXN0CiMKIyBAZXhpdGNvZGUgMSBpZiB0aGUgY29tbWFuZCBzcGVjaWZpZWQgZG9lcyBub3QgZXhpc3QKIyBAc3RkZXJyIGRpYWdub3N0aWMgaW5mb3JtYXRpb24gKyBoZWxwIGlmIHNlY29uZCBhcmd1bWVudCBpcyBwcm92aWRlZApBc3NlcnQ6OmNvbW1hbmRFeGlzdHMoKSB7CiAgbG9jYWwgY29tbWFuZE5hbWU9IiQxIgogIGxvY2FsIGhlbHBJZk5vdEV4aXN0cz0iJDIiCgogICIke0JBU0hfRlJBTUVXT1JLX0NPTU1BTkQ6LWNvbW1hbmR9IiAtdiAiJHtjb21tYW5kTmFtZX0iID4vZGV2L251bGwgMj4vZGV2L251bGwgfHwgewogICAgTG9nOjpkaXNwbGF5RXJyb3IgIiR7Y29tbWFuZE5hbWV9IGlzIG5vdCBpbnN0YWxsZWQsIHBsZWFzZSBpbnN0YWxsIGl0IgogICAgaWYgW1sgLW4gIiR7aGVscElmTm90RXhpc3RzfSIgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5SW5mbyAiJHtoZWxwSWZOb3RFeGlzdHN9IgogICAgZmkKICAgIHJldHVybiAxCiAgfQogIHJldHVybiAwCn0KCiMgQGRlc2NyaXB0aW9uIGRlZmF1bHQgZW52IGZpbGUgd2l0aCBhbGwgZGVmYXVsdCB2YWx1ZXMKIyBAc3Rkb3V0IHRoZSBkZWZhdWx0IGVudiBmaWxlcGF0aApFbnY6OmNyZWF0ZURlZmF1bHRFbnZGaWxlKCkgewogIGxvY2FsIGVudkZpbGUKICBlbnZGaWxlPSIkKEZyYW1ld29yazo6Y3JlYXRlVGVtcEZpbGUgImNyZWF0ZURlZmF1bHRFbnZGaWxlRW52RmlsZSIpIiB8fCByZXR1cm4gMgoKICAoCiAgICBlY2hvICJCQVNIX0ZSQU1FV09SS19USEVNRT0ke0JBU0hfRlJBTUVXT1JLX1RIRU1FOi1kZWZhdWx0fSIKICAgIGVjaG8gIkJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTD0ke0JBU0hfRlJBTUVXT1JLX0xPR19MRVZFTDotMH0iCiAgICBlY2hvICJCQVNIX0ZSQU1FV09SS19ESVNQTEFZX0xFVkVMPSR7QkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTDotJHtfX0xFVkVMX1dBUk5JTkd9fSIKICAgICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAxNgogICAgZWNobyAnQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEU9IiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEU6LSIke0ZSQU1FV09SS19ST09UX0RJUn0vbG9ncy8ke1NDUklQVF9OQU1FfS5sb2cifSInCiAgICBlY2hvICJCQVNIX0ZSQU1FV09SS19MT0dfRklMRV9NQVhfUk9UQVRJT049JHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRV9NQVhfUk9UQVRJT046LTV9IgogICkgPiIke2VudkZpbGV9IgogIGVjaG8gIiR7ZW52RmlsZX0iCn0KCiMgQGRlc2NyaXB0aW9uIHJlbW92ZSBjb21tZW50IGxpbmVzIGZyb20gaW5wdXQgb3IgZmlsZXMgcHJvdmlkZWQgYXMgYXJndW1lbnRzCiMgQGFyZyAkQCBmaWxlczpTdHJpbmdbXSAob3B0aW9uYWwpIHRoZSBmaWxlcyB0byBmaWx0ZXIKIyBAZW52IGNvbW1lbnRMaW5lUHJlZml4IFN0cmluZyB0aGUgY29tbWVudCBsaW5lIHByZWZpeCAoZGVmYXVsdCB2YWx1ZTogIykKIyBAZXhpdGNvZGUgMCBpZiBsaW5lcyBmaWx0ZXJlZCBvciBub3QKIyBAZXhpdGNvZGUgMiBpZiBncmVwIGZhaWxzIGZvciBhbnkgb3RoZXIgcmVhc29ucyB0aGFuIG5vdCBmb3VuZAojIEBzdGRpbiB0aGUgZmlsZSBhcyBzdGRpbiB0byBmaWx0ZXIgKGFsdGVybmF0aXZlIHRvIGZpbGVzIGFyZ3VtZW50KQojIEBzdGRvdXQgdGhlIGZpbHRlcmVkIGxpbmVzCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjEyMApGaWx0ZXJzOjpjb21tZW50TGluZXMoKSB7CiAgZ3JlcCAtdnhFICJbWzpibGFuazpdXSooJHtjb21tZW50TGluZVByZWZpeDotI30uKik/IiAiJEAiIHx8IHRlc3QgJD8gPSAxCn0KCiMgQGRlc2NyaXB0aW9uIGNyZWF0ZSBhIHRlbXAgZmlsZSB1c2luZyBkZWZhdWx0IFRNUERJUiB2YXJpYWJsZQojIGluaXRpYWxpemVkIGluIF9pbmNsdWRlcy9fY29tbW9uSGVhZGVyLnNoCiMgQGVudiBUTVBESVIgU3RyaW5nIChkZWZhdWx0IHZhbHVlIC90bXApCiMgQGFyZyAkMSB0ZW1wbGF0ZU5hbWU6U3RyaW5nIHRlbXBsYXRlIG5hbWUgdG8gdXNlKG9wdGlvbmFsKQpGcmFtZXdvcms6OmNyZWF0ZVRlbXBGaWxlKCkgewogIG1rdGVtcCAtcCAiJHtUTVBESVI6LS90bXB9IiAtdCAiJHsxOi19LlhYWFhYWFhYWFhYWCIKfQoKIyBAZGVzY3JpcHRpb24gbG9nIG1lc3NhZ2UgdG8gZmlsZQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmxvZ1NraXBwZWQoKSB7CiAgaWYgKChCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgPj0gX19MRVZFTF9JTkZPKSk7IHRoZW4KICAgIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1TS0lQUEVEfSIgIiQxIgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGxvYWQgY29sb3JzIHRoZW1lIGNvbnN0YW50cwojIEB3YXJuaW5nIGlmIHR0eSBub3Qgb3BlbmVkLCBub0NvbG9yIHRoZW1lIHdpbGwgYmUgY2hvc2VuCiMgQGFyZyAkMSB0aGVtZTpTdHJpbmcgdGhlIHRoZW1lIHRvIHVzZSAoZGVmYXVsdCwgbm9Db2xvcikKIyBAYXJnICRAIGFyZ3M6U3RyaW5nW10KIyBAc2V0IF9fRVJST1JfQ09MT1IgU3RyaW5nIGluZGljYXRlIGVycm9yIHN0YXR1cwojIEBzZXQgX19JTkZPX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSBpbmZvIHN0YXR1cwojIEBzZXQgX19TVUNDRVNTX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSBzdWNjZXNzIHN0YXR1cwojIEBzZXQgX19XQVJOSU5HX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSB3YXJuaW5nIHN0YXR1cwojIEBzZXQgX19TS0lQUEVEX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSBza2lwcGVkIHN0YXR1cwojIEBzZXQgX19ERUJVR19DT0xPUiBTdHJpbmcgaW5kaWNhdGUgZGVidWcgc3RhdHVzCiMgQHNldCBfX0hFTFBfQ09MT1IgU3RyaW5nIGluZGljYXRlIGhlbHAgc3RhdHVzCiMgQHNldCBfX1RFU1RfQ09MT1IgU3RyaW5nIG5vdCB1c2VkCiMgQHNldCBfX1RFU1RfRVJST1JfQ09MT1IgU3RyaW5nIG5vdCB1c2VkCiMgQHNldCBfX0hFTFBfVElUTEVfQ09MT1IgU3RyaW5nIHVzZWQgdG8gZGlzcGxheSBoZWxwIHRpdGxlIGluIGhlbHAgc3RyaW5ncwojIEBzZXQgX19IRUxQX09QVElPTl9DT0xPUiBTdHJpbmcgdXNlZCB0byBkaXNwbGF5IGhpZ2hsaWdodCBvcHRpb25zIGluIGhlbHAgc3RyaW5ncwojCiMgQHNldCBfX1JFU0VUX0NPTE9SIFN0cmluZyByZXNldCBkZWZhdWx0IGNvbG9yCiMKIyBAc2V0IF9fSEVMUF9FWEFNUExFIFN0cmluZyB0byByZW1vdmUKIyBAc2V0IF9fSEVMUF9USVRMRSBTdHJpbmcgdG8gcmVtb3ZlCiMgQHNldCBfX0hFTFBfTk9STUFMIFN0cmluZyB0byByZW1vdmUKVUk6OnRoZW1lKCkgewogIGxvY2FsIHRoZW1lPSIkezEtZGVmYXVsdH0iCiAgaWYgW1sgISAiJHt0aGVtZX0iID1+IC1mb3JjZSQgXV0gJiYgISBBc3NlcnQ6OnR0eTsgdGhlbgogICAgdGhlbWU9Im5vQ29sb3IiCiAgZmkKICBjYXNlICIke3RoZW1lfSIgaW4KICAgIGRlZmF1bHQgfCBkZWZhdWx0LWZvcmNlKQogICAgICB0aGVtZT0iZGVmYXVsdCIKICAgICAgOzsKICAgIG5vQ29sb3IpIDs7CiAgICAqKQogICAgICBMb2c6OmZhdGFsICJpbnZhbGlkIHRoZW1lIHByb3ZpZGVkIgogICAgICA7OwogIGVzYWMKICBpZiBbWyAiJHt0aGVtZX0iID0gImRlZmF1bHQiIF1dOyB0aGVuCiAgICBleHBvcnQgQkFTSF9GUkFNRVdPUktfVEhFTUU9ImRlZmF1bHQiCiAgICAjIGNoZWNrIGNvbG9ycyBhcHBsaWNhYmxlIGh0dHBzOi8vbWlzYy5mbG9naXNvZnQuY29tL2Jhc2gvdGlwX2NvbG9yc19hbmRfZm9ybWF0dGluZwogICAgZXhwb3J0IF9fRVJST1JfQ09MT1I9J1xlWzMxbScgICAgICAgICAjIFJlZAogICAgZXhwb3J0IF9fSU5GT19DT0xPUj0nXGVbNDRtJyAgICAgICAgICAjIHdoaXRlIG9uIGxpZ2h0Qmx1ZQogICAgZXhwb3J0IF9fU1VDQ0VTU19DT0xPUj0nXGVbMzJtJyAgICAgICAjIEdyZWVuCiAgICBleHBvcnQgX19XQVJOSU5HX0NPTE9SPSdcZVszM20nICAgICAgICMgWWVsbG93CiAgICBleHBvcnQgX19TS0lQUEVEX0NPTE9SPSdcZVszM20nICAgICAgICMgWWVsbG93CiAgICBleHBvcnQgX19ERUJVR19DT0xPUj0nXGVbMzdtJyAgICAgICAgICMgR3JleQogICAgZXhwb3J0IF9fSEVMUF9DT0xPUj0nXGVbNzs0OTszM20nICAgICAjIEJsYWNrIG9uIEdvbGQKICAgIGV4cG9ydCBfX1RFU1RfQ09MT1I9J1xlWzEwMG0nICAgICAgICAgIyBMaWdodCBtYWdlbnRhCiAgICBleHBvcnQgX19URVNUX0VSUk9SX0NPTE9SPSdcZVs0MW0nICAgICMgd2hpdGUgb24gcmVkCiAgICBleHBvcnQgX19IRUxQX1RJVExFX0NPTE9SPSJcZVsxOzM3bSIgICMgQm9sZAogICAgZXhwb3J0IF9fSEVMUF9PUFRJT05fQ09MT1I9IlxlWzE7MzRtIiAjIEJsdWUKICAgICMgSW50ZXJuYWw6IHJlc2V0IGNvbG9yCiAgICBleHBvcnQgX19SRVNFVF9DT0xPUj0nXGVbMG0nICMgUmVzZXQgQ29sb3IKICAgICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjE1NSxTQzIwMzQKICAgIGV4cG9ydCBfX0hFTFBfRVhBTVBMRT0iJChlY2hvIC1lICJcZVsyOzk3bSIpIgogICAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTU1LFNDMjAzNAogICAgZXhwb3J0IF9fSEVMUF9USVRMRT0iJChlY2hvIC1lICJcZVsxOzM3bSIpIgogICAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTU1LFNDMjAzNAogICAgZXhwb3J0IF9fSEVMUF9OT1JNQUw9IiQoZWNobyAtZSAiXDAzM1swbSIpIgogIGVsc2UKICAgIGV4cG9ydCBCQVNIX0ZSQU1FV09SS19USEVNRT0ibm9Db2xvciIKICAgICMgY2hlY2sgY29sb3JzIGFwcGxpY2FibGUgaHR0cHM6Ly9taXNjLmZsb2dpc29mdC5jb20vYmFzaC90aXBfY29sb3JzX2FuZF9mb3JtYXR0aW5nCiAgICBleHBvcnQgX19FUlJPUl9DT0xPUj0nJwogICAgZXhwb3J0IF9fSU5GT19DT0xPUj0nJwogICAgZXhwb3J0IF9fU1VDQ0VTU19DT0xPUj0nJwogICAgZXhwb3J0IF9fV0FSTklOR19DT0xPUj0nJwogICAgZXhwb3J0IF9fU0tJUFBFRF9DT0xPUj0nJwogICAgZXhwb3J0IF9fREVCVUdfQ09MT1I9JycKICAgIGV4cG9ydCBfX0hFTFBfQ09MT1I9JycKICAgIGV4cG9ydCBfX1RFU1RfQ09MT1I9JycKICAgIGV4cG9ydCBfX1RFU1RfRVJST1JfQ09MT1I9JycKICAgIGV4cG9ydCBfX0hFTFBfVElUTEVfQ09MT1I9JycKICAgIGV4cG9ydCBfX0hFTFBfT1BUSU9OX0NPTE9SPScnCiAgICAjIEludGVybmFsOiByZXNldCBjb2xvcgogICAgZXhwb3J0IF9fUkVTRVRfQ09MT1I9JycKICAgIGV4cG9ydCBfX0hFTFBfRVhBTVBMRT0nJwogICAgZXhwb3J0IF9fSEVMUF9USVRMRT0nJwogICAgZXhwb3J0IF9fSEVMUF9OT1JNQUw9JycKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBjaGVjayBpZiB0dHkgKGludGVyYWN0aXZlIG1vZGUpIGlzIGFjdGl2ZQojIEBub2FyZ3MKIyBAZXhpdGNvZGUgMSBpZiB0dHkgbm90IGFjdGl2ZQojIEBlbnZpcm9ubWVudCBOT05fSU5URVJBQ1RJVkUgaWYgMSBjb25zaWRlciBhcyBub3QgaW50ZXJhY3RpdmUgZXZlbiBpZiBlbnZpcm9uZW1lbnQgaXMgaW50ZXJhY3RpdmUKIyBAZW52aXJvbm1lbnQgSU5URVJBQ1RJVkUgaWYgMSBjb25zaWRlciBhcyBpbnRlcmFjdGl2ZSBldmVuIGlmIGVudmlyb25lbWVudCBpcyBub3QgaW50ZXJhY3RpdmUKIyBAc3RkZXJyIGRpYWdub3N0aWMgaW5mb3JtYXRpb24gKyBoZWxwIGlmIHNlY29uZCBhcmd1bWVudCBpcyBwcm92aWRlZApBc3NlcnQ6OnR0eSgpIHsKICBpZiBbWyAiJHtOT05fSU5URVJBQ1RJVkU6LTB9IiA9ICIxIiBdXTsgdGhlbgogICAgcmV0dXJuIDEKICBmaQogIGlmIFtbICIke0lOVEVSQUNUSVZFOi0wfSIgPSAiMSIgXV07IHRoZW4KICAgIHJldHVybiAwCiAgZmkKICBbWyAtdCAxIHx8IC10IDIgXV0KfQoKIyBGVU5DVElPTlMKCmZhY2FkZV9tYWluXzI0ZDA2MjIxZDI0ODRmOWZiM2IyNzBlNWI1MTliMzJkKCkgewojIFJFUVVJUkVTCkxpbnV4OjpyZXF1aXJlRXhlY3V0ZWRBc1VzZXIKRW52OjpyZXF1aXJlTG9hZApMb2c6OnJlcXVpcmVMb2FkCkxpbnV4OjpyZXF1aXJlUmVhbHBhdGhDb21tYW5kClVJOjpyZXF1aXJlVGhlbWUKQ29tcGlsZXI6OkZhY2FkZTo6cmVxdWlyZUNvbW1hbmRCaW5EaXIKCiMgQHJlcXVpcmUgQ29tcGlsZXI6OkZhY2FkZTo6cmVxdWlyZUNvbW1hbmRCaW5EaXIKCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjE1NCxTQzIwMTYKZnVuY3Rpb25Ub0NhbGw9J0RiOjpxdWVyeU9uZURhdGFiYXNlJwoiJHtmdW5jdGlvblRvQ2FsbH0iICIkQCIKCn0KCmZhY2FkZV9tYWluXzI0ZDA2MjIxZDI0ODRmOWZiM2IyNzBlNWI1MTliMzJkICIkQCIK" +declare -gx embed_function_DbQueryOneDatabase="${PERSISTENT_TMPDIR:-/tmp}/bin/831da3376e6ea1f6055692f8702d065c/dbQueryOneDatabase" +declare -gx encoded_binary_file_DbQueryOneDatabase="IyEvdXNyL2Jpbi9lbnYgYmFzaAojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgR0VORVJBVEVEIEZBQ0FERSBGUk9NIGh0dHBzOi8vZ2l0aHViLmNvbS9mY2hhc3RhbmV0L2Jhc2gtdG9vbHMvdHJlZS9tYXN0ZXIvLi4vYmFzaC1kZXYtZW52L3ZlbmRvci9iYXNoLXRvb2xzLWZyYW1ld29yay9zcmMvQ29tcGlsZXIvRW1iZWQvZW1iZWRGcmFtZXdvcmtGdW5jdGlvbi5iaW5GaWxlLnRwbAojIERPIE5PVCBFRElUIElUCiMgQGdlbmVyYXRlZAojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjI4OCxTQzIwMzQKIyBCSU5fRklMRT0ke0JJTl9GSUxFfQojIEZBQ0FERQoKIyBlbnN1cmUgdGhhdCBubyB1c2VyIGFsaWFzZXMgY291bGQgaW50ZXJmZXJlIHdpdGgKIyBjb21tYW5kcyB1c2VkIGluIHRoaXMgc2NyaXB0CnVuYWxpYXMgLWEgfHwgdHJ1ZQpzaG9wdCAtdSBleHBhbmRfYWxpYXNlcwoKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0CigoZmFpbHVyZXMgPSAwKSkgfHwgdHJ1ZQoKIyBCYXNoIHdpbGwgcmVtZW1iZXIgJiByZXR1cm4gdGhlIGhpZ2hlc3QgZXhpdCBjb2RlIGluIGEgY2hhaW4gb2YgcGlwZXMuCiMgVGhpcyB3YXkgeW91IGNhbiBjYXRjaCB0aGUgZXJyb3IgaW5zaWRlIHBpcGVzLCBlLmcuIG15c3FsZHVtcCB8IGd6aXAKc2V0IC1vIHBpcGVmYWlsCnNldCAtbyBlcnJleGl0CgojIENvbW1hbmQgU3Vic3RpdHV0aW9uIGNhbiBpbmhlcml0IGVycmV4aXQgb3B0aW9uIHNpbmNlIGJhc2ggdjQuNApzaG9wdCAtcyBpbmhlcml0X2VycmV4aXQgfHwgdHJ1ZQoKIyBhIGxvZyBpcyBnZW5lcmF0ZWQgd2hlbiBhIGNvbW1hbmQgZmFpbHMKc2V0IC1vIGVycnRyYWNlCgojIHVzZSBudWxsZ2xvYiBzbyB0aGF0IChmaWxlKi5waHApIHdpbGwgcmV0dXJuIGFuIGVtcHR5IGFycmF5IGlmIG5vIGZpbGUgbWF0Y2hlcyB0aGUgd2lsZGNhcmQKc2hvcHQgLXMgbnVsbGdsb2IKCiMgZW5zdXJlIHJlZ2V4cCBhcmUgaW50ZXJwcmV0ZWQgd2l0aG91dCBhY2NlbnR1YXRlZCBjaGFyYWN0ZXJzCmV4cG9ydCBMQ19BTEw9UE9TSVgKCmV4cG9ydCBURVJNPXh0ZXJtLTI1NmNvbG9yCgojIGF2b2lkIGludGVyYWN0aXZlIGluc3RhbGwKZXhwb3J0IERFQklBTl9GUk9OVEVORD1ub25pbnRlcmFjdGl2ZQpleHBvcnQgREVCQ09ORl9OT05JTlRFUkFDVElWRV9TRUVOPXRydWUKCiMgc3RvcmUgY29tbWFuZCBhcmd1bWVudHMgZm9yIGxhdGVyIHVzYWdlCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNApkZWNsYXJlIC1hIEJBU0hfRlJBTUVXT1JLX0FSR1Y9KCIkQCIpCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNApkZWNsYXJlIC1hIE9SSUdJTkFMX0JBU0hfRlJBTUVXT1JLX0FSR1Y9KCIkQCIpCgojIEBzZWUgaHR0cHM6Ly91bml4LnN0YWNrZXhjaGFuZ2UuY29tL2EvMzg2ODU2CiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjMxNwppbnRlcnJ1cHRNYW5hZ2VtZW50KCkgewogICMgcmVzdG9yZSBTSUdJTlQgaGFuZGxlcgogIHRyYXAgLSBJTlQKICAjIGVuc3VyZSB0aGF0IEN0cmwtQyBpcyB0cmFwcGVkIGJ5IHRoaXMgc2NyaXB0IGFuZCBub3QgYnkgc3ViIHByb2Nlc3MKICAjIHJlcG9ydCB0byB0aGUgcGFyZW50IHRoYXQgd2UgaGF2ZSBpbmRlZWQgYmVlbiBpbnRlcnJ1cHRlZAogIGtpbGwgLXMgSU5UICIkJCIKfQp0cmFwIGludGVycnVwdE1hbmFnZW1lbnQgSU5UClNDUklQVF9OQU1FPSR7MCMjKi99ClJFQUxfU0NSSVBUX0ZJTEU9IiQocmVhZGxpbmsgLWUgIiQocmVhbHBhdGggIiR7QkFTSF9TT1VSQ0VbMF19IikiKSIKQ1VSUkVOVF9ESVI9IiQoY2QgIiQocmVhZGxpbmsgLWUgIiR7UkVBTF9TQ1JJUFRfRklMRSUvKn0iKSIgJiYgcHdkIC1QKSIKCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojIFRlbXAgZGlyIG1hbmFnZW1lbnQKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgpLRUVQX1RFTVBfRklMRVM9IiR7S0VFUF9URU1QX0ZJTEVTOi0wfSIKZXhwb3J0IEtFRVBfVEVNUF9GSUxFUwoKIyBQRVJTSVNURU5UX1RNUERJUiBpcyBub3QgZGVsZXRlZCBieSB0cmFwcwpQRVJTSVNURU5UX1RNUERJUj0iJHtUTVBESVI6LS90bXB9L2Jhc2gtZnJhbWV3b3JrIgpleHBvcnQgUEVSU0lTVEVOVF9UTVBESVIKbWtkaXIgLXAgIiR7UEVSU0lTVEVOVF9UTVBESVJ9IgoKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0ClRNUERJUj0iJChta3RlbXAgLWQgLXAgIiR7UEVSU0lTVEVOVF9UTVBESVI6LS90bXB9IiAtdCBiYXNoLWZyYW1ld29yay0kJC1YWFhYWFgpIgpleHBvcnQgVE1QRElSCgojIHRlbXAgZGlyIGNsZWFuaW5nCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjMxNwpjbGVhbk9uRXhpdCgpIHsKICBpZiBbWyAiJHtLRUVQX1RFTVBfRklMRVM6LTB9IiA9ICIxIiBdXTsgdGhlbgogICAgTG9nOjpkaXNwbGF5SW5mbyAiS0VFUF9URU1QX0ZJTEVTPTEgdGVtcCBmaWxlcyBrZXB0IGhlcmUgJyR7VE1QRElSfSciCiAgZWxpZiBbWyAtbiAiJHtUTVBESVIreHh4fSIgXV07IHRoZW4KICAgIExvZzo6ZGlzcGxheURlYnVnICJLRUVQX1RFTVBfRklMRVM9MCByZW1vdmluZyB0ZW1wIGZpbGVzICcke1RNUERJUn0nIgogICAgcm0gLVJmICIke1RNUERJUjotL3RtcC9mYWtlfSIgPi9kZXYvbnVsbCAyPiYxCiAgZmkKfQp0cmFwIGNsZWFuT25FeGl0IEVYSVQgSFVQIFFVSVQgQUJSVCBURVJNCgojIFZBUl9NQUlOX0ZVTkNUSU9OX1ZBUl9OQU1FPWRiUXVlcnlBbGxEYXRhYmFzZXNGYWNhZGUKCiMgQGRlc2NyaXB0aW9uIHVzZWQgdG8gZXhlY3V0ZSBnaXZlbiBxdWVyeSB3aGVuIHVzaW5nCiMgZGJTY3JpcHRBbGxEYXRhYmFzZXMKIyBAYXJnICQxIGRzbjpTdHJpbmcKIyBAYXJnICQyIGRiOlN0cmluZwojIEBlbnYgcXVlcnkgU3RyaW5nCiMgQGVudiBvcHRpb25TZXBhcmF0b3IgU3RyaW5nCiMgQHJlcXVpcmUgTGludXg6OnJlcXVpcmVFeGVjdXRlZEFzVXNlcgpEYjo6cXVlcnlPbmVEYXRhYmFzZSgpIHsKICAjIHF1ZXJ5IGFuZCBvcHRpb25TZXBhcmF0b3IgYXJlIHBhc3NlZCB2aWEgZXhwb3J0CiAgbG9jYWwgZHNuPSIkMSIKICBsb2NhbCBkYj0iJDIiCgogIGxvY2FsIC1BIGRiSW5zdGFuY2UKICBEYXRhYmFzZTo6bmV3SW5zdGFuY2UgZGJJbnN0YW5jZSAiJHtkc259IgogIERhdGFiYXNlOjpzZXRRdWVyeU9wdGlvbnMgZGJJbnN0YW5jZSAiJHtkYkluc3RhbmNlW1FVRVJZX09QVElPTlNdfSAtLWNvbm5lY3QtdGltZW91dD01IgoKICAjIGlkZW50aWZ5IGNvbHVtbnMgaGVhZGVyCiAgZWNobyAtbiAiQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAiCiAgRGF0YWJhc2U6OnNraXBDb2x1bW5OYW1lcyBkYkluc3RhbmNlIDAKCiAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTU0CiAgaWYgISBEYXRhYmFzZTo6cXVlcnkgZGJJbnN0YW5jZSAiJHtxdWVyeX0iICIke2RifSIgfCBzZWQgInMvXHQvJHtvcHRpb25TZXBhcmF0b3J9L2ciOyB0aGVuCiAgICBMb2c6OmZhdGFsICJkYXRhYmFzZSAke2RifSBlcnJvciIgMT4mMgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIExvZyBuYW1lc3BhY2UgcHJvdmlkZXMgMiBraW5kIG9mIGZ1bmN0aW9ucwojIC0gTG9nOjpkaXNwbGF5KiBhbGxvd3MgdG8gZGlzcGxheSBnaXZlbiBtZXNzYWdlIHdpdGgKIyAgIGdpdmVuIGRpc3BsYXkgbGV2ZWwKIyAtIExvZzo6bG9nKiBhbGxvd3MgdG8gbG9nIGdpdmVuIG1lc3NhZ2Ugd2l0aAojICAgZ2l2ZW4gbG9nIGxldmVsCiMgTG9nOjpkaXNwbGF5KiBmdW5jdGlvbnMgYXV0b21hdGljYWxseSBsb2cgdGhlIG1lc3NhZ2UgdG9vCiMgQHNlZSBFbnY6OnJlcXVpcmVMb2FkIHRvIGxvYWQgdGhlIGRpc3BsYXkgYW5kIGxvZyBsZXZlbCBmcm9tIC5lbnYgZmlsZQoKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIG9mZgpleHBvcnQgX19MRVZFTF9PRkY9MAojIEBkZXNjcmlwdGlvbiBsb2cgbGV2ZWwgZXJyb3IKZXhwb3J0IF9fTEVWRUxfRVJST1I9MQojIEBkZXNjcmlwdGlvbiBsb2cgbGV2ZWwgd2FybmluZwpleHBvcnQgX19MRVZFTF9XQVJOSU5HPTIKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIGluZm8KZXhwb3J0IF9fTEVWRUxfSU5GTz0zCiMgQGRlc2NyaXB0aW9uIGxvZyBsZXZlbCBzdWNjZXNzCmV4cG9ydCBfX0xFVkVMX1NVQ0NFU1M9MwojIEBkZXNjcmlwdGlvbiBsb2cgbGV2ZWwgZGVidWcKZXhwb3J0IF9fTEVWRUxfREVCVUc9NAoKIyBAZGVzY3JpcHRpb24gdmVyYm9zZSBsZXZlbCBvZmYKZXhwb3J0IF9fVkVSQk9TRV9MRVZFTF9PRkY9MAojIEBkZXNjcmlwdGlvbiB2ZXJib3NlIGxldmVsIGluZm8KZXhwb3J0IF9fVkVSQk9TRV9MRVZFTF9JTkZPPTEKIyBAZGVzY3JpcHRpb24gdmVyYm9zZSBsZXZlbCBpbmZvCmV4cG9ydCBfX1ZFUkJPU0VfTEVWRUxfREVCVUc9MgojIEBkZXNjcmlwdGlvbiB2ZXJib3NlIGxldmVsIGluZm8KZXhwb3J0IF9fVkVSQk9TRV9MRVZFTF9UUkFDRT0zCgojIEBkZXNjcmlwdGlvbiBEaXNwbGF5IG1lc3NhZ2UgdXNpbmcgZGVidWcgY29sb3IgKGdyZXkpCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6ZGlzcGxheURlYnVnKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCA+PSBfX0xFVkVMX0RFQlVHKSk7IHRoZW4KICAgIGVjaG8gLWUgIiR7X19ERUJVR19DT0xPUn1ERUJVRyAgIC0gJHsxfSR7X19SRVNFVF9DT0xPUn0iID4mMgogIGZpCiAgTG9nOjpsb2dEZWJ1ZyAiJDEiCn0KCiMgQGRlc2NyaXB0aW9uIERpc3BsYXkgbWVzc2FnZSB1c2luZyBpbmZvIGNvbG9yIChiZyBsaWdodCBibHVlL2ZnIHdoaXRlKQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlJbmZvKCkgewogIGxvY2FsIHR5cGU9IiR7MjotSU5GT30iCiAgaWYgKChCQVNIX0ZSQU1FV09SS19ESVNQTEFZX0xFVkVMID49IF9fTEVWRUxfSU5GTykpOyB0aGVuCiAgICBlY2hvIC1lICIke19fSU5GT19DT0xPUn0ke3R5cGV9ICAgIC0gJHsxfSR7X19SRVNFVF9DT0xPUn0iID4mMgogIGZpCiAgTG9nOjpsb2dJbmZvICIkMSIgIiR7dHlwZX0iCn0KCiMgQGRlc2NyaXB0aW9uIGVuc3VyZSBDT01NQU5EX0JJTl9ESVIgZW52IHZhciBpcyBzZXQKIyBhbmQgUEFUSCBjb3JyZWN0bHkgcHJlcGFyZWQKIyBAbm9hcmdzCiMgQHNldCBDT01NQU5EX0JJTl9ESVIgc3RyaW5nIHRoZSBkaXJlY3Rvcnkgd2hlcmUgdG8gZmluZCB0aGlzIGNvbW1hbmQKIyBAc2V0IFBBVEggc3RyaW5nIGFkZCBkaXJlY3Rvcnkgd2hlcmUgdG8gZmluZCB0aGlzIGNvbW1hbmQgYmluYXJ5CkNvbXBpbGVyOjpGYWNhZGU6OnJlcXVpcmVDb21tYW5kQmluRGlyKCkgewogIENPTU1BTkRfQklOX0RJUj0iJHtDVVJSRU5UX0RJUn0iCiAgRW52OjpwYXRoUHJlcGVuZCAiJHtDT01NQU5EX0JJTl9ESVJ9Igp9CgojIEBkZXNjcmlwdGlvbiBjcmVhdGUgYSBuZXcgZGIgaW5zdGFuY2UKIyBSZXR1cm5zIGltbWVkaWF0ZWx5IGlmIHRoZSBpbnN0YW5jZSBpcyBhbHJlYWR5IGluaXRpYWxpemVkCiMKIyBAYXJnICQxIGluc3RhbmNlTmV3SW5zdGFuY2U6Jk1hcDxTdHJpbmcsU3RyaW5nPiAocGFzc2VkIGJ5IHJlZmVyZW5jZSkgZGF0YWJhc2UgaW5zdGFuY2UgdG8gdXNlCiMgQGFyZyAkMiBkc246U3RyaW5nIGRzbiBwcm9maWxlIC0gbG9hZCB0aGUgZHNuLmVudiBwcm9maWxlIGRlZHVjZWQgdXNpbmcgcnVsZXMgZGVmaW5lZCBpbiBDb25mOjpnZXRBYnNvbHV0ZUZpbGUKIwojIEBleGFtcGxlCiMgICBkZWNsYXJlIC1BZ3ggZGJJbnN0YW5jZQojICAgRGF0YWJhc2U6Om5ld0luc3RhbmNlIGRiSW5zdGFuY2UgImRlZmF1bHQubG9jYWwiCiMKIyBAZXhpdGNvZGUgMSBpZiBkbnMgZmlsZSBub3QgYWJsZSB0byBsb2FkZWQKRGF0YWJhc2U6Om5ld0luc3RhbmNlKCkgewogIGxvY2FsIC1uIGluc3RhbmNlTmV3SW5zdGFuY2U9JDEKICBsb2NhbCBkc249IiQyIgogIGxvY2FsIERTTl9GSUxFCgogIGlmIFtbIC12IGluc3RhbmNlTmV3SW5zdGFuY2VbJ0lOSVRJQUxJWkVEJ10gJiYgIiR7aW5zdGFuY2VOZXdJbnN0YW5jZVsnSU5JVElBTElaRUQnXTotMH0iID09ICIxIiBdXTsgdGhlbgogICAgcmV0dXJuCiAgZmkKCiAgIyBmaW5hbCBhdXRoIGZpbGUgZ2VuZXJhdGVkIGZyb20gZG5zIGZpbGUKICBpbnN0YW5jZU5ld0luc3RhbmNlWydBVVRIX0ZJTEUnXT0iIgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0RTTl9GSUxFJ109IiIKCiAgIyBjaGVjayBkc24gZmlsZQogIERTTl9GSUxFPSIkKENvbmY6OmdldEFic29sdXRlRmlsZSAiZHNuIiAiJHtkc259IiAiZW52IikiIHx8IHJldHVybiAxCiAgRGF0YWJhc2U6OmNoZWNrRHNuRmlsZSAiJHtEU05fRklMRX0iIHx8IHJldHVybiAxCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnRFNOX0ZJTEUnXT0iJHtEU05fRklMRX0iCgogICMgc2hlbGxjaGVjayBzb3VyY2U9L3NyYy9EYXRhYmFzZS90ZXN0c0RhdGEvZHNuX3ZhbGlkLmVudgogIHNvdXJjZSAiJHtpbnN0YW5jZU5ld0luc3RhbmNlWydEU05fRklMRSddfSIKCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnVVNFUiddPSIke1VTRVJ9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1BBU1NXT1JEJ109IiR7UEFTU1dPUkR9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0hPU1ROQU1FJ109IiR7SE9TVE5BTUV9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1BPUlQnXT0iJHtQT1JUfSIKCiAgIyBnZW5lcmF0ZSBhdXRoRmlsZSBmb3IgZWFzeSBhdXRoZW50aWNhdGlvbgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0FVVEhfRklMRSddPSQobWt0ZW1wIC1wICIke1RNUERJUjotL3RtcH0iIC10ICJteXNxbC5YWFhYWFhYWFhYWFgiKQogICgKICAgIGVjaG8gIltjbGllbnRdIgogICAgZWNobyAidXNlciA9ICR7VVNFUn0iCiAgICBlY2hvICJwYXNzd29yZCA9ICR7UEFTU1dPUkR9IgogICAgZWNobyAiaG9zdCA9ICR7SE9TVE5BTUV9IgogICAgZWNobyAicG9ydCA9ICR7UE9SVH0iCiAgKSA+IiR7aW5zdGFuY2VOZXdJbnN0YW5jZVsnQVVUSF9GSUxFJ119IgoKICAjIHNvbWUgb2YgdGhvc2UgdmFsdWVzIGNhbiBiZSBvdmVycmlkZGVuIHVzaW5nIHRoZSBkc24gZmlsZQogICMgU0tJUF9DT0xVTU5fTkFNRVMgZW5hYmxlZCBieSBkZWZhdWx0CiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnU0tJUF9DT0xVTU5fTkFNRVMnXT0iJHtTS0lQX0NPTFVNTl9OQU1FUzotMX0iCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnU1NMX09QVElPTlMnXT0iJHtNWVNRTF9TU0xfT1BUSU9OUzotLS1zc2wtbW9kZT1ESVNBQkxFRH0iCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnUVVFUllfT1BUSU9OUyddPSIke01ZU1FMX1FVRVJZX09QVElPTlM6LS0tYmF0Y2ggLS1yYXcgLS1kZWZhdWx0LWNoYXJhY3Rlci1zZXQ9dXRmOH0iCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnRFVNUF9PUFRJT05TJ109IiR7TVlTUUxfRFVNUF9PUFRJT05TOi0tLWRlZmF1bHQtY2hhcmFjdGVyLXNldD11dGY4IC0tY29tcHJlc3MgLS1oZXgtYmxvYiAtLXJvdXRpbmVzIC0tdHJpZ2dlcnMgLS1zaW5nbGUtdHJhbnNhY3Rpb24gLS1zZXQtZ3RpZC1wdXJnZWQ9T0ZGIC0tY29sdW1uLXN0YXRpc3RpY3M9MCAke2luc3RhbmNlTmV3SW5zdGFuY2VbJ1NTTF9PUFRJT05TJ119fSIKICBpbnN0YW5jZU5ld0luc3RhbmNlWydEQl9JTVBPUlRfT1BUSU9OUyddPSIke0RCX0lNUE9SVF9PUFRJT05TOi0tLWNvbm5lY3QtdGltZW91dD01IC0tYmF0Y2ggLS1yYXcgLS1kZWZhdWx0LWNoYXJhY3Rlci1zZXQ9dXRmOH0iCgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0lOSVRJQUxJWkVEJ109MQp9CgojIEBkZXNjcmlwdGlvbiBteXNxbCBxdWVyeSBvbiBhIGdpdmVuIGRiCiMgQHdhcm5pbmcgY291bGQgdXNlIFFVRVJZX09QVElPTlMgdmFyaWFibGUgZnJvbSBkc24gaWYgZGVmaW5lZAojIEBleGFtcGxlCiMgICBjYXQgZmlsZS5zcWwgfCBEYXRhYmFzZTo6cXVlcnkgLi4uCiMgQGFyZyAkMSBpbnN0YW5jZVF1ZXJ5OiZNYXA8U3RyaW5nLFN0cmluZz4gKHBhc3NlZCBieSByZWZlcmVuY2UpIGRhdGFiYXNlIGluc3RhbmNlIHRvIHVzZQojIEBhcmcgJDIgc3FsUXVlcnk6U3RyaW5nIChvcHRpb25hbCkgc3FsIHF1ZXJ5IG9yIHNxbCBmaWxlIHRvIGV4ZWN1dGUuIGlmIG5vdCBwcm92aWRlZCBvciBlbXB0eSwgdGhlIGNvbW1hbmQgY2FuIGJlIHBpcGVkCiMgQGFyZyAkMyBkYk5hbWU6U3RyaW5nIChvcHRpb25hbCkgdGhlIGRiIG5hbWUKIwojIEBleGl0Y29kZSBteXNxbCBjb21tYW5kIHN0YXR1cyBjb2RlCkRhdGFiYXNlOjpxdWVyeSgpIHsKICBsb2NhbCAtbiBpbnN0YW5jZVF1ZXJ5PSQxCiAgbG9jYWwgLWEgbXlzcWxDb21tYW5kPSgpCiAgbG9jYWwgLWEgcXVlcnlPcHRpb25zCgogIG15c3FsQ29tbWFuZCs9KG15c3FsKQogIG15c3FsQ29tbWFuZCs9KCItLWRlZmF1bHRzLWV4dHJhLWZpbGU9JHtpbnN0YW5jZVF1ZXJ5WydBVVRIX0ZJTEUnXX0iKQogIElGUz0nICcgcmVhZCAtciAtYSBxdWVyeU9wdGlvbnMgPDw8IiR7aW5zdGFuY2VRdWVyeVsnUVVFUllfT1BUSU9OUyddfSIKICBteXNxbENvbW1hbmQrPSgiJHtxdWVyeU9wdGlvbnNbQF19IikKICBpZiBbWyAiJHtpbnN0YW5jZVF1ZXJ5WydTS0lQX0NPTFVNTl9OQU1FUyddfSIgPSAiMSIgXV07IHRoZW4KICAgIG15c3FsQ29tbWFuZCs9KCItcyIgIi0tc2tpcC1jb2x1bW4tbmFtZXMiKQogIGZpCiAgIyBhZGQgb3B0aW9uYWwgZGIgbmFtZQogIGlmIFtbIC1uICIkezMreH0iIF1dOyB0aGVuCiAgICBteXNxbENvbW1hbmQrPSgiJDMiKQogIGZpCiAgIyBhZGQgb3B0aW9uYWwgc3FsIHF1ZXJ5CiAgaWYgW1sgLW4gIiR7Mit4fSIgJiYgLW4gIiQyIiAmJiAhIC1mICIkMiIgXV07IHRoZW4KICAgIG15c3FsQ29tbWFuZCs9KCItZSIpCiAgICBteXNxbENvbW1hbmQrPSgiJDIiKQogIGZpCiAgTG9nOjpkaXNwbGF5RGVidWcgIiQocHJpbnRmICJleGVjdXRlIGNvbW1hbmQ6ICclcyciICIke215c3FsQ29tbWFuZFsqXX0iKSIKCiAgaWYgW1sgLWYgIiQyIiBdXTsgdGhlbgogICAgIiR7bXlzcWxDb21tYW5kW0BdfSIgPCIkMiIKICBlbHNlCiAgICAiJHtteXNxbENvbW1hbmRbQF19IgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIHNldCB0aGUgZ2VuZXJhbCBvcHRpb25zIHRvIHVzZSBvbiBteXNxbCBjb21tYW5kIHRvIHF1ZXJ5IHRoZSBkYXRhYmFzZQojIERpZmZlcnMgdGhhbiBzZXRPcHRpb25zIGluIHRoZSB3YXkgdGhhdCB0aGVzZSBvcHRpb25zIGNvdWxkIGNoYW5nZSBlYWNoIHRpbWUKIwojIEBhcmcgJDEgaW5zdGFuY2VTZXRRdWVyeU9wdGlvbnM6Jk1hcDxTdHJpbmcsU3RyaW5nPiAocGFzc2VkIGJ5IHJlZmVyZW5jZSkgZGF0YWJhc2UgaW5zdGFuY2UgdG8gdXNlCiMgQGFyZyAkMiBvcHRpb25zTGlzdDpTdHJpbmcgcXVlcnkgb3B0aW9ucyBsaXN0CkRhdGFiYXNlOjpzZXRRdWVyeU9wdGlvbnMoKSB7CiAgbG9jYWwgLW4gaW5zdGFuY2VTZXRRdWVyeU9wdGlvbnM9JDEKICAjIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIwMzQKICBpbnN0YW5jZVNldFF1ZXJ5T3B0aW9uc1snUVVFUllfT1BUSU9OUyddPSIkMiIKfQoKIyBAZGVzY3JpcHRpb24gYnkgZGVmYXVsdCB3ZSBza2lwIHRoZSBjb2x1bW4gbmFtZXMKIyBidXQgc29tZXRpbWVzIHdlIG5lZWQgY29sdW1uIG5hbWVzIHRvIGRpc3BsYXkgc29tZSByZXN1bHRzCiMgZGlzYWJsZSB0aGlzIG9wdGlvbiB0ZW1wb3JhcmlseSBhbmQgdGhlbiByZXN0b3JlIGl0IHRvIHRydWUKIwojIEBhcmcgJDEgaW5zdGFuY2VTZXRRdWVyeU9wdGlvbnM6Jk1hcDxTdHJpbmcsU3RyaW5nPiAocGFzc2VkIGJ5IHJlZmVyZW5jZSkgZGF0YWJhc2UgaW5zdGFuY2UgdG8gdXNlCiMgQGFyZyAkMiBza2lwQ29sdW1uTmFtZXM6Qm9vbGVhbiAwIHRvIGRpc2FibGUsIDEgdG8gZW5hYmxlIChoaWRlIGNvbHVtbiBuYW1lcykKRGF0YWJhc2U6OnNraXBDb2x1bW5OYW1lcygpIHsKICBsb2NhbCAtbiBpbnN0YW5jZVNraXBDb2x1bW5OYW1lcz0kMQogICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNAogIGluc3RhbmNlU2tpcENvbHVtbk5hbWVzWydTS0lQX0NPTFVNTl9OQU1FUyddPSIkMiIKfQoKIyBAZGVzY3JpcHRpb24gcHJlcGVuZCBkaXJlY3RvcmllcyB0byB0aGUgUEFUSCBlbnZpcm9ubWVudCB2YXJpYWJsZQojIEBhcmcgJEAgYXJnczpTdHJpbmdbXSBsaXN0IG9mIGRpcmVjdG9yaWVzIHRvIHByZXBlbmQKIyBAc2V0IFBBVEggdXBkYXRlIFBBVEggd2l0aCB0aGUgZGlyZWN0b3JpZXMgcHJlcGVuZGVkCkVudjo6cGF0aFByZXBlbmQoKSB7CiAgbG9jYWwgYXJnCiAgZm9yIGFyZyBpbiAiJEAiOyBkbwogICAgaWYgW1sgLWQgIiR7YXJnfSIgJiYgIjoke1BBVEh9OiIgIT0gKiI6JHthcmd9OiIqIF1dOyB0aGVuCiAgICAgIFBBVEg9IiQocmVhbHBhdGggIiR7YXJnfSIpOiR7UEFUSH0iCiAgICBmaQogIGRvbmUKfQoKIyBAZGVzY3JpcHRpb24gRGlzcGxheSBtZXNzYWdlIHVzaW5nIGVycm9yIGNvbG9yIChyZWQpIGFuZCBleGl0IGltbWVkaWF0ZWx5IHdpdGggZXJyb3Igc3RhdHVzIDEKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpmYXRhbCgpIHsKICBlY2hvIC1lICIke19fRVJST1JfQ09MT1J9RkFUQUwgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBMb2c6OmxvZ0ZhdGFsICIkMSIKICBleGl0IDEKfQoKIyBAZGVzY3JpcHRpb24gbG9nIG1lc3NhZ2UgdG8gZmlsZQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmxvZ0RlYnVnKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMID49IF9fTEVWRUxfREVCVUcpKTsgdGhlbgogICAgTG9nOjpsb2dNZXNzYWdlICIkezI6LURFQlVHfSIgIiQxIgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGxvZyBtZXNzYWdlIHRvIGZpbGUKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpsb2dJbmZvKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMID49IF9fTEVWRUxfSU5GTykpOyB0aGVuCiAgICBMb2c6OmxvZ01lc3NhZ2UgIiR7MjotSU5GT30iICIkMSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBlbnN1cmUgcnVubmluZyB1c2VyIGlzIG5vdCByb290CiMgQGV4aXRjb2RlIDEgaWYgY3VycmVudCB1c2VyIGlzIHJvb3QKIyBAc3RkZXJyIGRpYWdub3N0aWNzIGluZm9ybWF0aW9uIGlzIGRpc3BsYXllZApMaW51eDo6cmVxdWlyZUV4ZWN1dGVkQXNVc2VyKCkgewogIGlmIFtbICIkKGlkIC11KSIgPSAiMCIgXV07IHRoZW4KICAgIExvZzo6ZmF0YWwgInRoaXMgc2NyaXB0IHNob3VsZCBiZSBleGVjdXRlZCBhcyBub3JtYWwgdXNlciIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBnZXQgYWJzb2x1dGUgY29uZiBmaWxlIGZyb20gc3BlY2lmaWVkIGNvbmYgZm9sZGVyIGRlZHVjZWQgdXNpbmcgdGhlc2UgcnVsZXMKIyAgICogZnJvbSBhYnNvbHV0ZSBmaWxlIChpZ25vcmVzIDxjb25mRm9sZGVyPiBhbmQgPGV4dGVuc2lvbj4pCiMgICAqIHJlbGF0aXZlIHRvIHdoZXJlIHNjcmlwdCBpcyBleGVjdXRlZCAoaWdub3JlcyA8Y29uZkZvbGRlcj4gYW5kIDxleHRlbnNpb24+KQojICAgKiBmcm9tIGhvbWUvLmJhc2gtdG9vbHMvPGNvbmZGb2xkZXI+CiMgICAqIGZyb20gZnJhbWV3b3JrIGNvbmYvPGNvbmZGb2xkZXI+CiMKIyBAYXJnICQxIGNvbmZGb2xkZXI6U3RyaW5nIHRoZSBkaXJlY3RvcnkgbmFtZSAobm90IHRoZSBwYXRoKSB0byBsaXN0CiMgQGFyZyAkMiBjb25mOlN0cmluZyBmaWxlIHRvIHVzZSB3aXRob3V0IGV4dGVuc2lvbgojIEBhcmcgJDMgZXh0ZW5zaW9uOlN0cmluZyB0aGUgZXh0ZW5zaW9uICguc2ggYnkgZGVmYXVsdCkKIwojIEBzdGRvdXQgYWJzb2x1dGUgY29uZiBmaWxlbmFtZQojIEBleGl0Y29kZSAxIGlmIGZpbGUgaXMgbm90IGZvdW5kIGluIGFueSBsb2NhdGlvbgpDb25mOjpnZXRBYnNvbHV0ZUZpbGUoKSB7CiAgbG9jYWwgY29uZkZvbGRlcj0iJDEiCiAgbG9jYWwgY29uZj0iJDIiCiAgbG9jYWwgZXh0ZW5zaW9uPSIkezMtLnNofSIKICBpZiBbWyAtbiAiJHtleHRlbnNpb259IiAmJiAiJHtleHRlbnNpb246MDoxfSIgIT0gIi4iIF1dOyB0aGVuCiAgICBleHRlbnNpb249Ii4ke2V4dGVuc2lvbn0iCiAgZmkKCiAgdGVzdEFicygpIHsKICAgIGxvY2FsIHJlc3VsdAogICAgcmVzdWx0PSIkKHJlYWxwYXRoIC1lICIkMSIgMj4vZGV2L251bGwpIgogICAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTgxCiAgICBpZiBbWyAiJD8iID0gIjAiICYmIC1mICIke3Jlc3VsdH0iIF1dOyB0aGVuCiAgICAgIGVjaG8gIiR7cmVzdWx0fSIKICAgICAgcmV0dXJuIDAKICAgIGZpCiAgICByZXR1cm4gMQogIH0KCiAgIyBjb25mIGlzIGFic29sdXRlIGZpbGUgKGluY2x1ZGluZyBleHRlbnNpb24pCiAgdGVzdEFicyAiJHtjb25mRm9sZGVyfSR7ZXh0ZW5zaW9ufSIgJiYgcmV0dXJuIDAKICAjIGNvbmYgaXMgYWJzb2x1dGUgZmlsZQogIHRlc3RBYnMgIiR7Y29uZkZvbGRlcn0iICYmIHJldHVybiAwCiAgIyBjb25mIGlzIGFic29sdXRlIGZpbGUgKGluY2x1ZGluZyBleHRlbnNpb24pCiAgdGVzdEFicyAiJHtjb25mfSR7ZXh0ZW5zaW9ufSIgJiYgcmV0dXJuIDAKICAjIGNvbmYgaXMgYWJzb2x1dGUgZmlsZQogIHRlc3RBYnMgIiR7Y29uZn0iICYmIHJldHVybiAwCgogICMgcmVsYXRpdmUgdG8gd2hlcmUgc2NyaXB0IGlzIGV4ZWN1dGVkIChpbmNsdWRpbmcgZXh0ZW5zaW9uKQogIGlmIFtbIC1uICIke0NVUlJFTlRfRElSK3h4eH0iIF1dOyB0aGVuCiAgICB0ZXN0QWJzICIkKEZpbGU6OmNvbmNhdGVuYXRlUGF0aCAiJHtDVVJSRU5UX0RJUn0iICIke2NvbmZGb2xkZXJ9IikvJHtjb25mfSR7ZXh0ZW5zaW9ufSIgJiYgcmV0dXJuIDAKICBmaQogICMgZnJvbSBob21lLy5iYXNoLXRvb2xzLzxjb25mRm9sZGVyPgogIHRlc3RBYnMgIiQoRmlsZTo6Y29uY2F0ZW5hdGVQYXRoICIke0hPTUV9Ly5iYXNoLXRvb2xzIiAiJHtjb25mRm9sZGVyfSIpLyR7Y29uZn0ke2V4dGVuc2lvbn0iICYmIHJldHVybiAwCgogIGlmIFtbIC1uICIke0ZSQU1FV09SS19ST09UX0RJUit4eHh9IiBdXTsgdGhlbgogICAgIyBmcm9tIGZyYW1ld29yayBjb25mLzxjb25mRm9sZGVyPiAoaW5jbHVkaW5nIGV4dGVuc2lvbikKICAgIHRlc3RBYnMgIiQoRmlsZTo6Y29uY2F0ZW5hdGVQYXRoICIke0ZSQU1FV09SS19ST09UX0RJUn0vY29uZiIgIiR7Y29uZkZvbGRlcn0iKS8ke2NvbmZ9JHtleHRlbnNpb259IiAmJiByZXR1cm4gMAoKICAgICMgZnJvbSBmcmFtZXdvcmsgY29uZi88Y29uZkZvbGRlcj4KICAgIHRlc3RBYnMgIiQoRmlsZTo6Y29uY2F0ZW5hdGVQYXRoICIke0ZSQU1FV09SS19ST09UX0RJUn0vY29uZiIgIiR7Y29uZkZvbGRlcn0iKS8ke2NvbmZ9IiAmJiByZXR1cm4gMAogIGZpCgogICMgZmlsZSBub3QgZm91bmQKICBMb2c6OmRpc3BsYXlFcnJvciAiY29uZiBmaWxlICcke2NvbmZ9JyBub3QgZm91bmQiCgogIHJldHVybiAxCn0KCiMgQGRlc2NyaXB0aW9uIGNoZWNrIGlmIGRzbiBmaWxlIGhhcyBhbGwgdGhlIG1hbmRhdG9yeSB2YXJpYWJsZXMgc2V0CiMgTWFuZGF0b3J5IHZhcmlhYmxlcyBhcmU6IEhPU1ROQU1FLCBVU0VSLCBQQVNTV09SRCwgUE9SVAojCiMgQGFyZyAkMSBkc25GaWxlTmFtZTpTdHJpbmcgZHNuIGFic29sdXRlIGZpbGVuYW1lCiMgQHNldCBIT1NUTkFNRSBsb2FkZWQgZnJvbSBkc24gZmlsZQojIEBzZXQgUE9SVCBsb2FkZWQgZnJvbSBkc24gZmlsZQojIEBzZXQgVVNFUiBsb2FkZWQgZnJvbSBkc24gZmlsZQojIEBzZXQgUEFTU1dPUkQgbG9hZGVkIGZyb20gZHNuIGZpbGUKIyBAZXhpdGNvZGUgMCBvbiB2YWxpZCBmaWxlCiMgQGV4aXRjb2RlIDEgaWYgb25lIG9mIHRoZSBwcm9wZXJ0aWVzIG9mIHRoZSBjb25mIGZpbGUgaXMgaW52YWxpZCBvciBpZiBmaWxlIG5vdCBmb3VuZAojIEBzdGRlcnIgbG9nIG91dHB1dCBpZiBlcnJvciBmb3VuZCBpbiBjb25mIGZpbGUKRGF0YWJhc2U6OmNoZWNrRHNuRmlsZSgpIHsKICBsb2NhbCBkc25GaWxlTmFtZT0iJDEiCiAgaWYgW1sgISAtZiAiJHtkc25GaWxlTmFtZX0iIF1dOyB0aGVuCiAgICBMb2c6OmRpc3BsYXlFcnJvciAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gbm90IGZvdW5kIgogICAgcmV0dXJuIDEKICBmaQoKICAoCiAgICB1bnNldCBIT1NUTkFNRSBQT1JUIFBBU1NXT1JEIFVTRVIKICAgICMgc2hlbGxjaGVjayBzb3VyY2U9L3NyYy9EYXRhYmFzZS90ZXN0c0RhdGEvZHNuX3ZhbGlkLmVudgogICAgc291cmNlICIke2RzbkZpbGVOYW1lfSIKICAgIGlmIFtbIC16ICR7SE9TVE5BTUUreH0gXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogSE9TVE5BTUUgbm90IHByb3ZpZGVkIgogICAgICByZXR1cm4gMQogICAgZmkKICAgIGlmIFtbIC16ICIke0hPU1ROQU1FfSIgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5V2FybmluZyAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gOiBIT1NUTkFNRSB2YWx1ZSBub3QgcHJvdmlkZWQiCiAgICBmaQogICAgaWYgW1sgIiR7SE9TVE5BTUV9IiA9ICJsb2NhbGhvc3QiIF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheVdhcm5pbmcgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogY2hlY2sgdGhhdCBIT1NUTkFNRSBzaG91bGQgbm90IGJlIDEyNy4wLjAuMSBpbnN0ZWFkIG9mIGxvY2FsaG9zdCIKICAgIGZpCiAgICBpZiBbWyAteiAiJHtQT1JUK3h9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlFcnJvciAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gOiBQT1JUIG5vdCBwcm92aWRlZCIKICAgICAgcmV0dXJuIDEKICAgIGZpCiAgICBpZiAhIFtbICR7UE9SVH0gPX4gXlswLTldKyQgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogUE9SVCBpbnZhbGlkIgogICAgICByZXR1cm4gMQogICAgZmkKICAgIGlmIFtbIC16ICIke1VTRVIreH0iIF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheUVycm9yICJkc24gZmlsZSAke2RzbkZpbGVOYW1lfSA6IFVTRVIgbm90IHByb3ZpZGVkIgogICAgICByZXR1cm4gMQogICAgZmkKICAgIGlmIFtbIC16ICIke1BBU1NXT1JEK3h9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlFcnJvciAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gOiBQQVNTV09SRCBub3QgcHJvdmlkZWQiCiAgICAgIHJldHVybiAxCiAgICBmaQogICkKfQoKIyBAZGVzY3JpcHRpb24gbG9nIG1lc3NhZ2UgdG8gZmlsZQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmxvZ0ZhdGFsKCkgewogIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1GQVRBTH0iICIkMSIKfQoKIyBAZGVzY3JpcHRpb24gSW50ZXJuYWw6IGNvbW1vbiBsb2cgbWVzc2FnZQojIEBleGFtcGxlIHRleHQKIyAgIFtkYXRlXXxbbGV2ZWxNc2ddfG1lc3NhZ2UKIwojIEBleGFtcGxlIHRleHQKIyAgIDIwMjAtMDEtMTkgMTk6MjA6MjF8RVJST1IgIHxsb2cgZXJyb3IKIyAgIDIwMjAtMDEtMTkgMTk6MjA6MjF8U0tJUFBFRHxsb2cgc2tpcHBlZAojCiMgQGFyZyAkMSBsZXZlbE1zZzpTdHJpbmcgbWVzc2FnZSdzIGxldmVsIGRlc2NyaXB0aW9uIChlZzogU1RBVFVTLCBFUlJPUiwgLi4uKQojIEBhcmcgJDIgbXNnOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CiMgQGVudiBCQVNIX0ZSQU1FV09SS19MT0dfRklMRSBTdHJpbmcgbG9nIGZpbGUgdG8gdXNlLCBkbyBub3RoaW5nIGlmIGVtcHR5CiMgQGVudiBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgaW50IGxvZyBsZXZlbCBsb2cgb25seSBpZiA+IE9GRiBvciBmYXRhbCBtZXNzYWdlcwojIEBzdGRlcnIgZGlhZ25vc3RpY3MgaW5mb3JtYXRpb24gaXMgZGlzcGxheWVkCiMgQHJlcXVpcmUgRW52OjpyZXF1aXJlTG9hZAojIEByZXF1aXJlIExvZzo6cmVxdWlyZUxvYWQKTG9nOjpsb2dNZXNzYWdlKCkgewogIGxvY2FsIGxldmVsTXNnPSIkMSIKICBsb2NhbCBtc2c9IiQyIgogIGxvY2FsIGRhdGUKCiAgaWYgW1sgLW4gIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IiBdXSAmJiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+IF9fTEVWRUxfT0ZGKSk7IHRoZW4KICAgIGRhdGU9IiQoZGF0ZSAnKyVZLSVtLSVkICVIOiVNOiVTJykiCiAgICB0b3VjaCAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iCiAgICBwcmludGYgIiVzfCU3c3wlc1xuIiAiJHtkYXRlfSIgIiR7bGV2ZWxNc2d9IiAiJHttc2d9IiA+PiIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBjb25jYXRlbmF0ZSAyIHBhdGhzIGFuZCBlbnN1cmUgdGhlIHBhdGggaXMgY29ycmVjdCB1c2luZyByZWFscGF0aCAtbQojIEBhcmcgJDEgYmFzZVBhdGg6U3RyaW5nCiMgQGFyZyAkMiBzdWJQYXRoOlN0cmluZwojIEByZXF1aXJlIExpbnV4OjpyZXF1aXJlUmVhbHBhdGhDb21tYW5kCkZpbGU6OmNvbmNhdGVuYXRlUGF0aCgpIHsKICBsb2NhbCBiYXNlUGF0aD0iJDEiCiAgbG9jYWwgc3ViUGF0aD0iJDIiCiAgbG9jYWwgZnVsbFBhdGg9IiR7YmFzZVBhdGg6KyR7YmFzZVBhdGh9L30ke3N1YlBhdGh9IgoKICByZWFscGF0aCAtbSAiJHtmdWxsUGF0aH0iIDI+L2Rldi9udWxsCn0KCiMgQGRlc2NyaXB0aW9uIERpc3BsYXkgbWVzc2FnZSB1c2luZyBlcnJvciBjb2xvciAocmVkKQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlFcnJvcigpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUwgPj0gX19MRVZFTF9FUlJPUikpOyB0aGVuCiAgICBlY2hvIC1lICIke19fRVJST1JfQ09MT1J9RVJST1IgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBmaQogIExvZzo6bG9nRXJyb3IgIiQxIgp9CgojIEBkZXNjcmlwdGlvbiBEaXNwbGF5IG1lc3NhZ2UgdXNpbmcgd2FybmluZyBjb2xvciAoeWVsbG93KQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlXYXJuaW5nKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCA+PSBfX0xFVkVMX1dBUk5JTkcpKTsgdGhlbgogICAgZWNobyAtZSAiJHtfX1dBUk5JTkdfQ09MT1J9V0FSTiAgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBmaQogIExvZzo6bG9nV2FybmluZyAiJDEiCn0KCiMgQGRlc2NyaXB0aW9uIGVuc3VyZSBlbnYgZmlsZXMgYXJlIGxvYWRlZAojIEBub2FyZ3MKIyBAZXhpdGNvZGUgMSBpZiBnZXRPcmRlcmVkQ29uZkZpbGVzIGZhaWxzCiMgQGV4aXRjb2RlIDIgaWYgb25lIG9mIGVudiBmaWxlcyBmYWlscyB0byBsb2FkCiMgQHN0ZGVyciBkaWFnbm9zdGljcyBpbmZvcm1hdGlvbiBpcyBkaXNwbGF5ZWQKRW52OjpyZXF1aXJlTG9hZCgpIHsKICBsb2NhbCBjb25maWdGaWxlc1N0cgogIGNvbmZpZ0ZpbGVzU3RyPSIkKEVudjo6Z2V0T3JkZXJlZENvbmZGaWxlcykiIHx8IHJldHVybiAxCgogIGxvY2FsIC1hIGNvbmZpZ0ZpbGVzCiAgcmVhZGFycmF5IC10IGNvbmZpZ0ZpbGVzIDw8PCIke2NvbmZpZ0ZpbGVzU3RyfSIKCiAgIyBpZiBlbXB0eSBzdHJpbmcsIHRoZXJlIHdpbGwgYmUgb25lIGVsZW1lbnQKICBpZiAoKCR7I2NvbmZpZ0ZpbGVzW0BdfSA9PSAwKSkgfHwgW1sgLXogIiR7Y29uZmlnRmlsZXNTdHJ9IiBdXTsgdGhlbgogICAgIyBzaG91bGQgbm90IGhhcHBlbiwgYXMgdGhlcmUgaXMgYWx3YXlzIGRlZmF1bHQgZmlsZQogICAgTG9nOjpkaXNwbGF5U2tpcHBlZCAibm8gZW52IGZpbGUgdG8gbG9hZCIKICAgIHJldHVybiAwCiAgZmkKCiAgRW52OjptZXJnZUNvbmZGaWxlcyAiJHtjb25maWdGaWxlc1tAXX0iIHx8IHsKICAgIExvZzo6ZGlzcGxheUVycm9yICJ3aGlsZSBsb2FkaW5nIGNvbmZpZyBmaWxlczogJHtjb25maWdGaWxlc1sqXX0iCiAgICByZXR1cm4gMgogIH0KfQoKIyBAZGVzY3JpcHRpb24gYWN0aXZhdGUgb3Igbm90IExvZzo6ZGlzcGxheSogYW5kIExvZzo6bG9nKiBmdW5jdGlvbnMKIyBiYXNlZCBvbiBCQVNIX0ZSQU1FV09SS19ESVNQTEFZX0xFVkVMIGFuZCBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwKIyBlbnZpcm9ubWVudCB2YXJpYWJsZXMgbG9hZGVkIGJ5IEVudjo6cmVxdWlyZUxvYWQKIyB0cnkgdG8gY3JlYXRlIGxvZyBmaWxlIGFuZCByb3RhdGUgaXQgaWYgbmVjZXNzYXJ5CiMgQG5vYXJncwojIEBzZXQgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMIGludCB0byBPRkYgbGV2ZWwgaWYgQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEUgaXMgZW1wdHkgb3Igbm90IHdyaXRhYmxlCiMgQGVudiBCQVNIX0ZSQU1FV09SS19ESVNQTEFZX0xFVkVMIGludAojIEBlbnYgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMIGludAojIEBlbnYgQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEUgU3RyaW5nCiMgQGVudiBCQVNIX0ZSQU1FV09SS19MT0dfRklMRV9NQVhfUk9UQVRJT04gaW50IGRvIGxvZyByb3RhdGlvbiBpZiA+IDAKIyBAZXhpdGNvZGUgMCBhbHdheXMgc3VjY2Vzc2Z1bAojIEBzdGRlcnIgZGlhZ25vc3RpY3MgaW5mb3JtYXRpb24gYWJvdXQgbG9nIGZpbGUgaXMgZGlzcGxheWVkCiMgQHJlcXVpcmUgRW52OjpyZXF1aXJlTG9hZAojIEByZXF1aXJlIFVJOjpyZXF1aXJlVGhlbWUKTG9nOjpyZXF1aXJlTG9hZCgpIHsKICBpZiBbWyAteiAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRTotfSIgXV07IHRoZW4KICAgIEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTD0ke19fTEVWRUxfT0ZGfQogICAgZXhwb3J0IEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTAogIGZpCgogIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMID4gX19MRVZFTF9PRkYpKTsgdGhlbgogICAgaWYgW1sgISAtZiAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iIF1dOyB0aGVuCiAgICAgIGlmCiAgICAgICAgISBta2RpciAtcCAiJChkaXJuYW1lICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSIpIiAyPi9kZXYvbnVsbCB8fAogICAgICAgICAgISB0b3VjaCAtLW5vLWNyZWF0ZSAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iIDI+L2Rldi9udWxsCiAgICAgIHRoZW4KICAgICAgICBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUw9JHtfX0xFVkVMX09GRn0KICAgICAgICBlY2hvIC1lICIke19fRVJST1JfQ09MT1J9RVJST1IgICAtIEZpbGUgJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0gaXMgbm90IHdyaXRhYmxlJHtfX1JFU0VUX0NPTE9SfSIgPiYyCiAgICAgIGZpCiAgICBlbGlmIFtbICEgLXcgIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IiBdXTsgdGhlbgogICAgICBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUw9JHtfX0xFVkVMX09GRn0KICAgICAgZWNobyAtZSAiJHtfX0VSUk9SX0NPTE9SfUVSUk9SICAgLSBGaWxlICR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IGlzIG5vdCB3cml0YWJsZSR7X19SRVNFVF9DT0xPUn0iID4mMgogICAgZmkKCiAgZmkKCiAgaWYgKChCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgPiBfX0xFVkVMX09GRikpOyB0aGVuCiAgICAjIHdpbGwgYWx3YXlzIGJlIGNyZWF0ZWQgZXZlbiBpZiBub3QgaW4gaW5mbyBsZXZlbAogICAgTG9nOjpsb2dNZXNzYWdlICJJTkZPIiAiTG9nZ2luZyB0byBmaWxlICR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IC0gTG9nIGxldmVsICR7QkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMfSIKICAgIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEVfTUFYX1JPVEFUSU9OID4gMCkpOyB0aGVuCiAgICAgIExvZzo6cm90YXRlICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSIgIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEVfTUFYX1JPVEFUSU9OfSIKICAgIGZpCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gZ2V0IGxpc3Qgb2YgZW52IGZpbGVzIHRvIGxvYWQKIyBpbiBvcmRlciB0byBtYWtlIHRoZW0gYXZhaWxhYmxlIGZvciBFbnY6OnJlcXVpcmVMb2FkCiMgQGVudiBCQVNIX0ZSQU1FV09SS19FTlZfRklMRVMgU3RyaW5nW10gbGlzdCBvZiBlbnYgZmlsZXMgdGhhdCBzaG91bGQgYmUgbG9hZGVkCiMgQGV4aXRjb2RlIDEgaWYgb25lIG9mIHRoZSBlbnYgZmlsZSBjYW5ub3QgYmUgZ2VuZXJhdGVkCiMgQGV4aXRjb2RlIDIgaWYgb25lIG9mIHRoZSBlbnYgZmlsZSBpcyBub3QgYSBmaWxlIG9yIHJlYWRhYmxlCiMgQHN0ZG91dCB0aGUgZW52IGZpbGVzIGFza2VkIHRvIGJlIGxvYWRlZAojIEBzdGRlcnIgZGlhZ25vc3RpYyBpbmZvcm1hdGlvbiBvbiBmYWlsdXJlCiMgQHNlZSBodHRwczovL2dpdGh1Yi5jb20vZmNoYXN0YW5ldC9iYXNoLXRvb2xzLWZyYW1ld29yay9ibG9iL21hc3Rlci9GcmFtZXdvcmtEb2MubWQjY29uZmlnX2ZpbGVfb3JkZXIKRW52OjpnZXRPcmRlcmVkQ29uZkZpbGVzKCkgewogIGxvY2FsIC1hIGNvbmZpZ0ZpbGVzPSgpCgogIGlmIFtbIC1uICIke0JBU0hfRlJBTUVXT1JLX0VOVl9GSUxFU1swXSsxfSIgXV07IHRoZW4KICAgICMgQkFTSF9GUkFNRVdPUktfRU5WX0ZJTEVTIGlzIGFuIGFycmF5CiAgICBjb25maWdGaWxlcys9KCIke0JBU0hfRlJBTUVXT1JLX0VOVl9GSUxFU1tAXX0iKQogIGZpCgogIGxvY2FsIGRlZmF1bHRFbnZGaWxlCiAgZGVmYXVsdEVudkZpbGU9IiQoRW52OjpjcmVhdGVEZWZhdWx0RW52RmlsZSkiIHx8IHJldHVybiAxCiAgY29uZmlnRmlsZXMrPSgiJHtkZWZhdWx0RW52RmlsZX0iKQoKICBsb2NhbCBmaWxlCiAgZm9yIGZpbGUgaW4gIiR7Y29uZmlnRmlsZXNbQF19IjsgZG8KICAgIGlmIFtbICEgLWYgIiR7ZmlsZX0iIHx8ICEgLXIgIiR7ZmlsZX0iIF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheUVycm9yICJPbmUgb2YgdGhlIGNvbmZpZyBmaWxlIGlzIG5vdCBhdmFpbGFibGUgJyR7ZmlsZX0nIgogICAgICByZXR1cm4gMgogICAgZmkKICAgIGVjaG8gIiR7ZmlsZX0iCiAgZG9uZQp9CgojIEBkZXNjcmlwdGlvbiBtZXJnZSBhbmQgbG9hZCBjb25mIGZpbGVzIHNwZWNpZmllZCBhcyBhcmd1bWVudAojIC0gZmlsZXMgYXJlIGNsZWFuZWQgZnJvbSBheSBjb21tZW50CiMgLSBtaXNzaW5nIHF1b3RlcyBhZnRlciBwcm9wZXJ0eSA9IHNpZ24gYXJlIGFkZGVkIGF1dG9tYXRpY2FsbHkKIyAtIGF1dG9tYXRpYyByZW1vdmUgb2YgYWxsIHdoaXRlc3BhY2UgYmVmb3JlIGFuZCBhZnRlciBkZWNsYXJhdGlvbnMKIyAtIGJhc2ggYXJyYXlzIGFyZSBub3Qgc3VwcG9ydGVkCiMgLSBpZiBhIHZhcmlhYmxlIGlzIGRlY2xhcmVkIGluIGZpcnN0IGZpbGUgYW5kIG92ZXJyaWRkZW4gbGF0ZXIgb24KIyAgIGluIHRoZSBzYW1lIGZpbGUgb3IgaW4gc3Vic2VxdWVudCBmaWxlcywgdGhvc2Ugb3ZlcmxvYWRzIHdpbGwgYmUKIyAgIGlnbm9yZWQKIyBAd2FybmluZyBpZiBhbiBlcnJvciBvY2N1cnMgd2hpbGUgbG9hZGluZyBvbmUgb2YgdGhlIGNvbmZpZyBmaWxlLCBleGl0IGNvZGUgMyBidXQgZW52aXJvbm1lbnQgY291bGQgYmUgcGFydGlhbGx5IGxvYWRlZAojIEBhcmcgJEAgYXJnczpTdHJpbmdbXSBsaXN0IG9mIGNvbmZpZ3VyYXRpb24gZmlsZXMgdG8gbG9hZCBpbiBvcmRlcgojIEBzZXQgZW52VmFycyBTdHJpbmcgd2lsbCBzZXQgaW4gZW52aXJvbm1lbnQgYWxsIHRoZSB2YXJpYWJsZXMgdGhhdCBoYXZlIGJlZW4gZGVjbGFyZWQgaW4gdGhlIGNvbmZpZyBmaWxlcwojIEBlbnYgZW52VmFycyBTdHJpbmcgdGhlIGVudiB2YXJpYWJsZXMgb2YgdGhlIGN1cnJlbnQgc2NyaXB0IGNvdWxkIGJlIHVzZWQgdG8gaW50ZXJwcmV0IHZhcmlhYmxlcyBkdXJpbmcgY29uZmlnIGZpbGVzIHBhcnNpbmcKIyBAZXhpdGNvZGUgMCBpZiBubyBjb25maWcgZmlsZXMgcHJvdmlkZWQgb3IgbG9hZCBjb21wbGV0ZWQgc3VjY2Vzc2Z1bGx5CiMgQGV4aXRjb2RlIDEgaWYgZXJyb3Igb2NjdXJyZWQgZHVyaW5nIHBhcnNpbmcgdGhlIGNvbmZpZyBmaWxlcyAoZmlsZSBub3QgZm91bmQsIGdyZXAsIGF3ayBvciBzZWQgZXJyb3IpCiMgQGV4aXRjb2RlIDIgaWYgdGVtcG9yYXJ5IGZpbGUgY2Fubm90IGJlIGNyZWF0ZWQKIyBAZXhpdGNvZGUgMyBpZiBhbiBlcnJvciBvY2N1cnJlZCBkdXJpbmcgY29uZmlnIGZpbGUgc291cmNpbmcKIyBAc3RkZXJyIGRpYWdub3N0aWNzIGluZm9ybWF0aW9uIGlzIGRpc3BsYXllZAojIEBzZWUgbGFyZ2VseSBpbnNwaXJlZCBidXQgbW9kaWZpZWQgZnJvbSBodHRwczovL29wZW5zb3VyY2UuY29tL2FydGljbGUvMjEvNS9wcm9jZXNzaW5nLWNvbmZpZ3VyYXRpb24tZmlsZXMtc2hlbGwKRW52OjptZXJnZUNvbmZGaWxlcygpIHsKICBsb2NhbCAtYSBjb25maWdGaWxlTGlzdD0oIiRAIikKCiAgaWYgKCgkeyNjb25maWdGaWxlTGlzdFtAXX0gPT0gMCkpOyB0aGVuCiAgICByZXR1cm4gMAogIGZpCgogIGxvY2FsIGNvbWJpbmVkQ29uZmlnRmlsZQogIGNvbWJpbmVkQ29uZmlnRmlsZT0iJChGcmFtZXdvcms6OmNyZWF0ZVRlbXBGaWxlICJtZXJnZUNvbmZGaWxlcyIpIiB8fCByZXR1cm4gMgoKICAoCiAgICAjIHJlbW92ZXMgYW55IHRyYWlsaW5nIHdoaXRlc3BhY2UgZnJvbSBlYWNoIGZpbGUsIGlmIGFueQogICAgIyB0aGlzIGlzIGFic29sdXRlbHkgcmVxdWlyZWQgd2hlbiBpbXBvcnRpbmcgaW50byBDb25maWdNYXBzCiAgICAjIHB1dCBxdW90ZXMgYXJvdW5kIHZhbHVlcwogICAgc2VkIC1FIC1lICQncy9ccyokLy8gOyAvXiQvZCA7IC9eIy4qJC9kIDsgcy89KFteIlwnXS4qKSQvPSJcXDEiLycgIiR7Y29uZmlnRmlsZUxpc3RbQF19IiB8CiAgICAgICMgcmVtb3ZlIGFsbCBjb21tZW50IGxpbmVzCiAgICAgIEZpbHRlcnM6OmNvbW1lbnRMaW5lcyB8CiAgICAgICMgaXRlcmF0ZXMgb3ZlciBlYWNoIGZpbGUgYW5kIHByaW50cyAoZGVmYXVsdCBhd2sgYmVoYXZpb3IpCiAgICAgICMgZWFjaCB1bmlxdWUgbGluZTsgb25seSB0YWtlcyBmaXJzdCB2YWx1ZSBhbmQgaWdub3JlcyBkdXBsaWNhdGVzCiAgICAgIGF3ayAtRj0gJyFsaW5lWyQxXSsrJwogICkgPiIke2NvbWJpbmVkQ29uZmlnRmlsZX0iIHx8IHJldHVybiAxCgogICMgaGF2ZSB0byBleHBvcnQgZXZlcnl0aGluZywgYW5kIHNvdXJjZSBpdCB0d2ljZToKICAjIDEpIGZpcnN0IHNvdXJjZSBpcyB0byByZWFsaXplIHZhcmlhYmxlcwogICMgMikgc2Vjb25kIHRpbWUgaXMgdG8gcmVhbGl6ZSByZWZlcmVuY2VzCiAgc2V0IC1vIGFsbGV4cG9ydAogICMgc2hlbGxjaGVjayBzb3VyY2U9LmZyYW1ld29yay1jb25maWcKICBzb3VyY2UgIiR7Y29tYmluZWRDb25maWdGaWxlfSIgfHwgcmV0dXJuIDMKICAjIHNoZWxsY2hlY2sgc291cmNlPS5mcmFtZXdvcmstY29uZmlnCiAgc291cmNlICIke2NvbWJpbmVkQ29uZmlnRmlsZX0iIHx8IHJldHVybiAzCiAgc2V0ICtvIGFsbGV4cG9ydAp9CgojIEBkZXNjcmlwdGlvbiBEaXNwbGF5IG1lc3NhZ2UgdXNpbmcgc2tpcCBjb2xvciAoeWVsbG93KQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlTa2lwcGVkKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCA+PSBfX0xFVkVMX0lORk8pKTsgdGhlbgogICAgZWNobyAtZSAiJHtfX1NLSVBQRURfQ09MT1J9U0tJUFBFRCAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBmaQogIExvZzo6bG9nU2tpcHBlZCAiJDEiCn0KCiMgQGRlc2NyaXB0aW9uIGxvZyBtZXNzYWdlIHRvIGZpbGUKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpsb2dFcnJvcigpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+PSBfX0xFVkVMX0VSUk9SKSk7IHRoZW4KICAgIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1FUlJPUn0iICIkMSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBsb2cgbWVzc2FnZSB0byBmaWxlCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6bG9nV2FybmluZygpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+PSBfX0xFVkVMX1dBUk5JTkcpKTsgdGhlbgogICAgTG9nOjpsb2dNZXNzYWdlICIkezI6LVdBUk5JTkd9IiAiJDEiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gVG8gYmUgY2FsbGVkIGJlZm9yZSBsb2dnaW5nIGluIHRoZSBsb2cgZmlsZQojIEBhcmcgJDEgZmlsZTpzdHJpbmcgbG9nIGZpbGUgbmFtZQojIEBhcmcgJDIgbWF4TG9nRmlsZXNDb3VudDppbnQgbWF4aW11bSBudW1iZXIgb2YgbG9nIGZpbGVzCkxvZzo6cm90YXRlKCkgewogIGxvY2FsIGZpbGU9IiQxIgogIGxvY2FsIG1heExvZ0ZpbGVzQ291bnQ9IiR7MjotNX0iCgogIGlmIFtbICEgLWYgIiR7ZmlsZX0iIF1dOyB0aGVuCiAgICBMb2c6OmRpc3BsYXlTa2lwcGVkICJMb2cgZmlsZSAke2ZpbGV9IGRvZXNuJ3QgZXhpc3QgeWV0IgogICAgcmV0dXJuIDAKICBmaQogIGZvciBpIGluICQoc2VxICQoKG1heExvZ0ZpbGVzQ291bnQgLSAxKSkgLTEgMSk7IGRvCiAgICBMb2c6OmRpc3BsYXlJbmZvICJMb2cgcm90YXRpb24gJHtmaWxlfS4ke2l9IHRvICR7ZmlsZX0uJCgoaSArIDEpKSIKICAgIG12ICIke2ZpbGV9LiJ7IiR7aX0iLCIkKChpICsgMSkpIn0gJj4vZGV2L251bGwgfHwgdHJ1ZQogIGRvbmUKICBpZiBjcCAiJHtmaWxlfSIgIiR7ZmlsZX0uMSIgJj4vZGV2L251bGw7IHRoZW4KICAgIGVjaG8gPiIke2ZpbGV9IiAjIHJlc2V0IGxvZyBmaWxlCiAgICBMb2c6OmRpc3BsYXlJbmZvICJMb2cgcm90YXRpb24gJHtmaWxlfSB0byAke2ZpbGV9LjEiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gZW5zdXJlIGNvbW1hbmQgcmVhbHBhdGggaXMgYXZhaWxhYmxlCiMgQGV4aXRjb2RlIDEgaWYgcmVhbHBhdGggY29tbWFuZCBub3QgYXZhaWxhYmxlCiMgQHN0ZGVyciBkaWFnbm9zdGljcyBpbmZvcm1hdGlvbiBpcyBkaXNwbGF5ZWQKTGludXg6OnJlcXVpcmVSZWFscGF0aENvbW1hbmQoKSB7CiAgQXNzZXJ0Ojpjb21tYW5kRXhpc3RzIHJlYWxwYXRoCn0KCiMgQGRlc2NyaXB0aW9uIGxvYWQgY29sb3IgdGhlbWUKIyBAbm9hcmdzCiMgQGVudiBCQVNIX0ZSQU1FV09SS19USEVNRSBTdHJpbmcgdGhlbWUgdG8gdXNlCiMgQGV4aXRjb2RlIDAgYWx3YXlzIHN1Y2Nlc3NmdWwKVUk6OnJlcXVpcmVUaGVtZSgpIHsKICBVSTo6dGhlbWUgIiR7QkFTSF9GUkFNRVdPUktfVEhFTUUtZGVmYXVsdH0iCn0KCiMgQGRlc2NyaXB0aW9uIGNoZWNrIGlmIGNvbW1hbmQgc3BlY2lmaWVkIGV4aXN0cyBvciByZXR1cm4gMQojIHdpdGggZXJyb3IgYW5kIG1lc3NhZ2UgaWYgbm90CiMKIyBAYXJnICQxIGNvbW1hbmROYW1lOlN0cmluZyBvbiB3aGljaCBleGlzdGVuY2UgbXVzdCBiZSBjaGVja2VkCiMgQGFyZyAkMiBoZWxwSWZOb3RFeGlzdHM6U3RyaW5nIGEgaGVscCBjb21tYW5kIHRvIGRpc3BsYXkgaWYgdGhlIGNvbW1hbmQgZG9lcyBub3QgZXhpc3QKIwojIEBleGl0Y29kZSAxIGlmIHRoZSBjb21tYW5kIHNwZWNpZmllZCBkb2VzIG5vdCBleGlzdAojIEBzdGRlcnIgZGlhZ25vc3RpYyBpbmZvcm1hdGlvbiArIGhlbHAgaWYgc2Vjb25kIGFyZ3VtZW50IGlzIHByb3ZpZGVkCkFzc2VydDo6Y29tbWFuZEV4aXN0cygpIHsKICBsb2NhbCBjb21tYW5kTmFtZT0iJDEiCiAgbG9jYWwgaGVscElmTm90RXhpc3RzPSIkMiIKCiAgIiR7QkFTSF9GUkFNRVdPUktfQ09NTUFORDotY29tbWFuZH0iIC12ICIke2NvbW1hbmROYW1lfSIgPi9kZXYvbnVsbCAyPi9kZXYvbnVsbCB8fCB7CiAgICBMb2c6OmRpc3BsYXlFcnJvciAiJHtjb21tYW5kTmFtZX0gaXMgbm90IGluc3RhbGxlZCwgcGxlYXNlIGluc3RhbGwgaXQiCiAgICBpZiBbWyAtbiAiJHtoZWxwSWZOb3RFeGlzdHN9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlJbmZvICIke2hlbHBJZk5vdEV4aXN0c30iCiAgICBmaQogICAgcmV0dXJuIDEKICB9CiAgcmV0dXJuIDAKfQoKIyBAZGVzY3JpcHRpb24gZGVmYXVsdCBlbnYgZmlsZSB3aXRoIGFsbCBkZWZhdWx0IHZhbHVlcwojIEBzdGRvdXQgdGhlIGRlZmF1bHQgZW52IGZpbGVwYXRoCkVudjo6Y3JlYXRlRGVmYXVsdEVudkZpbGUoKSB7CiAgbG9jYWwgZW52RmlsZQogIGVudkZpbGU9IiQoRnJhbWV3b3JrOjpjcmVhdGVUZW1wRmlsZSAiY3JlYXRlRGVmYXVsdEVudkZpbGVFbnZGaWxlIikiIHx8IHJldHVybiAyCgogICgKICAgIGVjaG8gIkJBU0hfRlJBTUVXT1JLX1RIRU1FPSR7QkFTSF9GUkFNRVdPUktfVEhFTUU6LWRlZmF1bHR9IgogICAgZWNobyAiQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMPSR7QkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMOi0wfSIKICAgIGVjaG8gIkJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUw9JHtCQVNIX0ZSQU1FV09SS19ESVNQTEFZX0xFVkVMOi0ke19fTEVWRUxfV0FSTklOR319IgogICAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDE2CiAgICBlY2hvICdCQVNIX0ZSQU1FV09SS19MT0dfRklMRT0iJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRTotIiR7RlJBTUVXT1JLX1JPT1RfRElSfS9sb2dzLyR7U0NSSVBUX05BTUV9LmxvZyJ9IicKICAgIGVjaG8gIkJBU0hfRlJBTUVXT1JLX0xPR19GSUxFX01BWF9ST1RBVElPTj0ke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFX01BWF9ST1RBVElPTjotNX0iCiAgKSA+IiR7ZW52RmlsZX0iCiAgZWNobyAiJHtlbnZGaWxlfSIKfQoKIyBAZGVzY3JpcHRpb24gcmVtb3ZlIGNvbW1lbnQgbGluZXMgZnJvbSBpbnB1dCBvciBmaWxlcyBwcm92aWRlZCBhcyBhcmd1bWVudHMKIyBAYXJnICRAIGZpbGVzOlN0cmluZ1tdIChvcHRpb25hbCkgdGhlIGZpbGVzIHRvIGZpbHRlcgojIEBlbnYgY29tbWVudExpbmVQcmVmaXggU3RyaW5nIHRoZSBjb21tZW50IGxpbmUgcHJlZml4IChkZWZhdWx0IHZhbHVlOiAjKQojIEBleGl0Y29kZSAwIGlmIGxpbmVzIGZpbHRlcmVkIG9yIG5vdAojIEBleGl0Y29kZSAyIGlmIGdyZXAgZmFpbHMgZm9yIGFueSBvdGhlciByZWFzb25zIHRoYW4gbm90IGZvdW5kCiMgQHN0ZGluIHRoZSBmaWxlIGFzIHN0ZGluIHRvIGZpbHRlciAoYWx0ZXJuYXRpdmUgdG8gZmlsZXMgYXJndW1lbnQpCiMgQHN0ZG91dCB0aGUgZmlsdGVyZWQgbGluZXMKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTIwCkZpbHRlcnM6OmNvbW1lbnRMaW5lcygpIHsKICBncmVwIC12eEUgIltbOmJsYW5rOl1dKigke2NvbW1lbnRMaW5lUHJlZml4Oi0jfS4qKT8iICIkQCIgfHwgdGVzdCAkPyA9IDEKfQoKIyBAZGVzY3JpcHRpb24gY3JlYXRlIGEgdGVtcCBmaWxlIHVzaW5nIGRlZmF1bHQgVE1QRElSIHZhcmlhYmxlCiMgaW5pdGlhbGl6ZWQgaW4gX2luY2x1ZGVzL19jb21tb25IZWFkZXIuc2gKIyBAZW52IFRNUERJUiBTdHJpbmcgKGRlZmF1bHQgdmFsdWUgL3RtcCkKIyBAYXJnICQxIHRlbXBsYXRlTmFtZTpTdHJpbmcgdGVtcGxhdGUgbmFtZSB0byB1c2Uob3B0aW9uYWwpCkZyYW1ld29yazo6Y3JlYXRlVGVtcEZpbGUoKSB7CiAgbWt0ZW1wIC1wICIke1RNUERJUjotL3RtcH0iIC10ICIkezE6LX0uWFhYWFhYWFhYWFhYIgp9CgojIEBkZXNjcmlwdGlvbiBsb2cgbWVzc2FnZSB0byBmaWxlCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6bG9nU2tpcHBlZCgpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+PSBfX0xFVkVMX0lORk8pKTsgdGhlbgogICAgTG9nOjpsb2dNZXNzYWdlICIkezI6LVNLSVBQRUR9IiAiJDEiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gbG9hZCBjb2xvcnMgdGhlbWUgY29uc3RhbnRzCiMgQHdhcm5pbmcgaWYgdHR5IG5vdCBvcGVuZWQsIG5vQ29sb3IgdGhlbWUgd2lsbCBiZSBjaG9zZW4KIyBAYXJnICQxIHRoZW1lOlN0cmluZyB0aGUgdGhlbWUgdG8gdXNlIChkZWZhdWx0LCBub0NvbG9yKQojIEBhcmcgJEAgYXJnczpTdHJpbmdbXQojIEBzZXQgX19FUlJPUl9DT0xPUiBTdHJpbmcgaW5kaWNhdGUgZXJyb3Igc3RhdHVzCiMgQHNldCBfX0lORk9fQ09MT1IgU3RyaW5nIGluZGljYXRlIGluZm8gc3RhdHVzCiMgQHNldCBfX1NVQ0NFU1NfQ09MT1IgU3RyaW5nIGluZGljYXRlIHN1Y2Nlc3Mgc3RhdHVzCiMgQHNldCBfX1dBUk5JTkdfQ09MT1IgU3RyaW5nIGluZGljYXRlIHdhcm5pbmcgc3RhdHVzCiMgQHNldCBfX1NLSVBQRURfQ09MT1IgU3RyaW5nIGluZGljYXRlIHNraXBwZWQgc3RhdHVzCiMgQHNldCBfX0RFQlVHX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSBkZWJ1ZyBzdGF0dXMKIyBAc2V0IF9fSEVMUF9DT0xPUiBTdHJpbmcgaW5kaWNhdGUgaGVscCBzdGF0dXMKIyBAc2V0IF9fVEVTVF9DT0xPUiBTdHJpbmcgbm90IHVzZWQKIyBAc2V0IF9fVEVTVF9FUlJPUl9DT0xPUiBTdHJpbmcgbm90IHVzZWQKIyBAc2V0IF9fSEVMUF9USVRMRV9DT0xPUiBTdHJpbmcgdXNlZCB0byBkaXNwbGF5IGhlbHAgdGl0bGUgaW4gaGVscCBzdHJpbmdzCiMgQHNldCBfX0hFTFBfT1BUSU9OX0NPTE9SIFN0cmluZyB1c2VkIHRvIGRpc3BsYXkgaGlnaGxpZ2h0IG9wdGlvbnMgaW4gaGVscCBzdHJpbmdzCiMKIyBAc2V0IF9fUkVTRVRfQ09MT1IgU3RyaW5nIHJlc2V0IGRlZmF1bHQgY29sb3IKIwojIEBzZXQgX19IRUxQX0VYQU1QTEUgU3RyaW5nIHRvIHJlbW92ZQojIEBzZXQgX19IRUxQX1RJVExFIFN0cmluZyB0byByZW1vdmUKIyBAc2V0IF9fSEVMUF9OT1JNQUwgU3RyaW5nIHRvIHJlbW92ZQojIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIwMzQKVUk6OnRoZW1lKCkgewogIGxvY2FsIHRoZW1lPSIkezEtZGVmYXVsdH0iCiAgaWYgW1sgISAiJHt0aGVtZX0iID1+IC1mb3JjZSQgXV0gJiYgISBBc3NlcnQ6OnR0eTsgdGhlbgogICAgdGhlbWU9Im5vQ29sb3IiCiAgZmkKICBjYXNlICIke3RoZW1lfSIgaW4KICAgIGRlZmF1bHQgfCBkZWZhdWx0LWZvcmNlKQogICAgICB0aGVtZT0iZGVmYXVsdCIKICAgICAgOzsKICAgIG5vQ29sb3IpIDs7CiAgICAqKQogICAgICBMb2c6OmZhdGFsICJpbnZhbGlkIHRoZW1lIHByb3ZpZGVkIgogICAgICA7OwogIGVzYWMKICBpZiBbWyAiJHt0aGVtZX0iID0gImRlZmF1bHQiIF1dOyB0aGVuCiAgICBCQVNIX0ZSQU1FV09SS19USEVNRT0iZGVmYXVsdCIKICAgICMgY2hlY2sgY29sb3JzIGFwcGxpY2FibGUgaHR0cHM6Ly9taXNjLmZsb2dpc29mdC5jb20vYmFzaC90aXBfY29sb3JzX2FuZF9mb3JtYXR0aW5nCiAgICBfX0VSUk9SX0NPTE9SPSdcZVszMW0nICAgICAgICAgIyBSZWQKICAgIF9fSU5GT19DT0xPUj0nXGVbNDRtJyAgICAgICAgICAjIHdoaXRlIG9uIGxpZ2h0Qmx1ZQogICAgX19TVUNDRVNTX0NPTE9SPSdcZVszMm0nICAgICAgICMgR3JlZW4KICAgIF9fV0FSTklOR19DT0xPUj0nXGVbMzNtJyAgICAgICAjIFllbGxvdwogICAgX19TS0lQUEVEX0NPTE9SPSdcZVszM20nICAgICAgICMgWWVsbG93CiAgICBfX0RFQlVHX0NPTE9SPSdcZVszN20nICAgICAgICAgIyBHcmV5CiAgICBfX0hFTFBfQ09MT1I9J1xlWzc7NDk7MzNtJyAgICAgIyBCbGFjayBvbiBHb2xkCiAgICBfX1RFU1RfQ09MT1I9J1xlWzEwMG0nICAgICAgICAgIyBMaWdodCBtYWdlbnRhCiAgICBfX1RFU1RfRVJST1JfQ09MT1I9J1xlWzQxbScgICAgIyB3aGl0ZSBvbiByZWQKICAgIF9fSEVMUF9USVRMRV9DT0xPUj0iXGVbMTszN20iICAjIEJvbGQKICAgIF9fSEVMUF9PUFRJT05fQ09MT1I9IlxlWzE7MzRtIiAjIEJsdWUKICAgICMgSW50ZXJuYWw6IHJlc2V0IGNvbG9yCiAgICBfX1JFU0VUX0NPTE9SPSdcZVswbScgIyBSZXNldCBDb2xvcgogICAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTU1LFNDMjAzNAogICAgX19IRUxQX0VYQU1QTEU9IiQoZWNobyAtZSAiXGVbMjs5N20iKSIKICAgICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjE1NSxTQzIwMzQKICAgIF9fSEVMUF9USVRMRT0iJChlY2hvIC1lICJcZVsxOzM3bSIpIgogICAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTU1LFNDMjAzNAogICAgX19IRUxQX05PUk1BTD0iJChlY2hvIC1lICJcMDMzWzBtIikiCiAgZWxzZQogICAgQkFTSF9GUkFNRVdPUktfVEhFTUU9Im5vQ29sb3IiCiAgICAjIGNoZWNrIGNvbG9ycyBhcHBsaWNhYmxlIGh0dHBzOi8vbWlzYy5mbG9naXNvZnQuY29tL2Jhc2gvdGlwX2NvbG9yc19hbmRfZm9ybWF0dGluZwogICAgX19FUlJPUl9DT0xPUj0nJwogICAgX19JTkZPX0NPTE9SPScnCiAgICBfX1NVQ0NFU1NfQ09MT1I9JycKICAgIF9fV0FSTklOR19DT0xPUj0nJwogICAgX19TS0lQUEVEX0NPTE9SPScnCiAgICBfX0RFQlVHX0NPTE9SPScnCiAgICBfX0hFTFBfQ09MT1I9JycKICAgIF9fVEVTVF9DT0xPUj0nJwogICAgX19URVNUX0VSUk9SX0NPTE9SPScnCiAgICBfX0hFTFBfVElUTEVfQ09MT1I9JycKICAgIF9fSEVMUF9PUFRJT05fQ09MT1I9JycKICAgICMgSW50ZXJuYWw6IHJlc2V0IGNvbG9yCiAgICBfX1JFU0VUX0NPTE9SPScnCiAgICBfX0hFTFBfRVhBTVBMRT0nJwogICAgX19IRUxQX1RJVExFPScnCiAgICBfX0hFTFBfTk9STUFMPScnCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gY2hlY2sgaWYgdHR5IChpbnRlcmFjdGl2ZSBtb2RlKSBpcyBhY3RpdmUKIyBAbm9hcmdzCiMgQGV4aXRjb2RlIDEgaWYgdHR5IG5vdCBhY3RpdmUKIyBAZW52IE5PTl9JTlRFUkFDVElWRSBpZiAxIGNvbnNpZGVyIGFzIG5vdCBpbnRlcmFjdGl2ZSBldmVuIGlmIGVudmlyb25tZW50IGlzIGludGVyYWN0aXZlCiMgQGVudiBJTlRFUkFDVElWRSBpZiAxIGNvbnNpZGVyIGFzIGludGVyYWN0aXZlIGV2ZW4gaWYgZW52aXJvbm1lbnQgaXMgbm90IGludGVyYWN0aXZlCiMgQHN0ZGVyciBkaWFnbm9zdGljIGluZm9ybWF0aW9uICsgaGVscCBpZiBzZWNvbmQgYXJndW1lbnQgaXMgcHJvdmlkZWQKQXNzZXJ0Ojp0dHkoKSB7CiAgaWYgW1sgIiR7Tk9OX0lOVEVSQUNUSVZFOi0wfSIgPSAiMSIgXV07IHRoZW4KICAgIHJldHVybiAxCiAgZmkKICBpZiBbWyAiJHtJTlRFUkFDVElWRTotMH0iID0gIjEiIF1dOyB0aGVuCiAgICByZXR1cm4gMAogIGZpCiAgW1sgLXQgMSB8fCAtdCAyIF1dCn0KCiMgRlVOQ1RJT05TCgpmYWNhZGVfbWFpbl9hMzk2ZTNlN2NiMzE0MDg0OGRlNGYwYWI0MzM4NDNiYigpIHsKIyBSRVFVSVJFUwpMaW51eDo6cmVxdWlyZUV4ZWN1dGVkQXNVc2VyCkVudjo6cmVxdWlyZUxvYWQKTG9nOjpyZXF1aXJlTG9hZApMaW51eDo6cmVxdWlyZVJlYWxwYXRoQ29tbWFuZApVSTo6cmVxdWlyZVRoZW1lCkNvbXBpbGVyOjpGYWNhZGU6OnJlcXVpcmVDb21tYW5kQmluRGlyCgojIEByZXF1aXJlIENvbXBpbGVyOjpGYWNhZGU6OnJlcXVpcmVDb21tYW5kQmluRGlyCgojIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIxNTQsU0MyMDE2CmZ1bmN0aW9uVG9DYWxsPSdEYjo6cXVlcnlPbmVEYXRhYmFzZScKIiR7ZnVuY3Rpb25Ub0NhbGx9IiAiJEAiCgp9CgpmYWNhZGVfbWFpbl9hMzk2ZTNlN2NiMzE0MDg0OGRlNGYwYWI0MzM4NDNiYiAiJEAiCg==" Compiler::Embed::extractFileFromBase64 \ "${embed_function_DbQueryOneDatabase}" \ @@ -1325,18 +1328,28 @@ Compiler::Facade::requireCommandBinDir Linux::requireExecutedAsUser # @require Compiler::Facade::requireCommandBinDir +# shellcheck disable=SC2034 + +#default values +# default value for FROM_DSN if from-aws not set +declare queryIsFile="0" +declare optionSeparator="|" +declare argQuery="" + +# other configuration +declare copyrightBeginYear="2020" +declare QUERIES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/dbQueries" +declare HOME_QUERIES_DIR="${HOME}/.bash-tools/dbQueries" declare example1=$'dbQueryAllDatabases databaseSize -j 12 --separator "|" --bar 2>/dev/null | column -s "|" -t -n -c 40' declare -a BASH_FRAMEWORK_ARGV_FILTERED=() copyrightCallback() { - local years - years="$(date +%Y)" - if [[ -n "${copyrightBeginYear}" && "${copyrightBeginYear}" != "${years}" ]]; then - years="${copyrightBeginYear}-${years}" + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" fi - echo "Copyright (c) ${years} François Chastanet" + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" } # shellcheck disable=SC2317 # if function is overridden @@ -1510,6 +1523,29 @@ optionBashFrameworkConfigCallback() { fi } +defaultFrameworkConfig="$( + cat <<'EOF' +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$" +# Source directories +FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" +) + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +EOF +)" + commandOptionParseFinished() { if [[ -z "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then BASH_FRAMEWORK_ENV_FILES=() @@ -1529,8 +1565,16 @@ commandOptionParseFinished() { # shellcheck disable=SC2034 BASH_FRAMEWORK_CONFIG_FILE="" # shellcheck source=/.framework-config - Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || - Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config file" + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || { + # load default template framework config + if [[ ! -f "${PERSISTENT_TMPDIR}/.framework-config" ]]; then + echo "${defaultFrameworkConfig}" > "${PERSISTENT_TMPDIR}/.framework-config" + fi + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${PERSISTENT_TMPDIR}" || { + Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config.default file" + } + Log::displayWarning "Command ${SCRIPT_NAME} - Load default .framework-config file - ${PERSISTENT_TMPDIR}/.framework-config" + } fi if [[ "${optionConfig}" = "1" ]]; then @@ -1562,13 +1606,13 @@ dbQueryAllDatabasesCommand() { shift || true if [[ "${options_parse_cmd}" = "parse" ]]; then - optionJobs=1 + optionJobs="1" local -i options_parse_optionParsedCountOptionJobs ((options_parse_optionParsedCountOptionJobs = 0)) || true optionProgressBar="0" local -i options_parse_optionParsedCountOptionProgressBar ((options_parse_optionParsedCountOptionProgressBar = 0)) || true - optionSeparator=| + optionSeparator="|" local -i options_parse_optionParsedCountOptionSeparator ((options_parse_optionParsedCountOptionSeparator = 0)) || true local -i options_parse_optionParsedCountOptionFromDsn @@ -1609,6 +1653,7 @@ dbQueryAllDatabasesCommand() { ((options_parse_optionParsedCountOptionDisplayLevel = 0)) || true local -i options_parse_argParsedCountArgQuery ((options_parse_argParsedCountArgQuery = 0)) || true + # shellcheck disable=SC2034 local -i options_parse_parsedArgIndex=0 while (($# > 0)); do local options_parse_arg="$1" @@ -1627,12 +1672,14 @@ dbQueryAllDatabasesCommand() { return 1 fi ((++options_parse_optionParsedCountOptionJobs)) + # shellcheck disable=SC2034 optionJobs="$1" optionJobsCallback "${options_parse_arg}" "${optionJobs}" ;; # Option 2/18 # Option optionProgressBar --bar|-b variableType Boolean min 0 max 1 authorizedValues '' regexp '' --bar | -b) + # shellcheck disable=SC2034 optionProgressBar="1" if ((options_parse_optionParsedCountOptionProgressBar >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1654,6 +1701,7 @@ dbQueryAllDatabasesCommand() { return 1 fi ((++options_parse_optionParsedCountOptionSeparator)) + # shellcheck disable=SC2034 optionSeparator="$1" optionSeparatorCallback "${options_parse_arg}" "${optionSeparator}" ;; @@ -1670,6 +1718,7 @@ dbQueryAllDatabasesCommand() { return 1 fi ((++options_parse_optionParsedCountOptionFromDsn)) + # shellcheck disable=SC2034 optionFromDsn="$1" ;; # Option 5/18 @@ -1685,12 +1734,14 @@ dbQueryAllDatabasesCommand() { return 1 fi ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 optionBashFrameworkConfig="$1" optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" ;; # Option 6/18 # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' --config) + # shellcheck disable=SC2034 optionConfig="1" if ((options_parse_optionParsedCountOptionConfig >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1701,6 +1752,7 @@ dbQueryAllDatabasesCommand() { # Option 7/18 # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' --verbose | -v) + # shellcheck disable=SC2034 optionInfoVerbose="1" if ((options_parse_optionParsedCountOptionInfoVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1713,6 +1765,7 @@ dbQueryAllDatabasesCommand() { # Option 8/18 # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vv) + # shellcheck disable=SC2034 optionDebugVerbose="1" if ((options_parse_optionParsedCountOptionDebugVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1725,6 +1778,7 @@ dbQueryAllDatabasesCommand() { # Option 9/18 # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vvv) + # shellcheck disable=SC2034 optionTraceVerbose="1" if ((options_parse_optionParsedCountOptionTraceVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1750,6 +1804,7 @@ dbQueryAllDatabasesCommand() { # Option 11/18 # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' --no-color) + # shellcheck disable=SC2034 optionNoColor="1" if ((options_parse_optionParsedCountOptionNoColor >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1776,6 +1831,7 @@ dbQueryAllDatabasesCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 optionTheme="$1" optionThemeCallback "${options_parse_arg}" "${optionTheme}" updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" @@ -1783,6 +1839,7 @@ dbQueryAllDatabasesCommand() { # Option 13/18 # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' --help | -h) + # shellcheck disable=SC2034 optionHelp="1" if ((options_parse_optionParsedCountOptionHelp >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1794,6 +1851,7 @@ dbQueryAllDatabasesCommand() { # Option 14/18 # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' --version) + # shellcheck disable=SC2034 optionVersion="1" if ((options_parse_optionParsedCountOptionVersion >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1805,6 +1863,7 @@ dbQueryAllDatabasesCommand() { # Option 15/18 # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' --quiet | -q) + # shellcheck disable=SC2034 optionQuiet="1" if ((options_parse_optionParsedCountOptionQuiet >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1831,6 +1890,7 @@ dbQueryAllDatabasesCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 optionLogLevel="$1" optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" @@ -1848,6 +1908,7 @@ dbQueryAllDatabasesCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 optionLogFile="$1" optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" @@ -1869,6 +1930,7 @@ dbQueryAllDatabasesCommand() { return 1 fi ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 optionDisplayLevel="$1" optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" @@ -1891,6 +1953,7 @@ dbQueryAllDatabasesCommand() { return 1 fi ((++options_parse_argParsedCountArgQuery)) + # shellcheck disable=SC2034 argQuery="${options_parse_arg}" argQueryCallback "${argQuery}" -- "${@:2}" else @@ -1924,86 +1987,105 @@ dbQueryAllDatabasesCommand() { echo -e "${__HELP_TITLE_COLOR}ARGUMENTS:${__RESET_COLOR}" echo -e " ${__HELP_OPTION_COLOR}argQuery${__HELP_NORMAL} {single} (mandatory)" local -a helpArray + # shellcheck disable=SC2054 helpArray=($'Query to execute \n - , try to execute the mysql query provided by the file \n - , search for query file in queries directory (see below) \n - else the argument is interpreted as query string') echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--jobs${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-j ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--jobs${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-j ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(specify\ the\ number\ of\ db\ to\ query\ in\ parallel) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo ' Default value: 1' - printf " %b\n" "${__HELP_OPTION_COLOR}--bar${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-b${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--bar${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-b${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Show\ progress\ as\ a\ progress\ bar.\ In\ the\ bar\ is\ shown:\ %\ of\ jobs\ completed\,\ estimated\ seconds\ left\,\ and\ number\ of\ jobs\ started.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--separator${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-s ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--separator${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-s ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(character\ to\ use\ to\ separate\ mysql\ column) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo ' Default value: |' echo echo -e "${__HELP_TITLE_COLOR}QUERY OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--from-dsn${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-f ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--from-dsn${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-f ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(target\ mysql\ server) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(use\ alternate\ bash\ framework\ configuration.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ configuration) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} (optional)" + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Load\ the\ specified\ env\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(choose\ color\ theme\ \(default\,\ default-force\ or\ noColor\)\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ this\ command\ help) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Print\ version\ information\ and\ quit) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(set\ display\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e """ @@ -2042,16 +2124,6 @@ ${__HELP_EXAMPLE}${example1}${__HELP_NORMAL}""" fi } -#default values -# default value for FROM_DSN if from-aws not set -declare queryIsFile="0" -declare optionSeparator="|" - -# other configuration -declare copyrightBeginYear="2020" -declare QUERIES_DIR="$(cd "${CURRENT_DIR}/.." && pwd -P)/conf/dbQueries" -declare HOME_QUERIES_DIR="${HOME}/.bash-tools/dbQueries" - optionHelpCallback() { local dsnList queriesList dsnList="$(Conf::getMergedList "dsn" "env")" @@ -2147,9 +2219,10 @@ run() { export query export optionSeparator export optionFromDsn + # shellcheck disable=SC2154 echo "${allDbs}" | SHELL=$(type -p bash) parallel --eta --progress "${PARALLEL_OPTIONS[@]}" \ - "${embed_function_DbQueryOneDatabase}" | + "${embed_function_DbQueryOneDatabase}" "${optionFromDsn}" | awk --source "${awkScript}" - } diff --git a/bin/dbScriptAllDatabases b/bin/dbScriptAllDatabases index 946cc038..d7a9f782 100755 --- a/bin/dbScriptAllDatabases +++ b/bin/dbScriptAllDatabases @@ -1,58 +1,65 @@ #!/usr/bin/env bash - -##################################### -# GENERATED FILE FROM https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/DbScriptAllDatabases/dbScriptAllDatabases.sh +############################################################################### +# GENERATED FACADE FROM https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/DbScriptAllDatabases/dbScriptAllDatabases.sh # DO NOT EDIT IT -##################################### +# @generated +############################################################################### +# shellcheck disable=SC2288,SC2034 +# BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/dbScriptAllDatabases +# VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. +# FACADE +# EMBED Db::queryOneDatabase as dbQueryOneDatabase # ensure that no user aliases could interfere with # commands used in this script unalias -a || true +shopt -u expand_aliases # shellcheck disable=SC2034 +((failures = 0)) || true + +# Bash will remember & return the highest exit code in a chain of pipes. +# This way you can catch the error inside pipes, e.g. mysqldump | gzip +set -o pipefail +set -o errexit + +# Command Substitution can inherit errexit option since bash v4.4 +shopt -s inherit_errexit || true + +# a log is generated when a command fails +set -o errtrace + +# use nullglob so that (file*.php) will return an empty array if no file matches the wildcard +shopt -s nullglob + +# ensure regexp are interpreted without accentuated characters +export LC_ALL=POSIX + +export TERM=xterm-256color + +# avoid interactive install +export DEBIAN_FRONTEND=noninteractive +export DEBCONF_NONINTERACTIVE_SEEN=true + +# store command arguments for later usage +# shellcheck disable=SC2034 +declare -a BASH_FRAMEWORK_ARGV=("$@") +# shellcheck disable=SC2034 +declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") + +# @see https://unix.stackexchange.com/a/386856 +# shellcheck disable=SC2317 +interruptManagement() { + # restore SIGINT handler + trap - INT + # ensure that Ctrl-C is trapped by this script and not by sub process + # report to the parent that we have indeed been interrupted + kill -s INT "$$" +} +trap interruptManagement INT SCRIPT_NAME=${0##*/} REAL_SCRIPT_FILE="$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")" -# shellcheck disable=SC2034 CURRENT_DIR="$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")" && pwd -P)" -# shellcheck disable=SC2034 -COMMAND_BIN_DIR="${CURRENT_DIR}" - -if [[ -t 1 || -t 2 ]]; then - # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __SKIPPED_COLOR='\e[33m' # Yellow - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __DEBUG_COLOR='\e[37m' # Grey - # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color - # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[1;30m")" - # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" - # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" -else - # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __DEBUG_COLOR='' - # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' -fi ################################################ # Temp dir management @@ -71,6 +78,7 @@ TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX) export TMPDIR # temp dir cleaning +# shellcheck disable=SC2317 cleanOnExit() { if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" @@ -81,144 +89,133 @@ cleanOnExit() { } trap cleanOnExit EXIT HUP QUIT ABRT TERM -# @see https://unix.stackexchange.com/a/386856 -interruptManagement() { - # restore SIGINT handler - trap - INT - # ensure that Ctrl-C is trapped by this script and not by sub process - # report to the parent that we have indeed been interrupted - kill -s INT "$$" -} -trap interruptManagement INT - -# shellcheck disable=SC2034 -((failures = 0)) || true - -shopt -s expand_aliases - -# Bash will remember & return the highest exit code in a chain of pipes. -# This way you can catch the error inside pipes, e.g. mysqldump | gzip -set -o pipefail -set -o errexit - -# Command Substitution can inherit errexit option since bash v4.4 -(shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit - -# a log is generated when a command fails -set -o errtrace - -# use nullglob so that (file*.php) will return an empty array if no file matches the wildcard -shopt -s nullglob - -# ensure regexp are interpreted without accentuated characters -export LC_ALL=POSIX - -export TERM=xterm-256color - -#avoid interactive install -export DEBIAN_FRONTEND=noninteractive -export DEBCONF_NONINTERACTIVE_SEEN=true - -BASH_TOOLS_ROOT_DIR="$(cd "${CURRENT_DIR}/.." && pwd -P)" -if [[ -d "${BASH_TOOLS_ROOT_DIR}/vendor/bash-tools-framework/" ]]; then - FRAMEWORK_ROOT_DIR="$(cd "${BASH_TOOLS_ROOT_DIR}/vendor/bash-tools-framework" && pwd -P)" -else - # if the directory does not exist yet, give a value to FRAMEWORK_ROOT_DIR - FRAMEWORK_ROOT_DIR="${BASH_TOOLS_ROOT_DIR}/vendor/bash-tools-framework" -fi -export BASH_TOOLS_ROOT_DIR FRAMEWORK_ROOT_DIR - -if [[ -f "${HOME}/.bash-tools/.env" ]]; then - export BASH_FRAMEWORK_ENV_FILEPATH="${HOME}/.bash-tools/.env" -fi - -# shellcheck disable=SC2034 +# @description concat each element of an array with a separator +# but wrapping text when line length is more than provided argument +# The algorithm will try not to cut the array element if can +# +# @arg $1 glue:String +# @arg $2 maxLineLength:int +# @arg $3 indentNextLine:int +# @arg $@ array:String[] +Array::wrap() { + local glue="${1-}" + local -i glueLength=0 + shift || true + local -i maxLineLength=$1 + shift || true + local -i indentNextLine=$1 + local indentStr="" + if ((indentNextLine > 0)); then + indentStr="$(head -c "${indentNextLine}" 0)); do + argNoAnsi="$(echo "${arg%%*( )}" | Filters::removeAnsiCodes)" + ((argNoAnsiLength = ${#argNoAnsi})) || true + if (($# < 1 && argNoAnsiLength == 0)); then break fi - shift || break + if [[ "${arg}" = $'\n' ]]; then + if [[ "${needEcho}" = "1" ]]; then + needEcho="0" + fi + echo "" + ((currentLineLength = 0)) || true + ((glueLength = 0)) || true + shift || return 0 + arg="$1" + elif ((argNoAnsiLength < maxLineLength - currentLineLength - glueLength)); then + # arg can be stored as a whole on current line + if ((glueLength > 0)); then + echo -e -n "${glue}" + ((currentLineLength += glueLength)) + fi + if ((currentLineLength == 0 && firstLine == 0)); then + echo -n "${indentStr}" + fi + echo -e -n "${arg}" | sed 's/[\t ]*$//g' + needEcho="1" + ((currentLineLength += argNoAnsiLength)) + ((glueLength = ${#glue})) || true + shift || return 0 + arg="$1" + else + if ((argNoAnsiLength >= (maxLineLength - indentNextLine))); then + if ((currentLineLength == 0 && firstLine == 0)); then + echo -n "${indentStr}" + ((currentLineLength += indentNextLine)) + fi + # arg can be stored on a whole line + if ((glueLength > 0)); then + echo -e -n "${glue}" + ((currentLineLength += glueLength)) + fi + local -i length + ((length = maxLineLength - currentLineLength)) || true + echo -e "${arg:0:${length}}" | sed 's/[\t ]*$//g' + ((currentLineLength = 0)) || true + ((glueLength = 0)) || true + arg="${arg:${length}}" + needEcho="0" + else + # arg cannot be stored on a whole line, so we add it on next line as a whole + echo + echo -e -n "${indentStr}${arg}" | sed 's/[\t ]*$//g' + ((glueLength = ${#glue})) || true + ((currentLineLength = argNoAnsiLength)) + arg="" # allows to go to next arg + needEcho="1" + fi + if [[ -z "${arg}" ]]; then + shift || return 0 + arg="$1" + fi + fi + ((firstLine = 0)) || true done - if [[ "${status}" = "0" ]]; then - export BASH_FRAMEWORK_DISPLAY_LEVEL=${verboseDisplayLevel} + if [[ "${needEcho}" = "1" ]]; then + echo fi - return "${status}" -} - -# check if an element is contained in an array -# -# **Arguments**: -# $@ - first parameter is the needle, rest is the array -# -# **Examples**: -# -# ```shell -# Array::contains "${libPath}" "${__BASH_FRAMEWORK_IMPORTED_FILES[@]}" -# ``` -# -# Returns 0 if found, 1 otherwise -Array::contains() { - local element - for element in "${@:2}"; do - [[ "${element}" = "$1" ]] && return 0 - done - return 1 } -# remove elements from array -# Performance1 : version taken from https://stackoverflow.com/a/59030460 -# Performance2 : for multiple values to remove, prefer using Array::removeIf -Array::remove() { - local -n arrayRemoveArray=$1 - shift || true # $@ contains elements to remove - local -A valuesToRemoveKeys=() - - # Tag items to remove - local del - for del in "$@"; do valuesToRemoveKeys[${del}]=1; done - - # remove items - local k - for k in "${!arrayRemoveArray[@]}"; do - if [[ -n "${valuesToRemoveKeys[${arrayRemoveArray[k]}]+xxx}" ]]; then - unset 'arrayRemoveArray[k]' - fi - done - - # compaction (element re-indexing, because unset makes "holes" in array ) - arrayRemoveArray=("${arrayRemoveArray[@]}") -} +#set -x +#Array::wrap ":" 40 0 "Lorem ipsum dolor sit amet," "consectetur adipiscing elit." "Curabitur ac elit id massa" "condimentum finibus." -# Public: check if command specified exists or return 1 +# @description check if command specified exists or return 1 # with error and message if not # -# **Arguments**: -# * $1 commandName on which existence must be checked -# * $2 helpIfNotExists a help command to display if the command does not exist +# @arg $1 commandName:String on which existence must be checked +# @arg $2 helpIfNotExists:String a help command to display if the command does not exist # -# **Exit**: code 1 if the command specified does not exist +# @exitcode 1 if the command specified does not exist +# @stderr diagnostic information + help if second argument is provided Assert::commandExists() { local commandName="$1" local helpIfNotExists="$2" @@ -233,27 +230,44 @@ Assert::commandExists() { return 0 } -# Public: exits with message if current user is root +# @description convert base64 encoded back to target file +# if target file is executable prepend dir of target +# file to PATH to make binary available everywhere +# it is advised to include in the path of the target file +# the md5sum of the binFile # -# **Exit**: code 1 if current user is root -Assert::expectNonRootUser() { - if [[ "$(id -u)" = "0" ]]; then - Log::fatal "The script must not be run as root" +# @arg $1 targetFile:String the file to write +# @arg $2 binFileBase64:String the base64 encoded file +# @arg $3 fileMode:String the chmod to set on the file +# @set PATH String prepend target embedded file binary directory to PATH variable if binary executable +Compiler::Embed::extractFileFromBase64() { + local targetFile="$1" + local binFileBase64="$2" + local fileMode="${3:-+x}" + + if [[ ! -f "${targetFile}" ]]; then + mkdir -p "$(dirname "${targetFile}")" + base64 -d >"${targetFile}" <<<"${binFileBase64}" + chmod "${fileMode}" "${targetFile}" + fi + + if [[ -x "${targetFile}" ]]; then + Env::pathPrepend "$(dirname "${targetFile}")" fi } -# Public: get absolute conf file from specified conf folder deduced using these rules +# @description get absolute conf file from specified conf folder deduced using these rules # * from absolute file (ignores and ) # * relative to where script is executed (ignores and ) # * from home/.bash-tools/ # * from framework conf/ # -# **Arguments**: -# * $1 confFolder the directory name (not the path) to list -# * $2 conf file to use without extension -# * $3 the extension (sh by default) +# @arg $1 confFolder:String the directory name (not the path) to list +# @arg $2 conf:String file to use without extension +# @arg $3 extension:String the extension (.sh by default) # -# Returns absolute conf filename +# @stdout absolute conf filename +# @exitcode 1 if file is not found in any location Conf::getAbsoluteFile() { local confFolder="$1" local conf="$2" @@ -303,22 +317,22 @@ Conf::getAbsoluteFile() { return 1 } -# Public: list the conf files list available in bash-tools/conf/ folder +# @description list the conf files list available in bash-tools/conf/ folder # and those overridden in ${HOME}/.bash-tools/ folder -# **Arguments**: -# * $1 confFolder the directory name (not the path) to list -# * $2 the extension (sh by default) -# * $3 the indentation (' - ' by default) can be any string compatible with sed not containing any / # -# **Output**: list of files without extension/directory -# eg: +# @arg $1 confFolder:String the directory name (not the path) to list +# @arg $2 extension:String the extension (.sh by default) +# @arg $3 indentStr:String the indentation (' - ' by default) can be any string compatible with sed not containing any / +# +# @stdout list of files without extension/directory +# @example text # - default.local # - default.remote # - localhost-root Conf::getMergedList() { local confFolder="$1" - local extension="${2:-sh}" - local indentStr="${3:- - }" + local extension="${2-sh}" + local indentStr="${3- - }" local DEFAULT_CONF_DIR="${FRAMEWORK_ROOT_DIR}/conf/${confFolder}" local HOME_CONF_DIR="${HOME}/.bash-tools/${confFolder}" @@ -333,16 +347,17 @@ Conf::getMergedList() { ) | sort | uniq } -# Public: lis dbs of given mysql server -# **Output**: -# the list of db except mysql admin ones : +# @description databases's list of given mysql server +# +# @example text # - information_schema # - mysql # - performance_schema # - sys # -# **Arguments**: -# * $1 (passed by reference) database instance to use +# @arg $1 instanceUserDbList:&Map (passed by reference) database instance to use +# @stdout the list of db except mysql admin ones (see example) +# @exitcode * the query exit code Database::getUserDbList() { # shellcheck disable=SC2034 local -n instanceUserDbList=$1 @@ -351,20 +366,17 @@ Database::getUserDbList() { Database::query instanceUserDbList "${sql}" } -# Public: create a new db instance +# @description create a new db instance +# Returns immediately if the instance is already initialized # -# **Arguments**: -# * $1 - (passed by reference) database instance to create -# * $2 - dsn profile - load the dsn.env profile -# absolute file is deduced using rules defined in Conf::getAbsoluteFile +# @arg $1 instanceNewInstance:&Map (passed by reference) database instance to use +# @arg $2 dsn:String dsn profile - load the dsn.env profile deduced using rules defined in Conf::getAbsoluteFile # -# **Example:** -# ```shell -# declare -Agx dbInstance -# Database::newInstance dbInstance "default.local" -# ``` +# @example +# declare -Agx dbInstance +# Database::newInstance dbInstance "default.local" # -# Returns immediately if the instance is already initialized +# @exitcode 1 if dns file not able to loaded Database::newInstance() { local -n instanceNewInstance=$1 local dsn="$2" @@ -412,238 +424,325 @@ Database::newInstance() { instanceNewInstance['INITIALIZED']=1 } -# Public: set the general options to use on mysql command to query the database +# @description set the general options to use on mysql command to query the database # Differs than setOptions in the way that these options could change each time # -# **Arguments**: -# * $1 - (passed by reference) database instance to use -# * $2 - options list +# @arg $1 instanceSetQueryOptions:&Map (passed by reference) database instance to use +# @arg $2 optionsList:String query options list Database::setQueryOptions() { local -n instanceSetQueryOptions=$1 # shellcheck disable=SC2034 instanceSetQueryOptions['QUERY_OPTIONS']="$2" } -# lazy initialization -declare -g BASH_FRAMEWORK_CACHED_ENV_FILE -declare -g BASH_FRAMEWORK_DEFAULT_ENV_FILE - -# load variables in order(from less specific to more specific) from : -# - ${FRAMEWORK_ROOT_DIR}/src/Env/testsData/.env file -# - ${FRAMEWORK_ROOT_DIR}/conf/.env file if exists -# - ~/.env file if exists -# - ~/.bash-tools/.env file if exists -# - BASH_FRAMEWORK_ENV_FILEPATH= -Env::load() { - if [[ "${BASH_FRAMEWORK_INITIALIZED:-0}" = "1" ]]; then +# @description ensure env files are loaded +# @noargs +# @exitcode 1 if getOrderedConfFiles fails +# @exitcode 2 if one of env files fails to load +# @stderr diagnostics information is displayed +Env::requireLoad() { + local configFilesStr + configFilesStr="$(Env::getOrderedConfFiles)" || return 1 + + local -a configFiles + readarray -t configFiles <<<"${configFilesStr}" + + # if empty string, there will be one element + if ((${#configFiles[@]} == 0)) || [[ -z "${configFilesStr}" ]]; then + # should not happen, as there is always default file + Log::displaySkipped "no env file to load" return 0 fi - BASH_FRAMEWORK_CACHED_ENV_FILE="$(mktemp -p "${TMPDIR:-/tmp}" -t "env_vars.XXXXXXX")" - BASH_FRAMEWORK_DEFAULT_ENV_FILE="$(mktemp -p "${TMPDIR:-/tmp}" -t "default_env_file.XXXXXXX")" - # shellcheck source=src/Env/testsData/.env - ( - echo "BASH_FRAMEWORK_LOG_LEVEL=${BASH_FRAMEWORK_LOG_LEVEL:-0}" - echo "BASH_FRAMEWORK_DISPLAY_LEVEL=${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" - echo "BASH_FRAMEWORK_LOG_FILE=${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/${SCRIPT_NAME}.log}" - echo "BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" - ) >"${BASH_FRAMEWORK_DEFAULT_ENV_FILE}" - - ( - # reset temp file - echo >"${BASH_FRAMEWORK_CACHED_ENV_FILE}" - # list .env files that need to be loaded - local -a files=() - if [[ -f "${BASH_FRAMEWORK_DEFAULT_ENV_FILE}" ]]; then - files+=("${BASH_FRAMEWORK_DEFAULT_ENV_FILE}") - fi - if [[ -f "${FRAMEWORK_ROOT_DIR}/conf/.env" && -r "${FRAMEWORK_ROOT_DIR}/conf/.env" ]]; then - files+=("${FRAMEWORK_ROOT_DIR}/conf/.env") - fi - if [[ -f "${HOME}/.env" && -r "${HOME}/.env" ]]; then - files+=("${HOME}/.env") - fi - local file - for file in "$@"; do - if [[ -f "${file}" && -r "${file}" ]]; then - files+=("${file}") - fi - done - # import custom .env file - if [[ -n "${BASH_FRAMEWORK_ENV_FILEPATH+xxx}" ]]; then - # load BASH_FRAMEWORK_ENV_FILEPATH - if [[ -f "${BASH_FRAMEWORK_ENV_FILEPATH}" && -r "${BASH_FRAMEWORK_ENV_FILEPATH}" ]]; then - files+=("${BASH_FRAMEWORK_ENV_FILEPATH}") - else - Log::displayWarning "env file not not found - ${BASH_FRAMEWORK_ENV_FILEPATH}" - fi - fi - - # add all files added as parameters - files+=("$@") - - # source each file in order - local file - for file in "${files[@]}"; do - # shellcheck source=src/Env/testsData/.env - source "${file}" || { - Log::displayWarning "Cannot load '${file}'" - } - done - - # copy only the variables to the tmp file - local varName overrideVarName - while IFS=$'\n' read -r varName; do - overrideVarName="OVERRIDE_${varName}" - if [[ -z ${!overrideVarName+xxx} ]]; then - echo "${varName}='${!varName}'" >>"${BASH_FRAMEWORK_CACHED_ENV_FILE}" - else - # variable is overridden - echo "${varName}='${!overrideVarName}'" >>"${BASH_FRAMEWORK_CACHED_ENV_FILE}" - fi - - # using awk deduce all variables that need to be copied in tmp file - # from less specific file to the most - done < <(awk -F= '!a[$1]++' "${files[@]}" | grep -v '^$\|^\s*\#' | cut -d= -f1) - ) || exit 1 - - # ensure all sourced variables will be exported - set -o allexport - - # Finally load the temp file to make the variables available in current script - # shellcheck source=src/Env/testsData/.env - source "${BASH_FRAMEWORK_CACHED_ENV_FILE}" - - set +o allexport - BASH_FRAMEWORK_INITIALIZED=1 + Env::mergeConfFiles "${configFiles[@]}" || { + Log::displayError "while loading config files: ${configFiles[*]}" + return 2 + } } -Env::pathPrepend() { - local arg - for arg in "$@"; do - if [[ -d "${arg}" && ":${PATH}:" != *":${arg}:"* ]]; then - PATH="$(realpath "${arg}"):${PATH}" - fi - done +# @description load .framework-config +# @arg $1 loadedConfigFile:&String (passed by reference) the finally loaded configuration file path +# @arg $@ srcDirs:String[] the src directories in which .framework-config file will be searched +# @stdout the config file path loaded if any +# @exitcode 0 if .framework-config file has been found in srcDirs provided +# @exitcode 1 if .framework-config file not found +# @see Conf::loadNearestFile +Framework::loadConfig() { + # shellcheck disable=SC2034 + local -n loadConfig_loadedConfigFile=$1 + shift || true + Conf::loadNearestFile ".framework-config" loadConfig_loadedConfigFile "$@" } -# Public: log level off +# @description Log namespace provides 2 kind of functions +# - Log::display* allows to display given message with +# given display level +# - Log::log* allows to log given message with +# given log level +# Log::display* functions automatically log the message too +# @see Env::requireLoad to load the display and log level from .env file + +# @description log level off export __LEVEL_OFF=0 -# Public: log level error +# @description log level error export __LEVEL_ERROR=1 -# Public: log level warning +# @description log level warning export __LEVEL_WARNING=2 -# Public: log level info +# @description log level info export __LEVEL_INFO=3 -# Public: log level success +# @description log level success export __LEVEL_SUCCESS=3 -# Public: log level debug +# @description log level debug export __LEVEL_DEBUG=4 -export __LEVEL_OFF -export __LEVEL_ERROR -export __LEVEL_WARNING -export __LEVEL_INFO -export __LEVEL_SUCCESS -export __LEVEL_DEBUG - -# Display message using debug color (grey) -# @param {String} $1 message +# @description verbose level off +export __VERBOSE_LEVEL_OFF=0 +# @description verbose level info +export __VERBOSE_LEVEL_INFO=1 +# @description verbose level info +export __VERBOSE_LEVEL_DEBUG=2 +# @description verbose level info +export __VERBOSE_LEVEL_TRACE=3 + +# @description Display message using debug color (grey) +# @arg $1 message:String the message to display Log::displayDebug() { - echo -e "${__DEBUG_COLOR}DEBUG - ${1}${__RESET_COLOR}" >&2 + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_DEBUG)); then + echo -e "${__DEBUG_COLOR}DEBUG - ${1}${__RESET_COLOR}" >&2 + fi Log::logDebug "$1" } -# Display message using info color (bg light blue/fg white) -# @param {String} $1 message +# @description Display message using error color (red) +# @arg $1 message:String the message to display +Log::displayError() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_ERROR)); then + echo -e "${__ERROR_COLOR}ERROR - ${1}${__RESET_COLOR}" >&2 + fi + Log::logError "$1" +} + +# @description Display message using info color (bg light blue/fg white) +# @arg $1 message:String the message to display Log::displayInfo() { local type="${2:-INFO}" - echo -e "${__INFO_COLOR}${type} - ${1}${__RESET_COLOR}" >&2 + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO)); then + echo -e "${__INFO_COLOR}${type} - ${1}${__RESET_COLOR}" >&2 + fi Log::logInfo "$1" "${type}" } -# Display message using error color (red) and exit immediately with error status 1 -# @param {String} $1 message +# @description Display message using warning color (yellow) +# @arg $1 message:String the message to display +Log::displayWarning() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then + echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 + fi + Log::logWarning "$1" +} + +# @description Display message using error color (red) and exit immediately with error status 1 +# @arg $1 message:String the message to display Log::fatal() { echo -e "${__ERROR_COLOR}FATAL - ${1}${__RESET_COLOR}" >&2 Log::logFatal "$1" exit 1 } -# shellcheck disable=SC2317 - -Log::load() { - # disable display methods following display level - if ((BASH_FRAMEWORK_DISPLAY_LEVEL < __LEVEL_DEBUG)); then - Log::displayDebug() { :; } - fi - if ((BASH_FRAMEWORK_DISPLAY_LEVEL < __LEVEL_INFO)); then - Log::displayHelp() { :; } - Log::displayInfo() { :; } - Log::displaySkipped() { :; } - Log::displaySuccess() { :; } - fi - if ((BASH_FRAMEWORK_DISPLAY_LEVEL < __LEVEL_WARNING)); then - Log::displayWarning() { :; } - Log::displayStatus() { :; } - fi - if ((BASH_FRAMEWORK_DISPLAY_LEVEL < __LEVEL_ERROR)); then - Log::displayError() { :; } - fi - # disable log methods following log level - if ((BASH_FRAMEWORK_LOG_LEVEL < __LEVEL_DEBUG)); then - Log::logDebug() { :; } - fi - if ((BASH_FRAMEWORK_LOG_LEVEL < __LEVEL_INFO)); then - Log::logHelp() { :; } - Log::logInfo() { :; } - Log::logSkipped() { :; } - Log::logSuccess() { :; } - fi - if ((BASH_FRAMEWORK_LOG_LEVEL < __LEVEL_WARNING)); then - Log::logWarning() { :; } - Log::logStatus() { :; } - fi - if ((BASH_FRAMEWORK_LOG_LEVEL < __LEVEL_ERROR)); then - Log::logError() { :; } +# @description activate or not Log::display* and Log::log* functions +# based on BASH_FRAMEWORK_DISPLAY_LEVEL and BASH_FRAMEWORK_LOG_LEVEL +# environment variables loaded by Env::requireLoad +# try to create log file and rotate it if necessary +# @noargs +# @set BASH_FRAMEWORK_LOG_LEVEL int to OFF level if BASH_FRAMEWORK_LOG_FILE is empty or not writable +# @env BASH_FRAMEWORK_DISPLAY_LEVEL int +# @env BASH_FRAMEWORK_LOG_LEVEL int +# @env BASH_FRAMEWORK_LOG_FILE String +# @env BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION int do log rotation if > 0 +# @exitcode 0 always successful +# @stderr diagnostics information about log file is displayed +# @require Env::requireLoad +# @require UI::requireTheme +Log::requireLoad() { + if [[ -z "${BASH_FRAMEWORK_LOG_FILE:-}" ]]; then + BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} + export BASH_FRAMEWORK_LOG_LEVEL fi if ((BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF)); then - if [[ -z "${BASH_FRAMEWORK_LOG_FILE}" ]]; then - BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} - export BASH_FRAMEWORK_LOG_LEVEL - elif [[ ! -f "${BASH_FRAMEWORK_LOG_FILE}" ]]; then - if ! mkdir -p "$(dirname "${BASH_FRAMEWORK_LOG_FILE}")" 2>/dev/null; then - BASH_FRAMEWORK_LOG_LEVEL=__LEVEL_OFF - Log::displayWarning "Log dir cannot be created $(dirname "${BASH_FRAMEWORK_LOG_FILE}")" - fi - if ! touch --no-create "${BASH_FRAMEWORK_LOG_FILE}" 2>/dev/null; then - BASH_FRAMEWORK_LOG_LEVEL=__LEVEL_OFF - Log::displayWarning "Log file '${BASH_FRAMEWORK_LOG_FILE}' cannot be created" + if [[ ! -f "${BASH_FRAMEWORK_LOG_FILE}" ]]; then + if + ! mkdir -p "$(dirname "${BASH_FRAMEWORK_LOG_FILE}")" 2>/dev/null || + ! touch --no-create "${BASH_FRAMEWORK_LOG_FILE}" 2>/dev/null + then + BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} + echo -e "${__ERROR_COLOR}ERROR - File ${BASH_FRAMEWORK_LOG_FILE} is not writable${__RESET_COLOR}" >&2 fi + elif [[ ! -w "${BASH_FRAMEWORK_LOG_FILE}" ]]; then + BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} + echo -e "${__ERROR_COLOR}ERROR - File ${BASH_FRAMEWORK_LOG_FILE} is not writable${__RESET_COLOR}" >&2 fi - Log::displayInfo "Logging to file ${BASH_FRAMEWORK_LOG_FILE}" + + fi + + if ((BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF)); then + # will always be created even if not in info level + Log::logMessage "INFO" "Logging to file ${BASH_FRAMEWORK_LOG_FILE} - Log level ${BASH_FRAMEWORK_LOG_LEVEL}" if ((BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION > 0)); then Log::rotate "${BASH_FRAMEWORK_LOG_FILE}" "${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION}" fi fi } -# Public: list files of dir with given extension and display it as a list one by line +# @description draw a line with the character passed in parameter repeated depending on terminal width +# @arg $1 character:String character to use as separator (default value #) +UI::drawLine() { + local character="${1:-#}" + printf '%*s\n' "${COLUMNS:-$([[ -t 0 ]] && tput cols || echo)}" '' | tr ' ' "${character}" +} + +# @description load colors theme constants +# @warning if tty not opened, noColor theme will be chosen +# @arg $1 theme:String the theme to use (default, noColor) +# @arg $@ args:String[] +# @set __ERROR_COLOR String indicate error status +# @set __INFO_COLOR String indicate info status +# @set __SUCCESS_COLOR String indicate success status +# @set __WARNING_COLOR String indicate warning status +# @set __SKIPPED_COLOR String indicate skipped status +# @set __DEBUG_COLOR String indicate debug status +# @set __HELP_COLOR String indicate help status +# @set __TEST_COLOR String not used +# @set __TEST_ERROR_COLOR String not used +# @set __HELP_TITLE_COLOR String used to display help title in help strings +# @set __HELP_OPTION_COLOR String used to display highlight options in help strings # -# @param {String} dir $1 the directory to list -# @param {String} prefix $2 the profile file prefix (default: "") -# @param {String} ext $3 the extension -# @param {String} findOptions $4 find options, eg: -type d -# @paramDefault {String} findOptions $4 '-type f' -# @param {String} indentStr $5 the indentation can be any string compatible with sed not containing any / -# @paramDefault {String} indentStr $5 ' - ' -# @output list of files without extension/directory -# eg: +# @set __RESET_COLOR String reset default color +# +# @set __HELP_EXAMPLE String to remove +# @set __HELP_TITLE String to remove +# @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 +UI::theme() { + local theme="${1-default}" + if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then + theme="noColor" + fi + case "${theme}" in + default | default-force) + theme="default" + ;; + noColor) ;; + *) + Log::fatal "invalid theme provided" + ;; + esac + if [[ "${theme}" = "default" ]]; then + BASH_FRAMEWORK_THEME="default" + # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue + # Internal: reset color + __RESET_COLOR='\e[0m' # Reset Color + # shellcheck disable=SC2155,SC2034 + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + # shellcheck disable=SC2155,SC2034 + __HELP_TITLE="$(echo -e "\e[1;37m")" + # shellcheck disable=SC2155,SC2034 + __HELP_NORMAL="$(echo -e "\033[0m")" + else + BASH_FRAMEWORK_THEME="noColor" + # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' + # Internal: reset color + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' + fi +} + +# @description ensure tmpdir/bin exists and +# is added to PATH to make embed being executed automatically +# @noargs +# @exitcode 1 if cannot create tmp bin directory +# @set PATH string add tmp bin directory where to find embed binaries +# @stderr diagnostics information is displayed +Compiler::Embed::requireEmbedBinDir() { + mkdir -p "${TMPDIR:-/tmp}/bin" || { + Log::displayError "unable to create directory ${TMPDIR:-/tmp}/bin" + return 1 + } + Env::pathPrepend "${TMPDIR:-/tmp}/bin" +} + +# @description ensure COMMAND_BIN_DIR env var is set +# and PATH correctly prepared +# @noargs +# @set COMMAND_BIN_DIR string the directory where to find this command +# @set PATH string add directory where to find this command binary +Compiler::Facade::requireCommandBinDir() { + COMMAND_BIN_DIR="${CURRENT_DIR}" + Env::pathPrepend "${COMMAND_BIN_DIR}" +} + +# @description ensure running user is not root +# @exitcode 1 if current user is root +# @stderr diagnostics information is displayed +Linux::requireExecutedAsUser() { + if [[ "$(id -u)" = "0" ]]; then + Log::fatal "this script should be executed as normal user" + fi +} + +# @description check if tty (interactive mode) is active +# @noargs +# @exitcode 1 if tty not active +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive +# @stderr diagnostic information + help if second argument is provided +Assert::tty() { + if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then + return 1 + fi + if [[ "${INTERACTIVE:-0}" = "1" ]]; then + return 0 + fi + [[ -t 1 || -t 2 ]] +} + +# @description list files of dir with given extension and display it as a list one by line +# +# @arg $1 dir:String the directory to list +# @arg $2 prefix:String the profile file prefix (default: "") +# @arg $3 ext:String the extension +# @arg $4 findOptions:String find options, eg: -type d (Default value: '-type f') +# @arg $5 indentStr:String the indentation can be any string compatible with sed not containing any / (Default value: ' - ') +# @stdout list of files without extension/directory +# @example text # - default.local # - default.remote # - localhost-root -# @return 1 if directory does not exists +# @exitcode 1 if directory does not exists Conf::list() { local dir="$1" local prefix="${2:-}" @@ -666,65 +765,105 @@ Conf::list() { ) } -# Internal: check if dsn file has all the mandatory variables set -# Mandatory variables are: HOSTNAME, USER, PASSWORD, PORT +# @description Load the nearest config file +# in next example will search first .framework-config file in "srcDir1" +# then if not found will go in up directories until / +# then will search in "srcDir2" +# then if not found will go in up directories until / +# source the file if found +# @example +# Conf::loadNearestFile ".framework-config" "srcDir1" "srcDir2" # -# **Arguments**: -# * $1 - dsn absolute filename +# @arg $1 configFileName:String config file name to search +# @arg $2 loadedFile:String (passed by reference) will return the loaded config file name +# @arg $@ srcDirs:String[] source directories in which the config file will be searched +# @exitcode 0 if file found +# @exitcode 1 if file not found +Conf::loadNearestFile() { + local configFileName="$1" + local -n loadedFile="$2" + shift 2 || true + local -a srcDirs=("$@") + for srcDir in "${srcDirs[@]}"; do + configFile="$(File::upFind "${srcDir}" "${configFileName}" || true)" + if [[ -n "${configFile}" ]]; then + # shellcheck source=/.framework-config + source "${configFile}" || Log::fatal "error while loading config file '${configFile}'" + Log::displayDebug "Config file ${configFile} is loaded" + # shellcheck disable=SC2034 + loadedFile="${configFile}" + return 0 + fi + done + + Log::displayWarning "Config file '${configFileName}' not found in any source directories provided" + return 1 +} + +# @description check if dsn file has all the mandatory variables set +# Mandatory variables are: HOSTNAME, USER, PASSWORD, PORT # -# Returns 0 on valid file, 1 otherwise with log output +# @arg $1 dsnFileName:String dsn absolute filename +# @set HOSTNAME loaded from dsn file +# @set PORT loaded from dsn file +# @set USER loaded from dsn file +# @set PASSWORD loaded from dsn file +# @exitcode 0 on valid file +# @exitcode 1 if one of the properties of the conf file is invalid or if file not found +# @stderr log output if error found in conf file Database::checkDsnFile() { - local DSN_FILENAME="$1" - if [[ ! -f "${DSN_FILENAME}" ]]; then - Log::displayError "dsn file ${DSN_FILENAME} not found" + local dsnFileName="$1" + if [[ ! -f "${dsnFileName}" ]]; then + Log::displayError "dsn file ${dsnFileName} not found" return 1 fi ( unset HOSTNAME PORT PASSWORD USER # shellcheck source=/src/Database/testsData/dsn_valid.env - source "${DSN_FILENAME}" + source "${dsnFileName}" if [[ -z ${HOSTNAME+x} ]]; then - Log::displayError "dsn file ${DSN_FILENAME} : HOSTNAME not provided" + Log::displayError "dsn file ${dsnFileName} : HOSTNAME not provided" return 1 fi if [[ -z "${HOSTNAME}" ]]; then - Log::displayWarning "dsn file ${DSN_FILENAME} : HOSTNAME value not provided" + Log::displayWarning "dsn file ${dsnFileName} : HOSTNAME value not provided" fi if [[ "${HOSTNAME}" = "localhost" ]]; then - Log::displayWarning "dsn file ${DSN_FILENAME} : check that HOSTNAME should not be 127.0.0.1 instead of localhost" + Log::displayWarning "dsn file ${dsnFileName} : check that HOSTNAME should not be 127.0.0.1 instead of localhost" fi if [[ -z "${PORT+x}" ]]; then - Log::displayError "dsn file ${DSN_FILENAME} : PORT not provided" + Log::displayError "dsn file ${dsnFileName} : PORT not provided" return 1 fi if ! [[ ${PORT} =~ ^[0-9]+$ ]]; then - Log::displayError "dsn file ${DSN_FILENAME} : PORT invalid" + Log::displayError "dsn file ${dsnFileName} : PORT invalid" return 1 fi if [[ -z "${USER+x}" ]]; then - Log::displayError "dsn file ${DSN_FILENAME} : USER not provided" + Log::displayError "dsn file ${dsnFileName} : USER not provided" return 1 fi if [[ -z "${PASSWORD+x}" ]]; then - Log::displayError "dsn file ${DSN_FILENAME} : PASSWORD not provided" + Log::displayError "dsn file ${dsnFileName} : PASSWORD not provided" return 1 fi ) } -# Public: mysql query on a given db +# @description mysql query on a given db +# @warning could use QUERY_OPTIONS variable from dsn if defined +# @example +# cat file.sql | Database::query ... +# @arg $1 instanceQuery:&Map (passed by reference) database instance to use +# @arg $2 sqlQuery:String (optional) sql query or sql file to execute. if not provided or empty, the command can be piped +# @arg $3 dbName:String (optional) the db name # -# **Arguments**: -# * $1 (passed by reference) database instance to use -# * $2 sql query to execute. -# if not provided or empty, the command can be piped (eg: cat file.sql | Database::query ...) -# * _$3 (optional)_ the db name -# -# **Returns**: mysql command status code +# @exitcode mysql command status code Database::query() { local -n instanceQuery=$1 local -a mysqlCommand=() + local -a queryOptions mysqlCommand+=(mysql) mysqlCommand+=("--defaults-extra-file=${instanceQuery['AUTH_FILE']}") @@ -738,11 +877,9 @@ Database::query() { mysqlCommand+=("$3") fi # add optional sql query - if [[ -n "${2+x}" && -n "$2" ]]; then - if [[ ! -f "$2" ]]; then - mysqlCommand+=("-e") - mysqlCommand+=("$2") - fi + if [[ -n "${2+x}" && -n "$2" && ! -f "$2" ]]; then + mysqlCommand+=("-e") + mysqlCommand+=("$2") fi Log::displayDebug "$(printf "execute command: '%s'" "${mysqlCommand[*]}")" @@ -753,115 +890,201 @@ Database::query() { fi } -File::concatenatePath() { - local basePath="${1}" - local subPath=${2} - local fullPath="${basePath:+${basePath}/}${subPath}" +# @description get list of env files to load +# in order to make them available for Env::requireLoad +# @env BASH_FRAMEWORK_ENV_FILES String[] list of env files that should be loaded +# @exitcode 1 if one of the env file cannot be generated +# @exitcode 2 if one of the env file is not a file or readable +# @stdout the env files asked to be loaded +# @stderr diagnostic information on failure +# @see https://github.com/fchastanet/bash-tools-framework/blob/master/FrameworkDoc.md#config_file_order +Env::getOrderedConfFiles() { + local -a configFiles=() + + if [[ -n "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then + # BASH_FRAMEWORK_ENV_FILES is an array + configFiles+=("${BASH_FRAMEWORK_ENV_FILES[@]}") + fi - realpath -m "${fullPath}" 2>/dev/null -} + local defaultEnvFile + defaultEnvFile="$(Env::createDefaultEnvFile)" || return 1 + configFiles+=("${defaultEnvFile}") -# Display message using error color (red) -# @param {String} $1 message -Log::displayError() { - echo -e "${__ERROR_COLOR}ERROR - ${1}${__RESET_COLOR}" >&2 - Log::logError "$1" + local file + for file in "${configFiles[@]}"; do + if [[ ! -f "${file}" || ! -r "${file}" ]]; then + Log::displayError "One of the config file is not available '${file}'" + return 2 + fi + echo "${file}" + done } -# Display message using info color (bg light blue/fg white) -# @param {String} $1 message -Log::displayHelp() { - local type="${2:-HELP}" - echo -e "${__HELP_COLOR}${type} - ${1}${__RESET_COLOR}" >&2 - Log::logHelp "$1" "${type}" +# @description merge and load conf files specified as argument +# - files are cleaned from ay comment +# - missing quotes after property = sign are added automatically +# - automatic remove of all whitespace before and after declarations +# - bash arrays are not supported +# - if a variable is declared in first file and overridden later on +# in the same file or in subsequent files, those overloads will be +# ignored +# @warning if an error occurs while loading one of the config file, exit code 3 but environment could be partially loaded +# @arg $@ args:String[] list of configuration files to load in order +# @set envVars String will set in environment all the variables that have been declared in the config files +# @env envVars String the env variables of the current script could be used to interpret variables during config files parsing +# @exitcode 0 if no config files provided or load completed successfully +# @exitcode 1 if error occurred during parsing the config files (file not found, grep, awk or sed error) +# @exitcode 2 if temporary file cannot be created +# @exitcode 3 if an error occurred during config file sourcing +# @stderr diagnostics information is displayed +# @see largely inspired but modified from https://opensource.com/article/21/5/processing-configuration-files-shell +Env::mergeConfFiles() { + local -a configFileList=("$@") + + if ((${#configFileList[@]} == 0)); then + return 0 + fi + + local combinedConfigFile + combinedConfigFile="$(Framework::createTempFile "mergeConfFiles")" || return 2 + + ( + # removes any trailing whitespace from each file, if any + # this is absolutely required when importing into ConfigMaps + # put quotes around values + sed -E -e $'s/\s*$// ; /^$/d ; /^#.*$/d ; s/=([^"\'].*)$/="\\1"/' "${configFileList[@]}" | + # remove all comment lines + Filters::commentLines | + # iterates over each file and prints (default awk behavior) + # each unique line; only takes first value and ignores duplicates + awk -F= '!line[$1]++' + ) >"${combinedConfigFile}" || return 1 + + # have to export everything, and source it twice: + # 1) first source is to realize variables + # 2) second time is to realize references + set -o allexport + # shellcheck source=.framework-config + source "${combinedConfigFile}" || return 3 + # shellcheck source=.framework-config + source "${combinedConfigFile}" || return 3 + set +o allexport } -# Display message using skip color (yellow) -# @param {String} $1 message -Log::displaySkipped() { - echo -e "${__SKIPPED_COLOR}SKIPPED - ${1}${__RESET_COLOR}" >&2 - Log::logSkipped "$1" +# @description prepend directories to the PATH environment variable +# @arg $@ args:String[] list of directories to prepend +# @set PATH update PATH with the directories prepended +Env::pathPrepend() { + local arg + for arg in "$@"; do + if [[ -d "${arg}" && ":${PATH}:" != *":${arg}:"* ]]; then + PATH="$(realpath "${arg}"):${PATH}" + fi + done } -# Display message using info color (blue) but warning level -# @param {String} $1 message -Log::displayStatus() { - local type="${2:-STATUS}" - echo -e "${__INFO_COLOR}${type} - ${1}${__RESET_COLOR}" >&2 - Log::logStatus "$1" "${type}" +# @description concatenate 2 paths and ensure the path is correct using realpath -m +# @arg $1 basePath:String +# @arg $2 subPath:String +# @require Linux::requireRealpathCommand +File::concatenatePath() { + local basePath="$1" + local subPath="$2" + local fullPath="${basePath:+${basePath}/}${subPath}" + + realpath -m "${fullPath}" 2>/dev/null } -# Display message using success color (bg green/fg white) -# @param {String} $1 message -Log::displaySuccess() { - echo -e "${__SUCCESS_COLOR}SUCCESS - ${1}${__RESET_COLOR}" >&2 - Log::logSuccess "$1" +# @description remove ansi codes from input or files given as argument +# @arg $@ files:String[] the files to filter +# @exitcode * if one of the filter command fails +# @stdin you can use stdin as alternative to files argument +# @stdout the filtered content +# @see https://en.wikipedia.org/wiki/ANSI_escape_code +# shellcheck disable=SC2120 +Filters::removeAnsiCodes() { + # cspell:disable + sed -E 's/\x1b\[[0-9;]*[mGKHF]//g' "$@" + # cspell:enable } -# Display message using warning color (yellow) -# @param {String} $1 message -Log::displayWarning() { - echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 - Log::logWarning "$1" +# @description Display message using skip color (yellow) +# @arg $1 message:String the message to display +Log::displaySkipped() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO)); then + echo -e "${__SKIPPED_COLOR}SKIPPED - ${1}${__RESET_COLOR}" >&2 + fi + Log::logSkipped "$1" } -# log message to file -# @param {String} $1 message +# @description log message to file +# @arg $1 message:String the message to display Log::logDebug() { - Log::logMessage "${2:-DEBUG}" "$1" + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_DEBUG)); then + Log::logMessage "${2:-DEBUG}" "$1" + fi } -# log message to file -# @param {String} $1 message +# @description log message to file +# @arg $1 message:String the message to display Log::logError() { - Log::logMessage "${2:-ERROR}" "$1" + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_ERROR)); then + Log::logMessage "${2:-ERROR}" "$1" + fi } -# log message to file -# @param {String} $1 message +# @description log message to file +# @arg $1 message:String the message to display Log::logFatal() { Log::logMessage "${2:-FATAL}" "$1" } -# log message to file -# @param {String} $1 message -Log::logHelp() { - Log::logMessage "${2:-HELP}" "$1" -} - -# log message to file -# @param {String} $1 message +# @description log message to file +# @arg $1 message:String the message to display Log::logInfo() { - Log::logMessage "${2:-INFO}" "$1" -} - -# log message to file -# @param {String} $1 message -Log::logSkipped() { - Log::logMessage "${2:-SKIPPED}" "$1" + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO)); then + Log::logMessage "${2:-INFO}" "$1" + fi } -# log message to file -# @param {String} $1 message -Log::logStatus() { - Log::logMessage "${2:-STATUS}" "$1" -} +# @description Internal: common log message +# @example text +# [date]|[levelMsg]|message +# +# @example text +# 2020-01-19 19:20:21|ERROR |log error +# 2020-01-19 19:20:21|SKIPPED|log skipped +# +# @arg $1 levelMsg:String message's level description (eg: STATUS, ERROR, ...) +# @arg $2 msg:String the message to display +# @env BASH_FRAMEWORK_LOG_FILE String log file to use, do nothing if empty +# @env BASH_FRAMEWORK_LOG_LEVEL int log level log only if > OFF or fatal messages +# @stderr diagnostics information is displayed +# @require Env::requireLoad +# @require Log::requireLoad +Log::logMessage() { + local levelMsg="$1" + local msg="$2" + local date -# log message to file -# @param {String} $1 message -Log::logSuccess() { - Log::logMessage "${2:-SUCCESS}" "$1" + if [[ -n "${BASH_FRAMEWORK_LOG_FILE}" ]] && ((BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF)); then + date="$(date '+%Y-%m-%d %H:%M:%S')" + touch "${BASH_FRAMEWORK_LOG_FILE}" + printf "%s|%7s|%s\n" "${date}" "${levelMsg}" "${msg}" >>"${BASH_FRAMEWORK_LOG_FILE}" + fi } -# log message to file -# @param {String} $1 message +# @description log message to file +# @arg $1 message:String the message to display Log::logWarning() { - Log::logMessage "${2:-WARNING}" "$1" + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then + Log::logMessage "${2:-WARNING}" "$1" + fi } -# To be called before logging in the log file -# @param {string} file $1 log file name -# @param {int} maxLogFilesCount $2 maximum number of log files +# @description To be called before logging in the log file +# @arg $1 file:string log file name +# @arg $2 maxLogFilesCount:int maximum number of log files Log::rotate() { local file="$1" local maxLogFilesCount="${2:-5}" @@ -880,253 +1103,1105 @@ Log::rotate() { fi } -# Internal: common log message -# -# **Arguments**: -# * $1 - message's level description -# * $2 - message -# **Output**: -# [date]|[levelMsg]|message +# @description load color theme +# @noargs +# @env BASH_FRAMEWORK_THEME String theme to use +# @exitcode 0 always successful +UI::requireTheme() { + UI::theme "${BASH_FRAMEWORK_THEME-default}" +} + +# @description default env file with all default values +# @stdout the default env filepath +Env::createDefaultEnvFile() { + local envFile + envFile="$(Framework::createTempFile "createDefaultEnvFileEnvFile")" || return 2 + + ( + echo "BASH_FRAMEWORK_THEME=${BASH_FRAMEWORK_THEME:-default}" + echo "BASH_FRAMEWORK_LOG_LEVEL=${BASH_FRAMEWORK_LOG_LEVEL:-0}" + echo "BASH_FRAMEWORK_DISPLAY_LEVEL=${BASH_FRAMEWORK_DISPLAY_LEVEL:-${__LEVEL_WARNING}}" + # shellcheck disable=SC2016 + echo 'BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-"${FRAMEWORK_ROOT_DIR}/logs/${SCRIPT_NAME}.log"}"' + echo "BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" + ) >"${envFile}" + echo "${envFile}" +} + +# @description search a file in parent directories # -# **Examples**: -#
-# 2020-01-19 19:20:21|ERROR  |log error
-# 2020-01-19 19:20:21|SKIPPED|log skipped
-# 
-Log::logMessage() { - local levelMsg="$1" - local msg="$2" - local date +# @arg $1 fromPath:String path +# @arg $2 fileName:String +# @arg $3 untilInclusivePath:String (optional) find for given file until reaching this folder (default value: /) +# @arg $@ untilInclusivePaths:String[] list of untilInclusivePath +# @stdout The filename if found +# @exitcode 1 if the command failed or file not found +File::upFind() { + local fromPath="$1" + shift || true + local fileName="$1" + shift || true + local untilInclusivePath="${1:-/}" + shift || true - if [[ -z "${BASH_FRAMEWORK_LOG_FILE}" ]]; then - return 0 + if [[ -f "${fromPath}" ]]; then + fromPath="$(dirname "${fromPath}")" fi - if ((BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF)) || [[ "${levelMsg}" = "FATAL" ]]; then - mkdir -p "$(dirname "${BASH_FRAMEWORK_LOG_FILE}")" || true - if Assert::fileWritable "${BASH_FRAMEWORK_LOG_FILE}"; then - date="$(date '+%Y-%m-%d %H:%M:%S')" - touch "${BASH_FRAMEWORK_LOG_FILE}" - printf "%s|%7s|%s\n" "${date}" "${levelMsg}" "${msg}" >>"${BASH_FRAMEWORK_LOG_FILE}" - else - echo -e "${__ERROR_COLOR}ERROR - File ${BASH_FRAMEWORK_LOG_FILE} is not writable${__RESET_COLOR}" >&2 + while true; do + if [[ -f "${fromPath}/${fileName}" ]]; then + echo "${fromPath}/${fileName}" + return 0 fi - fi + if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then + return 1 + fi + fromPath="$(readlink -f "${fromPath}"/..)" + done + return 1 } -# Checks if file can be created in folder -# The file does not need to exist -Assert::fileWritable() { - local file="$1" - local dir +# @description remove comment lines from input or files provided as arguments +# @arg $@ files:String[] (optional) the files to filter +# @env commentLinePrefix String the comment line prefix (default value: #) +# @exitcode 0 if lines filtered or not +# @exitcode 2 if grep fails for any other reasons than not found +# @stdin the file as stdin to filter (alternative to files argument) +# @stdout the filtered lines +# shellcheck disable=SC2120 +Filters::commentLines() { + grep -vxE "[[:blank:]]*(${commentLinePrefix:-#}.*)?" "$@" || test $? = 1 +} - dir="$(dirname "${file}")" +# @description create a temp file using default TMPDIR variable +# initialized in _includes/_commonHeader.sh +# @env TMPDIR String (default value /tmp) +# @arg $1 templateName:String template name to use(optional) +Framework::createTempFile() { + mktemp -p "${TMPDIR:-/tmp}" -t "${1:-}.XXXXXXXXXXXX" +} - Assert::validPath "${file}" && [[ -w "${dir}" ]] +# @description log message to file +# @arg $1 message:String the message to display +Log::logSkipped() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO)); then + Log::logMessage "${2:-SKIPPED}" "$1" + fi } -# Public: check if argument is a valid linux path -# -# @param {string} path $1 path that needs to be checked -# @return 1 if path is invalid -# invalid path are those with: -# - invalid characters -# - component beginning by a - (because option) -# - not beginning with a slash -# - relative -Assert::validPath() { - local path="$1" +# @description ensure command realpath is available +# @exitcode 1 if realpath command not available +# @stderr diagnostics information is displayed +Linux::requireRealpathCommand() { + Assert::commandExists realpath +} - # https://regex101.com/r/afLrmM/2 - [[ "${path}" =~ ^\/$|^(\/[.a-zA-Z_0-9][.a-zA-Z_0-9-]*)+$ ]] && - [[ ! "${path}" =~ (\/\.\.)|(\.\.\/)|^\.$|^\.\.$ ]] # avoid relative +# @description check if an element is contained in an array +# +# @arg $1 needle:String +# @arg $@ array:String[] +# @exitcode 0 if found +# @exitcode 1 otherwise +# @example +# Array::contains "${libPath}" "${__BASH_FRAMEWORK_IMPORTED_FILES[@]}" +Array::contains() { + local element + for element in "${@:2}"; do + [[ "${element}" = "$1" ]] && return 0 + done + return 1 } # FUNCTIONS -Env::load -export BASH_FRAMEWORK_DISPLAY_LEVEL="${__LEVEL_WARNING}" -Args::parseVerbose "${__LEVEL_INFO}" "$@" || true -declare -a args=("$@") -Array::remove args -v --verbose -set -- "${args[@]}" +# @require Compiler::Embed::requireEmbedBinDir -Log::load +declare -gx embed_function_DbQueryOneDatabase="${PERSISTENT_TMPDIR:-/tmp}/bin/b9b7aa9e913cf5a0f1fa26f2830396d7/dbQueryOneDatabase" +declare -gx encoded_binary_file_DbQueryOneDatabase="IyEvdXNyL2Jpbi9lbnYgYmFzaAojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgR0VORVJBVEVEIEZBQ0FERSBGUk9NIGh0dHBzOi8vZ2l0aHViLmNvbS9mY2hhc3RhbmV0L2Jhc2gtdG9vbHMvdHJlZS9tYXN0ZXIvLi4vYmFzaC1kZXYtZW52L3ZlbmRvci9iYXNoLXRvb2xzLWZyYW1ld29yay9zcmMvQ29tcGlsZXIvRW1iZWQvZW1iZWRGcmFtZXdvcmtGdW5jdGlvbi5iaW5GaWxlLnRwbAojIERPIE5PVCBFRElUIElUCiMgQGdlbmVyYXRlZAojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjI4OCxTQzIwMzQKIyBCSU5fRklMRT0ke0JJTl9GSUxFfQojIEZBQ0FERQoKIyBlbnN1cmUgdGhhdCBubyB1c2VyIGFsaWFzZXMgY291bGQgaW50ZXJmZXJlIHdpdGgKIyBjb21tYW5kcyB1c2VkIGluIHRoaXMgc2NyaXB0CnVuYWxpYXMgLWEgfHwgdHJ1ZQpzaG9wdCAtdSBleHBhbmRfYWxpYXNlcwoKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0CigoZmFpbHVyZXMgPSAwKSkgfHwgdHJ1ZQoKIyBCYXNoIHdpbGwgcmVtZW1iZXIgJiByZXR1cm4gdGhlIGhpZ2hlc3QgZXhpdCBjb2RlIGluIGEgY2hhaW4gb2YgcGlwZXMuCiMgVGhpcyB3YXkgeW91IGNhbiBjYXRjaCB0aGUgZXJyb3IgaW5zaWRlIHBpcGVzLCBlLmcuIG15c3FsZHVtcCB8IGd6aXAKc2V0IC1vIHBpcGVmYWlsCnNldCAtbyBlcnJleGl0CgojIENvbW1hbmQgU3Vic3RpdHV0aW9uIGNhbiBpbmhlcml0IGVycmV4aXQgb3B0aW9uIHNpbmNlIGJhc2ggdjQuNApzaG9wdCAtcyBpbmhlcml0X2VycmV4aXQgfHwgdHJ1ZQoKIyBhIGxvZyBpcyBnZW5lcmF0ZWQgd2hlbiBhIGNvbW1hbmQgZmFpbHMKc2V0IC1vIGVycnRyYWNlCgojIHVzZSBudWxsZ2xvYiBzbyB0aGF0IChmaWxlKi5waHApIHdpbGwgcmV0dXJuIGFuIGVtcHR5IGFycmF5IGlmIG5vIGZpbGUgbWF0Y2hlcyB0aGUgd2lsZGNhcmQKc2hvcHQgLXMgbnVsbGdsb2IKCiMgZW5zdXJlIHJlZ2V4cCBhcmUgaW50ZXJwcmV0ZWQgd2l0aG91dCBhY2NlbnR1YXRlZCBjaGFyYWN0ZXJzCmV4cG9ydCBMQ19BTEw9UE9TSVgKCmV4cG9ydCBURVJNPXh0ZXJtLTI1NmNvbG9yCgojIGF2b2lkIGludGVyYWN0aXZlIGluc3RhbGwKZXhwb3J0IERFQklBTl9GUk9OVEVORD1ub25pbnRlcmFjdGl2ZQpleHBvcnQgREVCQ09ORl9OT05JTlRFUkFDVElWRV9TRUVOPXRydWUKCiMgc3RvcmUgY29tbWFuZCBhcmd1bWVudHMgZm9yIGxhdGVyIHVzYWdlCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNApkZWNsYXJlIC1hIEJBU0hfRlJBTUVXT1JLX0FSR1Y9KCIkQCIpCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNApkZWNsYXJlIC1hIE9SSUdJTkFMX0JBU0hfRlJBTUVXT1JLX0FSR1Y9KCIkQCIpCgojIEBzZWUgaHR0cHM6Ly91bml4LnN0YWNrZXhjaGFuZ2UuY29tL2EvMzg2ODU2CiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjMxNwppbnRlcnJ1cHRNYW5hZ2VtZW50KCkgewogICMgcmVzdG9yZSBTSUdJTlQgaGFuZGxlcgogIHRyYXAgLSBJTlQKICAjIGVuc3VyZSB0aGF0IEN0cmwtQyBpcyB0cmFwcGVkIGJ5IHRoaXMgc2NyaXB0IGFuZCBub3QgYnkgc3ViIHByb2Nlc3MKICAjIHJlcG9ydCB0byB0aGUgcGFyZW50IHRoYXQgd2UgaGF2ZSBpbmRlZWQgYmVlbiBpbnRlcnJ1cHRlZAogIGtpbGwgLXMgSU5UICIkJCIKfQp0cmFwIGludGVycnVwdE1hbmFnZW1lbnQgSU5UClNDUklQVF9OQU1FPSR7MCMjKi99ClJFQUxfU0NSSVBUX0ZJTEU9IiQocmVhZGxpbmsgLWUgIiQocmVhbHBhdGggIiR7QkFTSF9TT1VSQ0VbMF19IikiKSIKQ1VSUkVOVF9ESVI9IiQoY2QgIiQocmVhZGxpbmsgLWUgIiR7UkVBTF9TQ1JJUFRfRklMRSUvKn0iKSIgJiYgcHdkIC1QKSIKCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojIFRlbXAgZGlyIG1hbmFnZW1lbnQKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgpLRUVQX1RFTVBfRklMRVM9IiR7S0VFUF9URU1QX0ZJTEVTOi0wfSIKZXhwb3J0IEtFRVBfVEVNUF9GSUxFUwoKIyBQRVJTSVNURU5UX1RNUERJUiBpcyBub3QgZGVsZXRlZCBieSB0cmFwcwpQRVJTSVNURU5UX1RNUERJUj0iJHtUTVBESVI6LS90bXB9L2Jhc2gtZnJhbWV3b3JrIgpleHBvcnQgUEVSU0lTVEVOVF9UTVBESVIKbWtkaXIgLXAgIiR7UEVSU0lTVEVOVF9UTVBESVJ9IgoKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0ClRNUERJUj0iJChta3RlbXAgLWQgLXAgIiR7UEVSU0lTVEVOVF9UTVBESVI6LS90bXB9IiAtdCBiYXNoLWZyYW1ld29yay0kJC1YWFhYWFgpIgpleHBvcnQgVE1QRElSCgojIHRlbXAgZGlyIGNsZWFuaW5nCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjMxNwpjbGVhbk9uRXhpdCgpIHsKICBpZiBbWyAiJHtLRUVQX1RFTVBfRklMRVM6LTB9IiA9ICIxIiBdXTsgdGhlbgogICAgTG9nOjpkaXNwbGF5SW5mbyAiS0VFUF9URU1QX0ZJTEVTPTEgdGVtcCBmaWxlcyBrZXB0IGhlcmUgJyR7VE1QRElSfSciCiAgZWxpZiBbWyAtbiAiJHtUTVBESVIreHh4fSIgXV07IHRoZW4KICAgIExvZzo6ZGlzcGxheURlYnVnICJLRUVQX1RFTVBfRklMRVM9MCByZW1vdmluZyB0ZW1wIGZpbGVzICcke1RNUERJUn0nIgogICAgcm0gLVJmICIke1RNUERJUjotL3RtcC9mYWtlfSIgPi9kZXYvbnVsbCAyPiYxCiAgZmkKfQp0cmFwIGNsZWFuT25FeGl0IEVYSVQgSFVQIFFVSVQgQUJSVCBURVJNCgojIFZBUl9NQUlOX0ZVTkNUSU9OX1ZBUl9OQU1FPWRiUXVlcnlBbGxEYXRhYmFzZXNGYWNhZGUKCiMgQGRlc2NyaXB0aW9uIHVzZWQgdG8gZXhlY3V0ZSBnaXZlbiBxdWVyeSB3aGVuIHVzaW5nCiMgZGJTY3JpcHRBbGxEYXRhYmFzZXMKIyBAYXJnICQxIGRzbjpTdHJpbmcKIyBAYXJnICQyIGRiOlN0cmluZwojIEBlbnYgcXVlcnkgU3RyaW5nCiMgQGVudiBvcHRpb25TZXBhcmF0b3IgU3RyaW5nCiMgQHJlcXVpcmUgTGludXg6OnJlcXVpcmVFeGVjdXRlZEFzVXNlcgpEYjo6cXVlcnlPbmVEYXRhYmFzZSgpIHsKICAjIHF1ZXJ5IGFuZCBvcHRpb25TZXBhcmF0b3IgYXJlIHBhc3NlZCB2aWEgZXhwb3J0CiAgbG9jYWwgZHNuPSIkMSIKICBsb2NhbCBkYj0iJDIiCgogIGxvY2FsIC1BIGRiSW5zdGFuY2UKICBEYXRhYmFzZTo6bmV3SW5zdGFuY2UgZGJJbnN0YW5jZSAiJHtkc259IgogIERhdGFiYXNlOjpzZXRRdWVyeU9wdGlvbnMgZGJJbnN0YW5jZSAiJHtkYkluc3RhbmNlW1FVRVJZX09QVElPTlNdfSAtLWNvbm5lY3QtdGltZW91dD01IgoKICAjIGlkZW50aWZ5IGNvbHVtbnMgaGVhZGVyCiAgZWNobyAtbiAiQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAiCiAgRGF0YWJhc2U6OnNraXBDb2x1bW5OYW1lcyBkYkluc3RhbmNlIDAKCiAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTU0CiAgaWYgISBEYXRhYmFzZTo6cXVlcnkgZGJJbnN0YW5jZSAiJHtxdWVyeX0iICIke2RifSIgfCBzZWQgInMvXHQvJHtvcHRpb25TZXBhcmF0b3J9L2ciOyB0aGVuCiAgICBMb2c6OmZhdGFsICJkYXRhYmFzZSAke2RifSBlcnJvciIgMT4mMgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIExvZyBuYW1lc3BhY2UgcHJvdmlkZXMgMiBraW5kIG9mIGZ1bmN0aW9ucwojIC0gTG9nOjpkaXNwbGF5KiBhbGxvd3MgdG8gZGlzcGxheSBnaXZlbiBtZXNzYWdlIHdpdGgKIyAgIGdpdmVuIGRpc3BsYXkgbGV2ZWwKIyAtIExvZzo6bG9nKiBhbGxvd3MgdG8gbG9nIGdpdmVuIG1lc3NhZ2Ugd2l0aAojICAgZ2l2ZW4gbG9nIGxldmVsCiMgTG9nOjpkaXNwbGF5KiBmdW5jdGlvbnMgYXV0b21hdGljYWxseSBsb2cgdGhlIG1lc3NhZ2UgdG9vCiMgQHNlZSBFbnY6OnJlcXVpcmVMb2FkIHRvIGxvYWQgdGhlIGRpc3BsYXkgYW5kIGxvZyBsZXZlbCBmcm9tIC5lbnYgZmlsZQoKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIG9mZgpleHBvcnQgX19MRVZFTF9PRkY9MAojIEBkZXNjcmlwdGlvbiBsb2cgbGV2ZWwgZXJyb3IKZXhwb3J0IF9fTEVWRUxfRVJST1I9MQojIEBkZXNjcmlwdGlvbiBsb2cgbGV2ZWwgd2FybmluZwpleHBvcnQgX19MRVZFTF9XQVJOSU5HPTIKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIGluZm8KZXhwb3J0IF9fTEVWRUxfSU5GTz0zCiMgQGRlc2NyaXB0aW9uIGxvZyBsZXZlbCBzdWNjZXNzCmV4cG9ydCBfX0xFVkVMX1NVQ0NFU1M9MwojIEBkZXNjcmlwdGlvbiBsb2cgbGV2ZWwgZGVidWcKZXhwb3J0IF9fTEVWRUxfREVCVUc9NAoKIyBAZGVzY3JpcHRpb24gdmVyYm9zZSBsZXZlbCBvZmYKZXhwb3J0IF9fVkVSQk9TRV9MRVZFTF9PRkY9MAojIEBkZXNjcmlwdGlvbiB2ZXJib3NlIGxldmVsIGluZm8KZXhwb3J0IF9fVkVSQk9TRV9MRVZFTF9JTkZPPTEKIyBAZGVzY3JpcHRpb24gdmVyYm9zZSBsZXZlbCBpbmZvCmV4cG9ydCBfX1ZFUkJPU0VfTEVWRUxfREVCVUc9MgojIEBkZXNjcmlwdGlvbiB2ZXJib3NlIGxldmVsIGluZm8KZXhwb3J0IF9fVkVSQk9TRV9MRVZFTF9UUkFDRT0zCgojIEBkZXNjcmlwdGlvbiBEaXNwbGF5IG1lc3NhZ2UgdXNpbmcgZGVidWcgY29sb3IgKGdyZXkpCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6ZGlzcGxheURlYnVnKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCA+PSBfX0xFVkVMX0RFQlVHKSk7IHRoZW4KICAgIGVjaG8gLWUgIiR7X19ERUJVR19DT0xPUn1ERUJVRyAgIC0gJHsxfSR7X19SRVNFVF9DT0xPUn0iID4mMgogIGZpCiAgTG9nOjpsb2dEZWJ1ZyAiJDEiCn0KCiMgQGRlc2NyaXB0aW9uIERpc3BsYXkgbWVzc2FnZSB1c2luZyBpbmZvIGNvbG9yIChiZyBsaWdodCBibHVlL2ZnIHdoaXRlKQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlJbmZvKCkgewogIGxvY2FsIHR5cGU9IiR7MjotSU5GT30iCiAgaWYgKChCQVNIX0ZSQU1FV09SS19ESVNQTEFZX0xFVkVMID49IF9fTEVWRUxfSU5GTykpOyB0aGVuCiAgICBlY2hvIC1lICIke19fSU5GT19DT0xPUn0ke3R5cGV9ICAgIC0gJHsxfSR7X19SRVNFVF9DT0xPUn0iID4mMgogIGZpCiAgTG9nOjpsb2dJbmZvICIkMSIgIiR7dHlwZX0iCn0KCiMgQGRlc2NyaXB0aW9uIGVuc3VyZSBDT01NQU5EX0JJTl9ESVIgZW52IHZhciBpcyBzZXQKIyBhbmQgUEFUSCBjb3JyZWN0bHkgcHJlcGFyZWQKIyBAbm9hcmdzCiMgQHNldCBDT01NQU5EX0JJTl9ESVIgc3RyaW5nIHRoZSBkaXJlY3Rvcnkgd2hlcmUgdG8gZmluZCB0aGlzIGNvbW1hbmQKIyBAc2V0IFBBVEggc3RyaW5nIGFkZCBkaXJlY3Rvcnkgd2hlcmUgdG8gZmluZCB0aGlzIGNvbW1hbmQgYmluYXJ5CkNvbXBpbGVyOjpGYWNhZGU6OnJlcXVpcmVDb21tYW5kQmluRGlyKCkgewogIENPTU1BTkRfQklOX0RJUj0iJHtDVVJSRU5UX0RJUn0iCiAgRW52OjpwYXRoUHJlcGVuZCAiJHtDT01NQU5EX0JJTl9ESVJ9Igp9CgojIEBkZXNjcmlwdGlvbiBjcmVhdGUgYSBuZXcgZGIgaW5zdGFuY2UKIyBSZXR1cm5zIGltbWVkaWF0ZWx5IGlmIHRoZSBpbnN0YW5jZSBpcyBhbHJlYWR5IGluaXRpYWxpemVkCiMKIyBAYXJnICQxIGluc3RhbmNlTmV3SW5zdGFuY2U6Jk1hcDxTdHJpbmcsU3RyaW5nPiAocGFzc2VkIGJ5IHJlZmVyZW5jZSkgZGF0YWJhc2UgaW5zdGFuY2UgdG8gdXNlCiMgQGFyZyAkMiBkc246U3RyaW5nIGRzbiBwcm9maWxlIC0gbG9hZCB0aGUgZHNuLmVudiBwcm9maWxlIGRlZHVjZWQgdXNpbmcgcnVsZXMgZGVmaW5lZCBpbiBDb25mOjpnZXRBYnNvbHV0ZUZpbGUKIwojIEBleGFtcGxlCiMgICBkZWNsYXJlIC1BZ3ggZGJJbnN0YW5jZQojICAgRGF0YWJhc2U6Om5ld0luc3RhbmNlIGRiSW5zdGFuY2UgImRlZmF1bHQubG9jYWwiCiMKIyBAZXhpdGNvZGUgMSBpZiBkbnMgZmlsZSBub3QgYWJsZSB0byBsb2FkZWQKRGF0YWJhc2U6Om5ld0luc3RhbmNlKCkgewogIGxvY2FsIC1uIGluc3RhbmNlTmV3SW5zdGFuY2U9JDEKICBsb2NhbCBkc249IiQyIgogIGxvY2FsIERTTl9GSUxFCgogIGlmIFtbIC12IGluc3RhbmNlTmV3SW5zdGFuY2VbJ0lOSVRJQUxJWkVEJ10gJiYgIiR7aW5zdGFuY2VOZXdJbnN0YW5jZVsnSU5JVElBTElaRUQnXTotMH0iID09ICIxIiBdXTsgdGhlbgogICAgcmV0dXJuCiAgZmkKCiAgIyBmaW5hbCBhdXRoIGZpbGUgZ2VuZXJhdGVkIGZyb20gZG5zIGZpbGUKICBpbnN0YW5jZU5ld0luc3RhbmNlWydBVVRIX0ZJTEUnXT0iIgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0RTTl9GSUxFJ109IiIKCiAgIyBjaGVjayBkc24gZmlsZQogIERTTl9GSUxFPSIkKENvbmY6OmdldEFic29sdXRlRmlsZSAiZHNuIiAiJHtkc259IiAiZW52IikiIHx8IHJldHVybiAxCiAgRGF0YWJhc2U6OmNoZWNrRHNuRmlsZSAiJHtEU05fRklMRX0iIHx8IHJldHVybiAxCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnRFNOX0ZJTEUnXT0iJHtEU05fRklMRX0iCgogICMgc2hlbGxjaGVjayBzb3VyY2U9L3NyYy9EYXRhYmFzZS90ZXN0c0RhdGEvZHNuX3ZhbGlkLmVudgogIHNvdXJjZSAiJHtpbnN0YW5jZU5ld0luc3RhbmNlWydEU05fRklMRSddfSIKCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnVVNFUiddPSIke1VTRVJ9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1BBU1NXT1JEJ109IiR7UEFTU1dPUkR9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0hPU1ROQU1FJ109IiR7SE9TVE5BTUV9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1BPUlQnXT0iJHtQT1JUfSIKCiAgIyBnZW5lcmF0ZSBhdXRoRmlsZSBmb3IgZWFzeSBhdXRoZW50aWNhdGlvbgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0FVVEhfRklMRSddPSQobWt0ZW1wIC1wICIke1RNUERJUjotL3RtcH0iIC10ICJteXNxbC5YWFhYWFhYWFhYWFgiKQogICgKICAgIGVjaG8gIltjbGllbnRdIgogICAgZWNobyAidXNlciA9ICR7VVNFUn0iCiAgICBlY2hvICJwYXNzd29yZCA9ICR7UEFTU1dPUkR9IgogICAgZWNobyAiaG9zdCA9ICR7SE9TVE5BTUV9IgogICAgZWNobyAicG9ydCA9ICR7UE9SVH0iCiAgKSA+IiR7aW5zdGFuY2VOZXdJbnN0YW5jZVsnQVVUSF9GSUxFJ119IgoKICAjIHNvbWUgb2YgdGhvc2UgdmFsdWVzIGNhbiBiZSBvdmVycmlkZGVuIHVzaW5nIHRoZSBkc24gZmlsZQogICMgU0tJUF9DT0xVTU5fTkFNRVMgZW5hYmxlZCBieSBkZWZhdWx0CiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnU0tJUF9DT0xVTU5fTkFNRVMnXT0iJHtTS0lQX0NPTFVNTl9OQU1FUzotMX0iCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnU1NMX09QVElPTlMnXT0iJHtNWVNRTF9TU0xfT1BUSU9OUzotLS1zc2wtbW9kZT1ESVNBQkxFRH0iCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnUVVFUllfT1BUSU9OUyddPSIke01ZU1FMX1FVRVJZX09QVElPTlM6LS0tYmF0Y2ggLS1yYXcgLS1kZWZhdWx0LWNoYXJhY3Rlci1zZXQ9dXRmOH0iCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnRFVNUF9PUFRJT05TJ109IiR7TVlTUUxfRFVNUF9PUFRJT05TOi0tLWRlZmF1bHQtY2hhcmFjdGVyLXNldD11dGY4IC0tY29tcHJlc3MgLS1oZXgtYmxvYiAtLXJvdXRpbmVzIC0tdHJpZ2dlcnMgLS1zaW5nbGUtdHJhbnNhY3Rpb24gLS1zZXQtZ3RpZC1wdXJnZWQ9T0ZGIC0tY29sdW1uLXN0YXRpc3RpY3M9MCAke2luc3RhbmNlTmV3SW5zdGFuY2VbJ1NTTF9PUFRJT05TJ119fSIKICBpbnN0YW5jZU5ld0luc3RhbmNlWydEQl9JTVBPUlRfT1BUSU9OUyddPSIke0RCX0lNUE9SVF9PUFRJT05TOi0tLWNvbm5lY3QtdGltZW91dD01IC0tYmF0Y2ggLS1yYXcgLS1kZWZhdWx0LWNoYXJhY3Rlci1zZXQ9dXRmOH0iCgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0lOSVRJQUxJWkVEJ109MQp9CgojIEBkZXNjcmlwdGlvbiBteXNxbCBxdWVyeSBvbiBhIGdpdmVuIGRiCiMgQHdhcm5pbmcgY291bGQgdXNlIFFVRVJZX09QVElPTlMgdmFyaWFibGUgZnJvbSBkc24gaWYgZGVmaW5lZAojIEBleGFtcGxlCiMgICBjYXQgZmlsZS5zcWwgfCBEYXRhYmFzZTo6cXVlcnkgLi4uCiMgQGFyZyAkMSBpbnN0YW5jZVF1ZXJ5OiZNYXA8U3RyaW5nLFN0cmluZz4gKHBhc3NlZCBieSByZWZlcmVuY2UpIGRhdGFiYXNlIGluc3RhbmNlIHRvIHVzZQojIEBhcmcgJDIgc3FsUXVlcnk6U3RyaW5nIChvcHRpb25hbCkgc3FsIHF1ZXJ5IG9yIHNxbCBmaWxlIHRvIGV4ZWN1dGUuIGlmIG5vdCBwcm92aWRlZCBvciBlbXB0eSwgdGhlIGNvbW1hbmQgY2FuIGJlIHBpcGVkCiMgQGFyZyAkMyBkYk5hbWU6U3RyaW5nIChvcHRpb25hbCkgdGhlIGRiIG5hbWUKIwojIEBleGl0Y29kZSBteXNxbCBjb21tYW5kIHN0YXR1cyBjb2RlCkRhdGFiYXNlOjpxdWVyeSgpIHsKICBsb2NhbCAtbiBpbnN0YW5jZVF1ZXJ5PSQxCiAgbG9jYWwgLWEgbXlzcWxDb21tYW5kPSgpCiAgbG9jYWwgLWEgcXVlcnlPcHRpb25zCgogIG15c3FsQ29tbWFuZCs9KG15c3FsKQogIG15c3FsQ29tbWFuZCs9KCItLWRlZmF1bHRzLWV4dHJhLWZpbGU9JHtpbnN0YW5jZVF1ZXJ5WydBVVRIX0ZJTEUnXX0iKQogIElGUz0nICcgcmVhZCAtciAtYSBxdWVyeU9wdGlvbnMgPDw8IiR7aW5zdGFuY2VRdWVyeVsnUVVFUllfT1BUSU9OUyddfSIKICBteXNxbENvbW1hbmQrPSgiJHtxdWVyeU9wdGlvbnNbQF19IikKICBpZiBbWyAiJHtpbnN0YW5jZVF1ZXJ5WydTS0lQX0NPTFVNTl9OQU1FUyddfSIgPSAiMSIgXV07IHRoZW4KICAgIG15c3FsQ29tbWFuZCs9KCItcyIgIi0tc2tpcC1jb2x1bW4tbmFtZXMiKQogIGZpCiAgIyBhZGQgb3B0aW9uYWwgZGIgbmFtZQogIGlmIFtbIC1uICIkezMreH0iIF1dOyB0aGVuCiAgICBteXNxbENvbW1hbmQrPSgiJDMiKQogIGZpCiAgIyBhZGQgb3B0aW9uYWwgc3FsIHF1ZXJ5CiAgaWYgW1sgLW4gIiR7Mit4fSIgJiYgLW4gIiQyIiAmJiAhIC1mICIkMiIgXV07IHRoZW4KICAgIG15c3FsQ29tbWFuZCs9KCItZSIpCiAgICBteXNxbENvbW1hbmQrPSgiJDIiKQogIGZpCiAgTG9nOjpkaXNwbGF5RGVidWcgIiQocHJpbnRmICJleGVjdXRlIGNvbW1hbmQ6ICclcyciICIke215c3FsQ29tbWFuZFsqXX0iKSIKCiAgaWYgW1sgLWYgIiQyIiBdXTsgdGhlbgogICAgIiR7bXlzcWxDb21tYW5kW0BdfSIgPCIkMiIKICBlbHNlCiAgICAiJHtteXNxbENvbW1hbmRbQF19IgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIHNldCB0aGUgZ2VuZXJhbCBvcHRpb25zIHRvIHVzZSBvbiBteXNxbCBjb21tYW5kIHRvIHF1ZXJ5IHRoZSBkYXRhYmFzZQojIERpZmZlcnMgdGhhbiBzZXRPcHRpb25zIGluIHRoZSB3YXkgdGhhdCB0aGVzZSBvcHRpb25zIGNvdWxkIGNoYW5nZSBlYWNoIHRpbWUKIwojIEBhcmcgJDEgaW5zdGFuY2VTZXRRdWVyeU9wdGlvbnM6Jk1hcDxTdHJpbmcsU3RyaW5nPiAocGFzc2VkIGJ5IHJlZmVyZW5jZSkgZGF0YWJhc2UgaW5zdGFuY2UgdG8gdXNlCiMgQGFyZyAkMiBvcHRpb25zTGlzdDpTdHJpbmcgcXVlcnkgb3B0aW9ucyBsaXN0CkRhdGFiYXNlOjpzZXRRdWVyeU9wdGlvbnMoKSB7CiAgbG9jYWwgLW4gaW5zdGFuY2VTZXRRdWVyeU9wdGlvbnM9JDEKICAjIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIwMzQKICBpbnN0YW5jZVNldFF1ZXJ5T3B0aW9uc1snUVVFUllfT1BUSU9OUyddPSIkMiIKfQoKIyBAZGVzY3JpcHRpb24gYnkgZGVmYXVsdCB3ZSBza2lwIHRoZSBjb2x1bW4gbmFtZXMKIyBidXQgc29tZXRpbWVzIHdlIG5lZWQgY29sdW1uIG5hbWVzIHRvIGRpc3BsYXkgc29tZSByZXN1bHRzCiMgZGlzYWJsZSB0aGlzIG9wdGlvbiB0ZW1wb3JhcmlseSBhbmQgdGhlbiByZXN0b3JlIGl0IHRvIHRydWUKIwojIEBhcmcgJDEgaW5zdGFuY2VTZXRRdWVyeU9wdGlvbnM6Jk1hcDxTdHJpbmcsU3RyaW5nPiAocGFzc2VkIGJ5IHJlZmVyZW5jZSkgZGF0YWJhc2UgaW5zdGFuY2UgdG8gdXNlCiMgQGFyZyAkMiBza2lwQ29sdW1uTmFtZXM6Qm9vbGVhbiAwIHRvIGRpc2FibGUsIDEgdG8gZW5hYmxlIChoaWRlIGNvbHVtbiBuYW1lcykKRGF0YWJhc2U6OnNraXBDb2x1bW5OYW1lcygpIHsKICBsb2NhbCAtbiBpbnN0YW5jZVNraXBDb2x1bW5OYW1lcz0kMQogICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNAogIGluc3RhbmNlU2tpcENvbHVtbk5hbWVzWydTS0lQX0NPTFVNTl9OQU1FUyddPSIkMiIKfQoKIyBAZGVzY3JpcHRpb24gcHJlcGVuZCBkaXJlY3RvcmllcyB0byB0aGUgUEFUSCBlbnZpcm9ubWVudCB2YXJpYWJsZQojIEBhcmcgJEAgYXJnczpTdHJpbmdbXSBsaXN0IG9mIGRpcmVjdG9yaWVzIHRvIHByZXBlbmQKIyBAc2V0IFBBVEggdXBkYXRlIFBBVEggd2l0aCB0aGUgZGlyZWN0b3JpZXMgcHJlcGVuZGVkCkVudjo6cGF0aFByZXBlbmQoKSB7CiAgbG9jYWwgYXJnCiAgZm9yIGFyZyBpbiAiJEAiOyBkbwogICAgaWYgW1sgLWQgIiR7YXJnfSIgJiYgIjoke1BBVEh9OiIgIT0gKiI6JHthcmd9OiIqIF1dOyB0aGVuCiAgICAgIFBBVEg9IiQocmVhbHBhdGggIiR7YXJnfSIpOiR7UEFUSH0iCiAgICBmaQogIGRvbmUKfQoKIyBAZGVzY3JpcHRpb24gRGlzcGxheSBtZXNzYWdlIHVzaW5nIGVycm9yIGNvbG9yIChyZWQpIGFuZCBleGl0IGltbWVkaWF0ZWx5IHdpdGggZXJyb3Igc3RhdHVzIDEKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpmYXRhbCgpIHsKICBlY2hvIC1lICIke19fRVJST1JfQ09MT1J9RkFUQUwgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBMb2c6OmxvZ0ZhdGFsICIkMSIKICBleGl0IDEKfQoKIyBAZGVzY3JpcHRpb24gbG9nIG1lc3NhZ2UgdG8gZmlsZQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmxvZ0RlYnVnKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMID49IF9fTEVWRUxfREVCVUcpKTsgdGhlbgogICAgTG9nOjpsb2dNZXNzYWdlICIkezI6LURFQlVHfSIgIiQxIgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGxvZyBtZXNzYWdlIHRvIGZpbGUKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpsb2dJbmZvKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMID49IF9fTEVWRUxfSU5GTykpOyB0aGVuCiAgICBMb2c6OmxvZ01lc3NhZ2UgIiR7MjotSU5GT30iICIkMSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBlbnN1cmUgcnVubmluZyB1c2VyIGlzIG5vdCByb290CiMgQGV4aXRjb2RlIDEgaWYgY3VycmVudCB1c2VyIGlzIHJvb3QKIyBAc3RkZXJyIGRpYWdub3N0aWNzIGluZm9ybWF0aW9uIGlzIGRpc3BsYXllZApMaW51eDo6cmVxdWlyZUV4ZWN1dGVkQXNVc2VyKCkgewogIGlmIFtbICIkKGlkIC11KSIgPSAiMCIgXV07IHRoZW4KICAgIExvZzo6ZmF0YWwgInRoaXMgc2NyaXB0IHNob3VsZCBiZSBleGVjdXRlZCBhcyBub3JtYWwgdXNlciIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBnZXQgYWJzb2x1dGUgY29uZiBmaWxlIGZyb20gc3BlY2lmaWVkIGNvbmYgZm9sZGVyIGRlZHVjZWQgdXNpbmcgdGhlc2UgcnVsZXMKIyAgICogZnJvbSBhYnNvbHV0ZSBmaWxlIChpZ25vcmVzIDxjb25mRm9sZGVyPiBhbmQgPGV4dGVuc2lvbj4pCiMgICAqIHJlbGF0aXZlIHRvIHdoZXJlIHNjcmlwdCBpcyBleGVjdXRlZCAoaWdub3JlcyA8Y29uZkZvbGRlcj4gYW5kIDxleHRlbnNpb24+KQojICAgKiBmcm9tIGhvbWUvLmJhc2gtdG9vbHMvPGNvbmZGb2xkZXI+CiMgICAqIGZyb20gZnJhbWV3b3JrIGNvbmYvPGNvbmZGb2xkZXI+CiMKIyBAYXJnICQxIGNvbmZGb2xkZXI6U3RyaW5nIHRoZSBkaXJlY3RvcnkgbmFtZSAobm90IHRoZSBwYXRoKSB0byBsaXN0CiMgQGFyZyAkMiBjb25mOlN0cmluZyBmaWxlIHRvIHVzZSB3aXRob3V0IGV4dGVuc2lvbgojIEBhcmcgJDMgZXh0ZW5zaW9uOlN0cmluZyB0aGUgZXh0ZW5zaW9uICguc2ggYnkgZGVmYXVsdCkKIwojIEBzdGRvdXQgYWJzb2x1dGUgY29uZiBmaWxlbmFtZQojIEBleGl0Y29kZSAxIGlmIGZpbGUgaXMgbm90IGZvdW5kIGluIGFueSBsb2NhdGlvbgpDb25mOjpnZXRBYnNvbHV0ZUZpbGUoKSB7CiAgbG9jYWwgY29uZkZvbGRlcj0iJDEiCiAgbG9jYWwgY29uZj0iJDIiCiAgbG9jYWwgZXh0ZW5zaW9uPSIkezMtLnNofSIKICBpZiBbWyAtbiAiJHtleHRlbnNpb259IiAmJiAiJHtleHRlbnNpb246MDoxfSIgIT0gIi4iIF1dOyB0aGVuCiAgICBleHRlbnNpb249Ii4ke2V4dGVuc2lvbn0iCiAgZmkKCiAgdGVzdEFicygpIHsKICAgIGxvY2FsIHJlc3VsdAogICAgcmVzdWx0PSIkKHJlYWxwYXRoIC1lICIkMSIgMj4vZGV2L251bGwpIgogICAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTgxCiAgICBpZiBbWyAiJD8iID0gIjAiICYmIC1mICIke3Jlc3VsdH0iIF1dOyB0aGVuCiAgICAgIGVjaG8gIiR7cmVzdWx0fSIKICAgICAgcmV0dXJuIDAKICAgIGZpCiAgICByZXR1cm4gMQogIH0KCiAgIyBjb25mIGlzIGFic29sdXRlIGZpbGUgKGluY2x1ZGluZyBleHRlbnNpb24pCiAgdGVzdEFicyAiJHtjb25mRm9sZGVyfSR7ZXh0ZW5zaW9ufSIgJiYgcmV0dXJuIDAKICAjIGNvbmYgaXMgYWJzb2x1dGUgZmlsZQogIHRlc3RBYnMgIiR7Y29uZkZvbGRlcn0iICYmIHJldHVybiAwCiAgIyBjb25mIGlzIGFic29sdXRlIGZpbGUgKGluY2x1ZGluZyBleHRlbnNpb24pCiAgdGVzdEFicyAiJHtjb25mfSR7ZXh0ZW5zaW9ufSIgJiYgcmV0dXJuIDAKICAjIGNvbmYgaXMgYWJzb2x1dGUgZmlsZQogIHRlc3RBYnMgIiR7Y29uZn0iICYmIHJldHVybiAwCgogICMgcmVsYXRpdmUgdG8gd2hlcmUgc2NyaXB0IGlzIGV4ZWN1dGVkIChpbmNsdWRpbmcgZXh0ZW5zaW9uKQogIGlmIFtbIC1uICIke0NVUlJFTlRfRElSK3h4eH0iIF1dOyB0aGVuCiAgICB0ZXN0QWJzICIkKEZpbGU6OmNvbmNhdGVuYXRlUGF0aCAiJHtDVVJSRU5UX0RJUn0iICIke2NvbmZGb2xkZXJ9IikvJHtjb25mfSR7ZXh0ZW5zaW9ufSIgJiYgcmV0dXJuIDAKICBmaQogICMgZnJvbSBob21lLy5iYXNoLXRvb2xzLzxjb25mRm9sZGVyPgogIHRlc3RBYnMgIiQoRmlsZTo6Y29uY2F0ZW5hdGVQYXRoICIke0hPTUV9Ly5iYXNoLXRvb2xzIiAiJHtjb25mRm9sZGVyfSIpLyR7Y29uZn0ke2V4dGVuc2lvbn0iICYmIHJldHVybiAwCgogIGlmIFtbIC1uICIke0ZSQU1FV09SS19ST09UX0RJUit4eHh9IiBdXTsgdGhlbgogICAgIyBmcm9tIGZyYW1ld29yayBjb25mLzxjb25mRm9sZGVyPiAoaW5jbHVkaW5nIGV4dGVuc2lvbikKICAgIHRlc3RBYnMgIiQoRmlsZTo6Y29uY2F0ZW5hdGVQYXRoICIke0ZSQU1FV09SS19ST09UX0RJUn0vY29uZiIgIiR7Y29uZkZvbGRlcn0iKS8ke2NvbmZ9JHtleHRlbnNpb259IiAmJiByZXR1cm4gMAoKICAgICMgZnJvbSBmcmFtZXdvcmsgY29uZi88Y29uZkZvbGRlcj4KICAgIHRlc3RBYnMgIiQoRmlsZTo6Y29uY2F0ZW5hdGVQYXRoICIke0ZSQU1FV09SS19ST09UX0RJUn0vY29uZiIgIiR7Y29uZkZvbGRlcn0iKS8ke2NvbmZ9IiAmJiByZXR1cm4gMAogIGZpCgogICMgZmlsZSBub3QgZm91bmQKICBMb2c6OmRpc3BsYXlFcnJvciAiY29uZiBmaWxlICcke2NvbmZ9JyBub3QgZm91bmQiCgogIHJldHVybiAxCn0KCiMgQGRlc2NyaXB0aW9uIGNoZWNrIGlmIGRzbiBmaWxlIGhhcyBhbGwgdGhlIG1hbmRhdG9yeSB2YXJpYWJsZXMgc2V0CiMgTWFuZGF0b3J5IHZhcmlhYmxlcyBhcmU6IEhPU1ROQU1FLCBVU0VSLCBQQVNTV09SRCwgUE9SVAojCiMgQGFyZyAkMSBkc25GaWxlTmFtZTpTdHJpbmcgZHNuIGFic29sdXRlIGZpbGVuYW1lCiMgQHNldCBIT1NUTkFNRSBsb2FkZWQgZnJvbSBkc24gZmlsZQojIEBzZXQgUE9SVCBsb2FkZWQgZnJvbSBkc24gZmlsZQojIEBzZXQgVVNFUiBsb2FkZWQgZnJvbSBkc24gZmlsZQojIEBzZXQgUEFTU1dPUkQgbG9hZGVkIGZyb20gZHNuIGZpbGUKIyBAZXhpdGNvZGUgMCBvbiB2YWxpZCBmaWxlCiMgQGV4aXRjb2RlIDEgaWYgb25lIG9mIHRoZSBwcm9wZXJ0aWVzIG9mIHRoZSBjb25mIGZpbGUgaXMgaW52YWxpZCBvciBpZiBmaWxlIG5vdCBmb3VuZAojIEBzdGRlcnIgbG9nIG91dHB1dCBpZiBlcnJvciBmb3VuZCBpbiBjb25mIGZpbGUKRGF0YWJhc2U6OmNoZWNrRHNuRmlsZSgpIHsKICBsb2NhbCBkc25GaWxlTmFtZT0iJDEiCiAgaWYgW1sgISAtZiAiJHtkc25GaWxlTmFtZX0iIF1dOyB0aGVuCiAgICBMb2c6OmRpc3BsYXlFcnJvciAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gbm90IGZvdW5kIgogICAgcmV0dXJuIDEKICBmaQoKICAoCiAgICB1bnNldCBIT1NUTkFNRSBQT1JUIFBBU1NXT1JEIFVTRVIKICAgICMgc2hlbGxjaGVjayBzb3VyY2U9L3NyYy9EYXRhYmFzZS90ZXN0c0RhdGEvZHNuX3ZhbGlkLmVudgogICAgc291cmNlICIke2RzbkZpbGVOYW1lfSIKICAgIGlmIFtbIC16ICR7SE9TVE5BTUUreH0gXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogSE9TVE5BTUUgbm90IHByb3ZpZGVkIgogICAgICByZXR1cm4gMQogICAgZmkKICAgIGlmIFtbIC16ICIke0hPU1ROQU1FfSIgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5V2FybmluZyAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gOiBIT1NUTkFNRSB2YWx1ZSBub3QgcHJvdmlkZWQiCiAgICBmaQogICAgaWYgW1sgIiR7SE9TVE5BTUV9IiA9ICJsb2NhbGhvc3QiIF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheVdhcm5pbmcgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogY2hlY2sgdGhhdCBIT1NUTkFNRSBzaG91bGQgbm90IGJlIDEyNy4wLjAuMSBpbnN0ZWFkIG9mIGxvY2FsaG9zdCIKICAgIGZpCiAgICBpZiBbWyAteiAiJHtQT1JUK3h9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlFcnJvciAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gOiBQT1JUIG5vdCBwcm92aWRlZCIKICAgICAgcmV0dXJuIDEKICAgIGZpCiAgICBpZiAhIFtbICR7UE9SVH0gPX4gXlswLTldKyQgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogUE9SVCBpbnZhbGlkIgogICAgICByZXR1cm4gMQogICAgZmkKICAgIGlmIFtbIC16ICIke1VTRVIreH0iIF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheUVycm9yICJkc24gZmlsZSAke2RzbkZpbGVOYW1lfSA6IFVTRVIgbm90IHByb3ZpZGVkIgogICAgICByZXR1cm4gMQogICAgZmkKICAgIGlmIFtbIC16ICIke1BBU1NXT1JEK3h9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlFcnJvciAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gOiBQQVNTV09SRCBub3QgcHJvdmlkZWQiCiAgICAgIHJldHVybiAxCiAgICBmaQogICkKfQoKIyBAZGVzY3JpcHRpb24gbG9nIG1lc3NhZ2UgdG8gZmlsZQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmxvZ0ZhdGFsKCkgewogIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1GQVRBTH0iICIkMSIKfQoKIyBAZGVzY3JpcHRpb24gSW50ZXJuYWw6IGNvbW1vbiBsb2cgbWVzc2FnZQojIEBleGFtcGxlIHRleHQKIyAgIFtkYXRlXXxbbGV2ZWxNc2ddfG1lc3NhZ2UKIwojIEBleGFtcGxlIHRleHQKIyAgIDIwMjAtMDEtMTkgMTk6MjA6MjF8RVJST1IgIHxsb2cgZXJyb3IKIyAgIDIwMjAtMDEtMTkgMTk6MjA6MjF8U0tJUFBFRHxsb2cgc2tpcHBlZAojCiMgQGFyZyAkMSBsZXZlbE1zZzpTdHJpbmcgbWVzc2FnZSdzIGxldmVsIGRlc2NyaXB0aW9uIChlZzogU1RBVFVTLCBFUlJPUiwgLi4uKQojIEBhcmcgJDIgbXNnOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CiMgQGVudiBCQVNIX0ZSQU1FV09SS19MT0dfRklMRSBTdHJpbmcgbG9nIGZpbGUgdG8gdXNlLCBkbyBub3RoaW5nIGlmIGVtcHR5CiMgQGVudiBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgaW50IGxvZyBsZXZlbCBsb2cgb25seSBpZiA+IE9GRiBvciBmYXRhbCBtZXNzYWdlcwojIEBzdGRlcnIgZGlhZ25vc3RpY3MgaW5mb3JtYXRpb24gaXMgZGlzcGxheWVkCiMgQHJlcXVpcmUgRW52OjpyZXF1aXJlTG9hZAojIEByZXF1aXJlIExvZzo6cmVxdWlyZUxvYWQKTG9nOjpsb2dNZXNzYWdlKCkgewogIGxvY2FsIGxldmVsTXNnPSIkMSIKICBsb2NhbCBtc2c9IiQyIgogIGxvY2FsIGRhdGUKCiAgaWYgW1sgLW4gIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IiBdXSAmJiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+IF9fTEVWRUxfT0ZGKSk7IHRoZW4KICAgIGRhdGU9IiQoZGF0ZSAnKyVZLSVtLSVkICVIOiVNOiVTJykiCiAgICB0b3VjaCAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iCiAgICBwcmludGYgIiVzfCU3c3wlc1xuIiAiJHtkYXRlfSIgIiR7bGV2ZWxNc2d9IiAiJHttc2d9IiA+PiIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBjb25jYXRlbmF0ZSAyIHBhdGhzIGFuZCBlbnN1cmUgdGhlIHBhdGggaXMgY29ycmVjdCB1c2luZyByZWFscGF0aCAtbQojIEBhcmcgJDEgYmFzZVBhdGg6U3RyaW5nCiMgQGFyZyAkMiBzdWJQYXRoOlN0cmluZwojIEByZXF1aXJlIExpbnV4OjpyZXF1aXJlUmVhbHBhdGhDb21tYW5kCkZpbGU6OmNvbmNhdGVuYXRlUGF0aCgpIHsKICBsb2NhbCBiYXNlUGF0aD0iJDEiCiAgbG9jYWwgc3ViUGF0aD0iJDIiCiAgbG9jYWwgZnVsbFBhdGg9IiR7YmFzZVBhdGg6KyR7YmFzZVBhdGh9L30ke3N1YlBhdGh9IgoKICByZWFscGF0aCAtbSAiJHtmdWxsUGF0aH0iIDI+L2Rldi9udWxsCn0KCiMgQGRlc2NyaXB0aW9uIERpc3BsYXkgbWVzc2FnZSB1c2luZyBlcnJvciBjb2xvciAocmVkKQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlFcnJvcigpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUwgPj0gX19MRVZFTF9FUlJPUikpOyB0aGVuCiAgICBlY2hvIC1lICIke19fRVJST1JfQ09MT1J9RVJST1IgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBmaQogIExvZzo6bG9nRXJyb3IgIiQxIgp9CgojIEBkZXNjcmlwdGlvbiBEaXNwbGF5IG1lc3NhZ2UgdXNpbmcgd2FybmluZyBjb2xvciAoeWVsbG93KQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlXYXJuaW5nKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCA+PSBfX0xFVkVMX1dBUk5JTkcpKTsgdGhlbgogICAgZWNobyAtZSAiJHtfX1dBUk5JTkdfQ09MT1J9V0FSTiAgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBmaQogIExvZzo6bG9nV2FybmluZyAiJDEiCn0KCiMgQGRlc2NyaXB0aW9uIGVuc3VyZSBlbnYgZmlsZXMgYXJlIGxvYWRlZAojIEBub2FyZ3MKIyBAZXhpdGNvZGUgMSBpZiBnZXRPcmRlcmVkQ29uZkZpbGVzIGZhaWxzCiMgQGV4aXRjb2RlIDIgaWYgb25lIG9mIGVudiBmaWxlcyBmYWlscyB0byBsb2FkCiMgQHN0ZGVyciBkaWFnbm9zdGljcyBpbmZvcm1hdGlvbiBpcyBkaXNwbGF5ZWQKRW52OjpyZXF1aXJlTG9hZCgpIHsKICBsb2NhbCBjb25maWdGaWxlc1N0cgogIGNvbmZpZ0ZpbGVzU3RyPSIkKEVudjo6Z2V0T3JkZXJlZENvbmZGaWxlcykiIHx8IHJldHVybiAxCgogIGxvY2FsIC1hIGNvbmZpZ0ZpbGVzCiAgcmVhZGFycmF5IC10IGNvbmZpZ0ZpbGVzIDw8PCIke2NvbmZpZ0ZpbGVzU3RyfSIKCiAgIyBpZiBlbXB0eSBzdHJpbmcsIHRoZXJlIHdpbGwgYmUgb25lIGVsZW1lbnQKICBpZiAoKCR7I2NvbmZpZ0ZpbGVzW0BdfSA9PSAwKSkgfHwgW1sgLXogIiR7Y29uZmlnRmlsZXNTdHJ9IiBdXTsgdGhlbgogICAgIyBzaG91bGQgbm90IGhhcHBlbiwgYXMgdGhlcmUgaXMgYWx3YXlzIGRlZmF1bHQgZmlsZQogICAgTG9nOjpkaXNwbGF5U2tpcHBlZCAibm8gZW52IGZpbGUgdG8gbG9hZCIKICAgIHJldHVybiAwCiAgZmkKCiAgRW52OjptZXJnZUNvbmZGaWxlcyAiJHtjb25maWdGaWxlc1tAXX0iIHx8IHsKICAgIExvZzo6ZGlzcGxheUVycm9yICJ3aGlsZSBsb2FkaW5nIGNvbmZpZyBmaWxlczogJHtjb25maWdGaWxlc1sqXX0iCiAgICByZXR1cm4gMgogIH0KfQoKIyBAZGVzY3JpcHRpb24gYWN0aXZhdGUgb3Igbm90IExvZzo6ZGlzcGxheSogYW5kIExvZzo6bG9nKiBmdW5jdGlvbnMKIyBiYXNlZCBvbiBCQVNIX0ZSQU1FV09SS19ESVNQTEFZX0xFVkVMIGFuZCBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwKIyBlbnZpcm9ubWVudCB2YXJpYWJsZXMgbG9hZGVkIGJ5IEVudjo6cmVxdWlyZUxvYWQKIyB0cnkgdG8gY3JlYXRlIGxvZyBmaWxlIGFuZCByb3RhdGUgaXQgaWYgbmVjZXNzYXJ5CiMgQG5vYXJncwojIEBzZXQgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMIGludCB0byBPRkYgbGV2ZWwgaWYgQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEUgaXMgZW1wdHkgb3Igbm90IHdyaXRhYmxlCiMgQGVudiBCQVNIX0ZSQU1FV09SS19ESVNQTEFZX0xFVkVMIGludAojIEBlbnYgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMIGludAojIEBlbnYgQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEUgU3RyaW5nCiMgQGVudiBCQVNIX0ZSQU1FV09SS19MT0dfRklMRV9NQVhfUk9UQVRJT04gaW50IGRvIGxvZyByb3RhdGlvbiBpZiA+IDAKIyBAZXhpdGNvZGUgMCBhbHdheXMgc3VjY2Vzc2Z1bAojIEBzdGRlcnIgZGlhZ25vc3RpY3MgaW5mb3JtYXRpb24gYWJvdXQgbG9nIGZpbGUgaXMgZGlzcGxheWVkCiMgQHJlcXVpcmUgRW52OjpyZXF1aXJlTG9hZAojIEByZXF1aXJlIFVJOjpyZXF1aXJlVGhlbWUKTG9nOjpyZXF1aXJlTG9hZCgpIHsKICBpZiBbWyAteiAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRTotfSIgXV07IHRoZW4KICAgIEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTD0ke19fTEVWRUxfT0ZGfQogICAgZXhwb3J0IEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTAogIGZpCgogIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMID4gX19MRVZFTF9PRkYpKTsgdGhlbgogICAgaWYgW1sgISAtZiAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iIF1dOyB0aGVuCiAgICAgIGlmCiAgICAgICAgISBta2RpciAtcCAiJChkaXJuYW1lICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSIpIiAyPi9kZXYvbnVsbCB8fAogICAgICAgICAgISB0b3VjaCAtLW5vLWNyZWF0ZSAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iIDI+L2Rldi9udWxsCiAgICAgIHRoZW4KICAgICAgICBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUw9JHtfX0xFVkVMX09GRn0KICAgICAgICBlY2hvIC1lICIke19fRVJST1JfQ09MT1J9RVJST1IgICAtIEZpbGUgJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0gaXMgbm90IHdyaXRhYmxlJHtfX1JFU0VUX0NPTE9SfSIgPiYyCiAgICAgIGZpCiAgICBlbGlmIFtbICEgLXcgIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IiBdXTsgdGhlbgogICAgICBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUw9JHtfX0xFVkVMX09GRn0KICAgICAgZWNobyAtZSAiJHtfX0VSUk9SX0NPTE9SfUVSUk9SICAgLSBGaWxlICR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IGlzIG5vdCB3cml0YWJsZSR7X19SRVNFVF9DT0xPUn0iID4mMgogICAgZmkKCiAgZmkKCiAgaWYgKChCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgPiBfX0xFVkVMX09GRikpOyB0aGVuCiAgICAjIHdpbGwgYWx3YXlzIGJlIGNyZWF0ZWQgZXZlbiBpZiBub3QgaW4gaW5mbyBsZXZlbAogICAgTG9nOjpsb2dNZXNzYWdlICJJTkZPIiAiTG9nZ2luZyB0byBmaWxlICR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IC0gTG9nIGxldmVsICR7QkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMfSIKICAgIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEVfTUFYX1JPVEFUSU9OID4gMCkpOyB0aGVuCiAgICAgIExvZzo6cm90YXRlICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSIgIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEVfTUFYX1JPVEFUSU9OfSIKICAgIGZpCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gZ2V0IGxpc3Qgb2YgZW52IGZpbGVzIHRvIGxvYWQKIyBpbiBvcmRlciB0byBtYWtlIHRoZW0gYXZhaWxhYmxlIGZvciBFbnY6OnJlcXVpcmVMb2FkCiMgQGVudiBCQVNIX0ZSQU1FV09SS19FTlZfRklMRVMgU3RyaW5nW10gbGlzdCBvZiBlbnYgZmlsZXMgdGhhdCBzaG91bGQgYmUgbG9hZGVkCiMgQGV4aXRjb2RlIDEgaWYgb25lIG9mIHRoZSBlbnYgZmlsZSBjYW5ub3QgYmUgZ2VuZXJhdGVkCiMgQGV4aXRjb2RlIDIgaWYgb25lIG9mIHRoZSBlbnYgZmlsZSBpcyBub3QgYSBmaWxlIG9yIHJlYWRhYmxlCiMgQHN0ZG91dCB0aGUgZW52IGZpbGVzIGFza2VkIHRvIGJlIGxvYWRlZAojIEBzdGRlcnIgZGlhZ25vc3RpYyBpbmZvcm1hdGlvbiBvbiBmYWlsdXJlCiMgQHNlZSBodHRwczovL2dpdGh1Yi5jb20vZmNoYXN0YW5ldC9iYXNoLXRvb2xzLWZyYW1ld29yay9ibG9iL21hc3Rlci9GcmFtZXdvcmtEb2MubWQjY29uZmlnX2ZpbGVfb3JkZXIKRW52OjpnZXRPcmRlcmVkQ29uZkZpbGVzKCkgewogIGxvY2FsIC1hIGNvbmZpZ0ZpbGVzPSgpCgogIGlmIFtbIC1uICIke0JBU0hfRlJBTUVXT1JLX0VOVl9GSUxFU1swXSsxfSIgXV07IHRoZW4KICAgICMgQkFTSF9GUkFNRVdPUktfRU5WX0ZJTEVTIGlzIGFuIGFycmF5CiAgICBjb25maWdGaWxlcys9KCIke0JBU0hfRlJBTUVXT1JLX0VOVl9GSUxFU1tAXX0iKQogIGZpCgogIGxvY2FsIGRlZmF1bHRFbnZGaWxlCiAgZGVmYXVsdEVudkZpbGU9IiQoRW52OjpjcmVhdGVEZWZhdWx0RW52RmlsZSkiIHx8IHJldHVybiAxCiAgY29uZmlnRmlsZXMrPSgiJHtkZWZhdWx0RW52RmlsZX0iKQoKICBsb2NhbCBmaWxlCiAgZm9yIGZpbGUgaW4gIiR7Y29uZmlnRmlsZXNbQF19IjsgZG8KICAgIGlmIFtbICEgLWYgIiR7ZmlsZX0iIHx8ICEgLXIgIiR7ZmlsZX0iIF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheUVycm9yICJPbmUgb2YgdGhlIGNvbmZpZyBmaWxlIGlzIG5vdCBhdmFpbGFibGUgJyR7ZmlsZX0nIgogICAgICByZXR1cm4gMgogICAgZmkKICAgIGVjaG8gIiR7ZmlsZX0iCiAgZG9uZQp9CgojIEBkZXNjcmlwdGlvbiBtZXJnZSBhbmQgbG9hZCBjb25mIGZpbGVzIHNwZWNpZmllZCBhcyBhcmd1bWVudAojIC0gZmlsZXMgYXJlIGNsZWFuZWQgZnJvbSBheSBjb21tZW50CiMgLSBtaXNzaW5nIHF1b3RlcyBhZnRlciBwcm9wZXJ0eSA9IHNpZ24gYXJlIGFkZGVkIGF1dG9tYXRpY2FsbHkKIyAtIGF1dG9tYXRpYyByZW1vdmUgb2YgYWxsIHdoaXRlc3BhY2UgYmVmb3JlIGFuZCBhZnRlciBkZWNsYXJhdGlvbnMKIyAtIGJhc2ggYXJyYXlzIGFyZSBub3Qgc3VwcG9ydGVkCiMgLSBpZiBhIHZhcmlhYmxlIGlzIGRlY2xhcmVkIGluIGZpcnN0IGZpbGUgYW5kIG92ZXJyaWRkZW4gbGF0ZXIgb24KIyAgIGluIHRoZSBzYW1lIGZpbGUgb3IgaW4gc3Vic2VxdWVudCBmaWxlcywgdGhvc2Ugb3ZlcmxvYWRzIHdpbGwgYmUKIyAgIGlnbm9yZWQKIyBAd2FybmluZyBpZiBhbiBlcnJvciBvY2N1cnMgd2hpbGUgbG9hZGluZyBvbmUgb2YgdGhlIGNvbmZpZyBmaWxlLCBleGl0IGNvZGUgMyBidXQgZW52aXJvbm1lbnQgY291bGQgYmUgcGFydGlhbGx5IGxvYWRlZAojIEBhcmcgJEAgYXJnczpTdHJpbmdbXSBsaXN0IG9mIGNvbmZpZ3VyYXRpb24gZmlsZXMgdG8gbG9hZCBpbiBvcmRlcgojIEBzZXQgZW52VmFycyBTdHJpbmcgd2lsbCBzZXQgaW4gZW52aXJvbm1lbnQgYWxsIHRoZSB2YXJpYWJsZXMgdGhhdCBoYXZlIGJlZW4gZGVjbGFyZWQgaW4gdGhlIGNvbmZpZyBmaWxlcwojIEBlbnYgZW52VmFycyBTdHJpbmcgdGhlIGVudiB2YXJpYWJsZXMgb2YgdGhlIGN1cnJlbnQgc2NyaXB0IGNvdWxkIGJlIHVzZWQgdG8gaW50ZXJwcmV0IHZhcmlhYmxlcyBkdXJpbmcgY29uZmlnIGZpbGVzIHBhcnNpbmcKIyBAZXhpdGNvZGUgMCBpZiBubyBjb25maWcgZmlsZXMgcHJvdmlkZWQgb3IgbG9hZCBjb21wbGV0ZWQgc3VjY2Vzc2Z1bGx5CiMgQGV4aXRjb2RlIDEgaWYgZXJyb3Igb2NjdXJyZWQgZHVyaW5nIHBhcnNpbmcgdGhlIGNvbmZpZyBmaWxlcyAoZmlsZSBub3QgZm91bmQsIGdyZXAsIGF3ayBvciBzZWQgZXJyb3IpCiMgQGV4aXRjb2RlIDIgaWYgdGVtcG9yYXJ5IGZpbGUgY2Fubm90IGJlIGNyZWF0ZWQKIyBAZXhpdGNvZGUgMyBpZiBhbiBlcnJvciBvY2N1cnJlZCBkdXJpbmcgY29uZmlnIGZpbGUgc291cmNpbmcKIyBAc3RkZXJyIGRpYWdub3N0aWNzIGluZm9ybWF0aW9uIGlzIGRpc3BsYXllZAojIEBzZWUgbGFyZ2VseSBpbnNwaXJlZCBidXQgbW9kaWZpZWQgZnJvbSBodHRwczovL29wZW5zb3VyY2UuY29tL2FydGljbGUvMjEvNS9wcm9jZXNzaW5nLWNvbmZpZ3VyYXRpb24tZmlsZXMtc2hlbGwKRW52OjptZXJnZUNvbmZGaWxlcygpIHsKICBsb2NhbCAtYSBjb25maWdGaWxlTGlzdD0oIiRAIikKCiAgaWYgKCgkeyNjb25maWdGaWxlTGlzdFtAXX0gPT0gMCkpOyB0aGVuCiAgICByZXR1cm4gMAogIGZpCgogIGxvY2FsIGNvbWJpbmVkQ29uZmlnRmlsZQogIGNvbWJpbmVkQ29uZmlnRmlsZT0iJChGcmFtZXdvcms6OmNyZWF0ZVRlbXBGaWxlICJtZXJnZUNvbmZGaWxlcyIpIiB8fCByZXR1cm4gMgoKICAoCiAgICAjIHJlbW92ZXMgYW55IHRyYWlsaW5nIHdoaXRlc3BhY2UgZnJvbSBlYWNoIGZpbGUsIGlmIGFueQogICAgIyB0aGlzIGlzIGFic29sdXRlbHkgcmVxdWlyZWQgd2hlbiBpbXBvcnRpbmcgaW50byBDb25maWdNYXBzCiAgICAjIHB1dCBxdW90ZXMgYXJvdW5kIHZhbHVlcwogICAgc2VkIC1FIC1lICQncy9ccyokLy8gOyAvXiQvZCA7IC9eIy4qJC9kIDsgcy89KFteIlwnXS4qKSQvPSJcXDEiLycgIiR7Y29uZmlnRmlsZUxpc3RbQF19IiB8CiAgICAgICMgcmVtb3ZlIGFsbCBjb21tZW50IGxpbmVzCiAgICAgIEZpbHRlcnM6OmNvbW1lbnRMaW5lcyB8CiAgICAgICMgaXRlcmF0ZXMgb3ZlciBlYWNoIGZpbGUgYW5kIHByaW50cyAoZGVmYXVsdCBhd2sgYmVoYXZpb3IpCiAgICAgICMgZWFjaCB1bmlxdWUgbGluZTsgb25seSB0YWtlcyBmaXJzdCB2YWx1ZSBhbmQgaWdub3JlcyBkdXBsaWNhdGVzCiAgICAgIGF3ayAtRj0gJyFsaW5lWyQxXSsrJwogICkgPiIke2NvbWJpbmVkQ29uZmlnRmlsZX0iIHx8IHJldHVybiAxCgogICMgaGF2ZSB0byBleHBvcnQgZXZlcnl0aGluZywgYW5kIHNvdXJjZSBpdCB0d2ljZToKICAjIDEpIGZpcnN0IHNvdXJjZSBpcyB0byByZWFsaXplIHZhcmlhYmxlcwogICMgMikgc2Vjb25kIHRpbWUgaXMgdG8gcmVhbGl6ZSByZWZlcmVuY2VzCiAgc2V0IC1vIGFsbGV4cG9ydAogICMgc2hlbGxjaGVjayBzb3VyY2U9LmZyYW1ld29yay1jb25maWcKICBzb3VyY2UgIiR7Y29tYmluZWRDb25maWdGaWxlfSIgfHwgcmV0dXJuIDMKICAjIHNoZWxsY2hlY2sgc291cmNlPS5mcmFtZXdvcmstY29uZmlnCiAgc291cmNlICIke2NvbWJpbmVkQ29uZmlnRmlsZX0iIHx8IHJldHVybiAzCiAgc2V0ICtvIGFsbGV4cG9ydAp9CgojIEBkZXNjcmlwdGlvbiBEaXNwbGF5IG1lc3NhZ2UgdXNpbmcgc2tpcCBjb2xvciAoeWVsbG93KQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlTa2lwcGVkKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCA+PSBfX0xFVkVMX0lORk8pKTsgdGhlbgogICAgZWNobyAtZSAiJHtfX1NLSVBQRURfQ09MT1J9U0tJUFBFRCAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBmaQogIExvZzo6bG9nU2tpcHBlZCAiJDEiCn0KCiMgQGRlc2NyaXB0aW9uIGxvZyBtZXNzYWdlIHRvIGZpbGUKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpsb2dFcnJvcigpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+PSBfX0xFVkVMX0VSUk9SKSk7IHRoZW4KICAgIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1FUlJPUn0iICIkMSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBsb2cgbWVzc2FnZSB0byBmaWxlCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6bG9nV2FybmluZygpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+PSBfX0xFVkVMX1dBUk5JTkcpKTsgdGhlbgogICAgTG9nOjpsb2dNZXNzYWdlICIkezI6LVdBUk5JTkd9IiAiJDEiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gVG8gYmUgY2FsbGVkIGJlZm9yZSBsb2dnaW5nIGluIHRoZSBsb2cgZmlsZQojIEBhcmcgJDEgZmlsZTpzdHJpbmcgbG9nIGZpbGUgbmFtZQojIEBhcmcgJDIgbWF4TG9nRmlsZXNDb3VudDppbnQgbWF4aW11bSBudW1iZXIgb2YgbG9nIGZpbGVzCkxvZzo6cm90YXRlKCkgewogIGxvY2FsIGZpbGU9IiQxIgogIGxvY2FsIG1heExvZ0ZpbGVzQ291bnQ9IiR7MjotNX0iCgogIGlmIFtbICEgLWYgIiR7ZmlsZX0iIF1dOyB0aGVuCiAgICBMb2c6OmRpc3BsYXlTa2lwcGVkICJMb2cgZmlsZSAke2ZpbGV9IGRvZXNuJ3QgZXhpc3QgeWV0IgogICAgcmV0dXJuIDAKICBmaQogIGZvciBpIGluICQoc2VxICQoKG1heExvZ0ZpbGVzQ291bnQgLSAxKSkgLTEgMSk7IGRvCiAgICBMb2c6OmRpc3BsYXlJbmZvICJMb2cgcm90YXRpb24gJHtmaWxlfS4ke2l9IHRvICR7ZmlsZX0uJCgoaSArIDEpKSIKICAgIG12ICIke2ZpbGV9LiJ7IiR7aX0iLCIkKChpICsgMSkpIn0gJj4vZGV2L251bGwgfHwgdHJ1ZQogIGRvbmUKICBpZiBjcCAiJHtmaWxlfSIgIiR7ZmlsZX0uMSIgJj4vZGV2L251bGw7IHRoZW4KICAgIGVjaG8gPiIke2ZpbGV9IiAjIHJlc2V0IGxvZyBmaWxlCiAgICBMb2c6OmRpc3BsYXlJbmZvICJMb2cgcm90YXRpb24gJHtmaWxlfSB0byAke2ZpbGV9LjEiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gZW5zdXJlIGNvbW1hbmQgcmVhbHBhdGggaXMgYXZhaWxhYmxlCiMgQGV4aXRjb2RlIDEgaWYgcmVhbHBhdGggY29tbWFuZCBub3QgYXZhaWxhYmxlCiMgQHN0ZGVyciBkaWFnbm9zdGljcyBpbmZvcm1hdGlvbiBpcyBkaXNwbGF5ZWQKTGludXg6OnJlcXVpcmVSZWFscGF0aENvbW1hbmQoKSB7CiAgQXNzZXJ0Ojpjb21tYW5kRXhpc3RzIHJlYWxwYXRoCn0KCiMgQGRlc2NyaXB0aW9uIGxvYWQgY29sb3IgdGhlbWUKIyBAbm9hcmdzCiMgQGVudiBCQVNIX0ZSQU1FV09SS19USEVNRSBTdHJpbmcgdGhlbWUgdG8gdXNlCiMgQGV4aXRjb2RlIDAgYWx3YXlzIHN1Y2Nlc3NmdWwKVUk6OnJlcXVpcmVUaGVtZSgpIHsKICBVSTo6dGhlbWUgIiR7QkFTSF9GUkFNRVdPUktfVEhFTUUtZGVmYXVsdH0iCn0KCiMgQGRlc2NyaXB0aW9uIGNoZWNrIGlmIGNvbW1hbmQgc3BlY2lmaWVkIGV4aXN0cyBvciByZXR1cm4gMQojIHdpdGggZXJyb3IgYW5kIG1lc3NhZ2UgaWYgbm90CiMKIyBAYXJnICQxIGNvbW1hbmROYW1lOlN0cmluZyBvbiB3aGljaCBleGlzdGVuY2UgbXVzdCBiZSBjaGVja2VkCiMgQGFyZyAkMiBoZWxwSWZOb3RFeGlzdHM6U3RyaW5nIGEgaGVscCBjb21tYW5kIHRvIGRpc3BsYXkgaWYgdGhlIGNvbW1hbmQgZG9lcyBub3QgZXhpc3QKIwojIEBleGl0Y29kZSAxIGlmIHRoZSBjb21tYW5kIHNwZWNpZmllZCBkb2VzIG5vdCBleGlzdAojIEBzdGRlcnIgZGlhZ25vc3RpYyBpbmZvcm1hdGlvbiArIGhlbHAgaWYgc2Vjb25kIGFyZ3VtZW50IGlzIHByb3ZpZGVkCkFzc2VydDo6Y29tbWFuZEV4aXN0cygpIHsKICBsb2NhbCBjb21tYW5kTmFtZT0iJDEiCiAgbG9jYWwgaGVscElmTm90RXhpc3RzPSIkMiIKCiAgIiR7QkFTSF9GUkFNRVdPUktfQ09NTUFORDotY29tbWFuZH0iIC12ICIke2NvbW1hbmROYW1lfSIgPi9kZXYvbnVsbCAyPi9kZXYvbnVsbCB8fCB7CiAgICBMb2c6OmRpc3BsYXlFcnJvciAiJHtjb21tYW5kTmFtZX0gaXMgbm90IGluc3RhbGxlZCwgcGxlYXNlIGluc3RhbGwgaXQiCiAgICBpZiBbWyAtbiAiJHtoZWxwSWZOb3RFeGlzdHN9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlJbmZvICIke2hlbHBJZk5vdEV4aXN0c30iCiAgICBmaQogICAgcmV0dXJuIDEKICB9CiAgcmV0dXJuIDAKfQoKIyBAZGVzY3JpcHRpb24gZGVmYXVsdCBlbnYgZmlsZSB3aXRoIGFsbCBkZWZhdWx0IHZhbHVlcwojIEBzdGRvdXQgdGhlIGRlZmF1bHQgZW52IGZpbGVwYXRoCkVudjo6Y3JlYXRlRGVmYXVsdEVudkZpbGUoKSB7CiAgbG9jYWwgZW52RmlsZQogIGVudkZpbGU9IiQoRnJhbWV3b3JrOjpjcmVhdGVUZW1wRmlsZSAiY3JlYXRlRGVmYXVsdEVudkZpbGVFbnZGaWxlIikiIHx8IHJldHVybiAyCgogICgKICAgIGVjaG8gIkJBU0hfRlJBTUVXT1JLX1RIRU1FPSR7QkFTSF9GUkFNRVdPUktfVEhFTUU6LWRlZmF1bHR9IgogICAgZWNobyAiQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMPSR7QkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMOi0wfSIKICAgIGVjaG8gIkJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUw9JHtCQVNIX0ZSQU1FV09SS19ESVNQTEFZX0xFVkVMOi0ke19fTEVWRUxfV0FSTklOR319IgogICAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDE2CiAgICBlY2hvICdCQVNIX0ZSQU1FV09SS19MT0dfRklMRT0iJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRTotIiR7RlJBTUVXT1JLX1JPT1RfRElSfS9sb2dzLyR7U0NSSVBUX05BTUV9LmxvZyJ9IicKICAgIGVjaG8gIkJBU0hfRlJBTUVXT1JLX0xPR19GSUxFX01BWF9ST1RBVElPTj0ke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFX01BWF9ST1RBVElPTjotNX0iCiAgKSA+IiR7ZW52RmlsZX0iCiAgZWNobyAiJHtlbnZGaWxlfSIKfQoKIyBAZGVzY3JpcHRpb24gcmVtb3ZlIGNvbW1lbnQgbGluZXMgZnJvbSBpbnB1dCBvciBmaWxlcyBwcm92aWRlZCBhcyBhcmd1bWVudHMKIyBAYXJnICRAIGZpbGVzOlN0cmluZ1tdIChvcHRpb25hbCkgdGhlIGZpbGVzIHRvIGZpbHRlcgojIEBlbnYgY29tbWVudExpbmVQcmVmaXggU3RyaW5nIHRoZSBjb21tZW50IGxpbmUgcHJlZml4IChkZWZhdWx0IHZhbHVlOiAjKQojIEBleGl0Y29kZSAwIGlmIGxpbmVzIGZpbHRlcmVkIG9yIG5vdAojIEBleGl0Y29kZSAyIGlmIGdyZXAgZmFpbHMgZm9yIGFueSBvdGhlciByZWFzb25zIHRoYW4gbm90IGZvdW5kCiMgQHN0ZGluIHRoZSBmaWxlIGFzIHN0ZGluIHRvIGZpbHRlciAoYWx0ZXJuYXRpdmUgdG8gZmlsZXMgYXJndW1lbnQpCiMgQHN0ZG91dCB0aGUgZmlsdGVyZWQgbGluZXMKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTIwCkZpbHRlcnM6OmNvbW1lbnRMaW5lcygpIHsKICBncmVwIC12eEUgIltbOmJsYW5rOl1dKigke2NvbW1lbnRMaW5lUHJlZml4Oi0jfS4qKT8iICIkQCIgfHwgdGVzdCAkPyA9IDEKfQoKIyBAZGVzY3JpcHRpb24gY3JlYXRlIGEgdGVtcCBmaWxlIHVzaW5nIGRlZmF1bHQgVE1QRElSIHZhcmlhYmxlCiMgaW5pdGlhbGl6ZWQgaW4gX2luY2x1ZGVzL19jb21tb25IZWFkZXIuc2gKIyBAZW52IFRNUERJUiBTdHJpbmcgKGRlZmF1bHQgdmFsdWUgL3RtcCkKIyBAYXJnICQxIHRlbXBsYXRlTmFtZTpTdHJpbmcgdGVtcGxhdGUgbmFtZSB0byB1c2Uob3B0aW9uYWwpCkZyYW1ld29yazo6Y3JlYXRlVGVtcEZpbGUoKSB7CiAgbWt0ZW1wIC1wICIke1RNUERJUjotL3RtcH0iIC10ICIkezE6LX0uWFhYWFhYWFhYWFhYIgp9CgojIEBkZXNjcmlwdGlvbiBsb2cgbWVzc2FnZSB0byBmaWxlCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6bG9nU2tpcHBlZCgpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+PSBfX0xFVkVMX0lORk8pKTsgdGhlbgogICAgTG9nOjpsb2dNZXNzYWdlICIkezI6LVNLSVBQRUR9IiAiJDEiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gbG9hZCBjb2xvcnMgdGhlbWUgY29uc3RhbnRzCiMgQHdhcm5pbmcgaWYgdHR5IG5vdCBvcGVuZWQsIG5vQ29sb3IgdGhlbWUgd2lsbCBiZSBjaG9zZW4KIyBAYXJnICQxIHRoZW1lOlN0cmluZyB0aGUgdGhlbWUgdG8gdXNlIChkZWZhdWx0LCBub0NvbG9yKQojIEBhcmcgJEAgYXJnczpTdHJpbmdbXQojIEBzZXQgX19FUlJPUl9DT0xPUiBTdHJpbmcgaW5kaWNhdGUgZXJyb3Igc3RhdHVzCiMgQHNldCBfX0lORk9fQ09MT1IgU3RyaW5nIGluZGljYXRlIGluZm8gc3RhdHVzCiMgQHNldCBfX1NVQ0NFU1NfQ09MT1IgU3RyaW5nIGluZGljYXRlIHN1Y2Nlc3Mgc3RhdHVzCiMgQHNldCBfX1dBUk5JTkdfQ09MT1IgU3RyaW5nIGluZGljYXRlIHdhcm5pbmcgc3RhdHVzCiMgQHNldCBfX1NLSVBQRURfQ09MT1IgU3RyaW5nIGluZGljYXRlIHNraXBwZWQgc3RhdHVzCiMgQHNldCBfX0RFQlVHX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSBkZWJ1ZyBzdGF0dXMKIyBAc2V0IF9fSEVMUF9DT0xPUiBTdHJpbmcgaW5kaWNhdGUgaGVscCBzdGF0dXMKIyBAc2V0IF9fVEVTVF9DT0xPUiBTdHJpbmcgbm90IHVzZWQKIyBAc2V0IF9fVEVTVF9FUlJPUl9DT0xPUiBTdHJpbmcgbm90IHVzZWQKIyBAc2V0IF9fSEVMUF9USVRMRV9DT0xPUiBTdHJpbmcgdXNlZCB0byBkaXNwbGF5IGhlbHAgdGl0bGUgaW4gaGVscCBzdHJpbmdzCiMgQHNldCBfX0hFTFBfT1BUSU9OX0NPTE9SIFN0cmluZyB1c2VkIHRvIGRpc3BsYXkgaGlnaGxpZ2h0IG9wdGlvbnMgaW4gaGVscCBzdHJpbmdzCiMKIyBAc2V0IF9fUkVTRVRfQ09MT1IgU3RyaW5nIHJlc2V0IGRlZmF1bHQgY29sb3IKIwojIEBzZXQgX19IRUxQX0VYQU1QTEUgU3RyaW5nIHRvIHJlbW92ZQojIEBzZXQgX19IRUxQX1RJVExFIFN0cmluZyB0byByZW1vdmUKIyBAc2V0IF9fSEVMUF9OT1JNQUwgU3RyaW5nIHRvIHJlbW92ZQojIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIwMzQKVUk6OnRoZW1lKCkgewogIGxvY2FsIHRoZW1lPSIkezEtZGVmYXVsdH0iCiAgaWYgW1sgISAiJHt0aGVtZX0iID1+IC1mb3JjZSQgXV0gJiYgISBBc3NlcnQ6OnR0eTsgdGhlbgogICAgdGhlbWU9Im5vQ29sb3IiCiAgZmkKICBjYXNlICIke3RoZW1lfSIgaW4KICAgIGRlZmF1bHQgfCBkZWZhdWx0LWZvcmNlKQogICAgICB0aGVtZT0iZGVmYXVsdCIKICAgICAgOzsKICAgIG5vQ29sb3IpIDs7CiAgICAqKQogICAgICBMb2c6OmZhdGFsICJpbnZhbGlkIHRoZW1lIHByb3ZpZGVkIgogICAgICA7OwogIGVzYWMKICBpZiBbWyAiJHt0aGVtZX0iID0gImRlZmF1bHQiIF1dOyB0aGVuCiAgICBCQVNIX0ZSQU1FV09SS19USEVNRT0iZGVmYXVsdCIKICAgICMgY2hlY2sgY29sb3JzIGFwcGxpY2FibGUgaHR0cHM6Ly9taXNjLmZsb2dpc29mdC5jb20vYmFzaC90aXBfY29sb3JzX2FuZF9mb3JtYXR0aW5nCiAgICBfX0VSUk9SX0NPTE9SPSdcZVszMW0nICAgICAgICAgIyBSZWQKICAgIF9fSU5GT19DT0xPUj0nXGVbNDRtJyAgICAgICAgICAjIHdoaXRlIG9uIGxpZ2h0Qmx1ZQogICAgX19TVUNDRVNTX0NPTE9SPSdcZVszMm0nICAgICAgICMgR3JlZW4KICAgIF9fV0FSTklOR19DT0xPUj0nXGVbMzNtJyAgICAgICAjIFllbGxvdwogICAgX19TS0lQUEVEX0NPTE9SPSdcZVszM20nICAgICAgICMgWWVsbG93CiAgICBfX0RFQlVHX0NPTE9SPSdcZVszN20nICAgICAgICAgIyBHcmV5CiAgICBfX0hFTFBfQ09MT1I9J1xlWzc7NDk7MzNtJyAgICAgIyBCbGFjayBvbiBHb2xkCiAgICBfX1RFU1RfQ09MT1I9J1xlWzEwMG0nICAgICAgICAgIyBMaWdodCBtYWdlbnRhCiAgICBfX1RFU1RfRVJST1JfQ09MT1I9J1xlWzQxbScgICAgIyB3aGl0ZSBvbiByZWQKICAgIF9fSEVMUF9USVRMRV9DT0xPUj0iXGVbMTszN20iICAjIEJvbGQKICAgIF9fSEVMUF9PUFRJT05fQ09MT1I9IlxlWzE7MzRtIiAjIEJsdWUKICAgICMgSW50ZXJuYWw6IHJlc2V0IGNvbG9yCiAgICBfX1JFU0VUX0NPTE9SPSdcZVswbScgIyBSZXNldCBDb2xvcgogICAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTU1LFNDMjAzNAogICAgX19IRUxQX0VYQU1QTEU9IiQoZWNobyAtZSAiXGVbMjs5N20iKSIKICAgICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjE1NSxTQzIwMzQKICAgIF9fSEVMUF9USVRMRT0iJChlY2hvIC1lICJcZVsxOzM3bSIpIgogICAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTU1LFNDMjAzNAogICAgX19IRUxQX05PUk1BTD0iJChlY2hvIC1lICJcMDMzWzBtIikiCiAgZWxzZQogICAgQkFTSF9GUkFNRVdPUktfVEhFTUU9Im5vQ29sb3IiCiAgICAjIGNoZWNrIGNvbG9ycyBhcHBsaWNhYmxlIGh0dHBzOi8vbWlzYy5mbG9naXNvZnQuY29tL2Jhc2gvdGlwX2NvbG9yc19hbmRfZm9ybWF0dGluZwogICAgX19FUlJPUl9DT0xPUj0nJwogICAgX19JTkZPX0NPTE9SPScnCiAgICBfX1NVQ0NFU1NfQ09MT1I9JycKICAgIF9fV0FSTklOR19DT0xPUj0nJwogICAgX19TS0lQUEVEX0NPTE9SPScnCiAgICBfX0RFQlVHX0NPTE9SPScnCiAgICBfX0hFTFBfQ09MT1I9JycKICAgIF9fVEVTVF9DT0xPUj0nJwogICAgX19URVNUX0VSUk9SX0NPTE9SPScnCiAgICBfX0hFTFBfVElUTEVfQ09MT1I9JycKICAgIF9fSEVMUF9PUFRJT05fQ09MT1I9JycKICAgICMgSW50ZXJuYWw6IHJlc2V0IGNvbG9yCiAgICBfX1JFU0VUX0NPTE9SPScnCiAgICBfX0hFTFBfRVhBTVBMRT0nJwogICAgX19IRUxQX1RJVExFPScnCiAgICBfX0hFTFBfTk9STUFMPScnCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gY2hlY2sgaWYgdHR5IChpbnRlcmFjdGl2ZSBtb2RlKSBpcyBhY3RpdmUKIyBAbm9hcmdzCiMgQGV4aXRjb2RlIDEgaWYgdHR5IG5vdCBhY3RpdmUKIyBAZW52IE5PTl9JTlRFUkFDVElWRSBpZiAxIGNvbnNpZGVyIGFzIG5vdCBpbnRlcmFjdGl2ZSBldmVuIGlmIGVudmlyb25tZW50IGlzIGludGVyYWN0aXZlCiMgQGVudiBJTlRFUkFDVElWRSBpZiAxIGNvbnNpZGVyIGFzIGludGVyYWN0aXZlIGV2ZW4gaWYgZW52aXJvbm1lbnQgaXMgbm90IGludGVyYWN0aXZlCiMgQHN0ZGVyciBkaWFnbm9zdGljIGluZm9ybWF0aW9uICsgaGVscCBpZiBzZWNvbmQgYXJndW1lbnQgaXMgcHJvdmlkZWQKQXNzZXJ0Ojp0dHkoKSB7CiAgaWYgW1sgIiR7Tk9OX0lOVEVSQUNUSVZFOi0wfSIgPSAiMSIgXV07IHRoZW4KICAgIHJldHVybiAxCiAgZmkKICBpZiBbWyAiJHtJTlRFUkFDVElWRTotMH0iID0gIjEiIF1dOyB0aGVuCiAgICByZXR1cm4gMAogIGZpCiAgW1sgLXQgMSB8fCAtdCAyIF1dCn0KCiMgRlVOQ1RJT05TCgpmYWNhZGVfbWFpbl9hMTc1Yjg1MjczNjg0ZjBkODkxMGQxNmQ2ZDgwY2FmZigpIHsKIyBSRVFVSVJFUwpMaW51eDo6cmVxdWlyZUV4ZWN1dGVkQXNVc2VyCkVudjo6cmVxdWlyZUxvYWQKTG9nOjpyZXF1aXJlTG9hZApMaW51eDo6cmVxdWlyZVJlYWxwYXRoQ29tbWFuZApVSTo6cmVxdWlyZVRoZW1lCkNvbXBpbGVyOjpGYWNhZGU6OnJlcXVpcmVDb21tYW5kQmluRGlyCgojIEByZXF1aXJlIENvbXBpbGVyOjpGYWNhZGU6OnJlcXVpcmVDb21tYW5kQmluRGlyCgojIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIxNTQsU0MyMDE2CmZ1bmN0aW9uVG9DYWxsPSdEYjo6cXVlcnlPbmVEYXRhYmFzZScKIiR7ZnVuY3Rpb25Ub0NhbGx9IiAiJEAiCgp9CgpmYWNhZGVfbWFpbl9hMTc1Yjg1MjczNjg0ZjBkODkxMGQxNmQ2ZDgwY2FmZiAiJEAiCg==" -Env::pathPrepend "${COMMAND_BIN_DIR}" +Compiler::Embed::extractFileFromBase64 \ + "${embed_function_DbQueryOneDatabase}" \ + "${encoded_binary_file_DbQueryOneDatabase}" -# prepare bin directory for eventual bin files generated by Embed::embed -mkdir -p "${TMPDIR:-/tmp}/bin" -Env::pathPrepend "${TMPDIR:-/tmp}/bin" +facade_main_18f5ddcf2cbe4feba7e4cda5e715c153() { +BASH_TOOLS_ROOT_DIR="$(cd "${CURRENT_DIR}/.." && pwd -P)" +if [[ -d "${BASH_TOOLS_ROOT_DIR}/vendor/bash-tools-framework/" ]]; then + FRAMEWORK_ROOT_DIR="$(cd "${BASH_TOOLS_ROOT_DIR}/vendor/bash-tools-framework" && pwd -P)" +else + # if the directory does not exist yet, give a value to FRAMEWORK_ROOT_DIR + FRAMEWORK_ROOT_DIR="${BASH_TOOLS_ROOT_DIR}/vendor/bash-tools-framework" +fi +FRAMEWORK_SRC_DIR="${FRAMEWORK_ROOT_DIR}/src" +FRAMEWORK_BIN_DIR="${FRAMEWORK_ROOT_DIR}/bin" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_ROOT_DIR}/vendor" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_ROOT_DIR}/vendor/bin" -Assert::expectNonRootUser +if [[ -f "${HOME}/.bash-tools/.env" ]]; then + export BASH_FRAMEWORK_ENV_FILES=("${HOME}/.bash-tools/.env") +fi +# REQUIRES +Env::requireLoad +UI::requireTheme +Linux::requireRealpathCommand +Log::requireLoad +Compiler::Embed::requireEmbedBinDir +Compiler::Facade::requireCommandBinDir +Linux::requireExecutedAsUser + +# @require Compiler::Facade::requireCommandBinDir +# shellcheck disable=SC2034 -#default values -SCRIPT_NAME=${0##*/} -JOBS_NUMBER=1 -MYSQL_OPTIONS="" -OUTPUT_DIR="${HOME}/.bash-tools/output" -DSN="default.local" -LOG_FORMAT="none" -DB_NAME="" - -# Usage info -showHelp() { - local dsnList scriptsList - dsnList="$(Conf::getMergedList "dsn" "env")" - scriptsList="$(Conf::getMergedList "dbScripts" "sh")" +# default values +declare outputDirectory="${HOME}/.bash-tools/output" +declare optionFromDsn="" +declare optionOutputDir="" +declare optionLogFormat="" +declare optionJobs="" +declare argScriptToExecute="" +declare -a scriptArguments=() + +# other configuration +declare copyrightBeginYear="2020" +declare QUERIES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/dbQueries" +declare HOME_QUERIES_DIR="${HOME}/.bash-tools/dbQueries" + +declare -a BASH_FRAMEWORK_ARGV_FILTERED=() + +copyrightCallback() { + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" + fi + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" +} + +# shellcheck disable=SC2317 # if function is overridden +updateArgListInfoVerboseCallback() { + BASH_FRAMEWORK_ARGV_FILTERED+=(--verbose) +} +# shellcheck disable=SC2317 # if function is overridden +updateArgListDebugVerboseCallback() { + BASH_FRAMEWORK_ARGV_FILTERED+=(-vv) +} +# shellcheck disable=SC2317 # if function is overridden +updateArgListTraceVerboseCallback() { + BASH_FRAMEWORK_ARGV_FILTERED+=(-vvv) +} +# shellcheck disable=SC2317 # if function is overridden +updateArgListEnvFileCallback() { :; } +# shellcheck disable=SC2317 # if function is overridden +updateArgListLogLevelCallback() { :; } +# shellcheck disable=SC2317 # if function is overridden +updateArgListDisplayLevelCallback() { :; } +# shellcheck disable=SC2317 # if function is overridden +updateArgListNoColorCallback() { + BASH_FRAMEWORK_ARGV_FILTERED+=(--no-color) +} +# shellcheck disable=SC2317 # if function is overridden +updateArgListThemeCallback() { :; } +# shellcheck disable=SC2317 # if function is overridden +updateArgListQuietCallback() { :; } + +# shellcheck disable=SC2317 # if function is overridden +optionHelpCallback() { + dbScriptAllDatabasesCommand help + exit 0 +} + +# shellcheck disable=SC2317 # if function is overridden +optionVersionCallback() { + echo "${SCRIPT_NAME} version 2.0" + exit 0 +} + +# shellcheck disable=SC2317 # if function is overridden +optionEnvFileCallback() { + local envFile="$2" + if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then + Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" + exit 1 + fi +} + +# shellcheck disable=SC2317 # if function is overridden +optionInfoVerboseCallback() { + BASH_FRAMEWORK_ARGS_VERBOSE_OPTION='--verbose' + BASH_FRAMEWORK_ARGS_VERBOSE=${__VERBOSE_LEVEL_INFO} + BASH_FRAMEWORK_DISPLAY_LEVEL=${__LEVEL_INFO} +} + +# shellcheck disable=SC2317 # if function is overridden +optionDebugVerboseCallback() { + BASH_FRAMEWORK_ARGS_VERBOSE_OPTION='-vv' + BASH_FRAMEWORK_ARGS_VERBOSE=${__VERBOSE_LEVEL_DEBUG} + BASH_FRAMEWORK_DISPLAY_LEVEL=${__LEVEL_DEBUG} +} + +# shellcheck disable=SC2317 # if function is overridden +optionTraceVerboseCallback() { + BASH_FRAMEWORK_ARGS_VERBOSE_OPTION='-vvv' + BASH_FRAMEWORK_ARGS_VERBOSE=${__VERBOSE_LEVEL_TRACE} + BASH_FRAMEWORK_DISPLAY_LEVEL=${__LEVEL_DEBUG} +} + +getLevel() { + local levelName="$1" + case "${levelName^^}" in + OFF) + echo "${__LEVEL_OFF}" + ;; + ERROR) + echo "${__LEVEL_ERROR}" + ;; + WARNING) + echo "${__LEVEL_WARNING}" + ;; + INFO) + echo "${__LEVEL_INFO}" + ;; + DEBUG | TRACE) + echo "${__LEVEL_DEBUG}" + ;; + *) + Log::displayError "Command ${SCRIPT_NAME} - Invalid level ${level}" + return 1 + esac +} + +getVerboseLevel() { + local levelName="$1" + case "${levelName^^}" in + OFF) + echo "${__VERBOSE_LEVEL_OFF}" + ;; + ERROR | WARNING | INFO) + echo "${__VERBOSE_LEVEL_INFO}" + ;; + DEBUG) + echo "${__VERBOSE_LEVEL_DEBUG}" + ;; + TRACE) + echo "${__VERBOSE_LEVEL_TRACE}" + ;; + *) + Log::displayError "Command ${SCRIPT_NAME} - Invalid level ${level}" + return 1 + esac +} + +# shellcheck disable=SC2317 # if function is overridden +optionDisplayLevelCallback() { + local level="$2" + local logLevel verboseLevel + logLevel="$(getLevel "${level}")" + verboseLevel="$(getVerboseLevel "${level}")" + BASH_FRAMEWORK_ARGS_VERBOSE=${verboseLevel} + BASH_FRAMEWORK_DISPLAY_LEVEL=${logLevel} +} + +# shellcheck disable=SC2317 # if function is overridden +optionLogLevelCallback() { + local level="$2" + local logLevel verboseLevel + logLevel="$(getLevel "${level}")" + verboseLevel="$(getVerboseLevel "${level}")" + BASH_FRAMEWORK_ARGS_VERBOSE=${verboseLevel} + BASH_FRAMEWORK_LOG_LEVEL=${logLevel} +} + +# shellcheck disable=SC2317 # if function is overridden +optionLogFileCallback() { + local logFile="$2" + BASH_FRAMEWORK_LOG_FILE="${logFile}" +} + +# shellcheck disable=SC2317 # if function is overridden +optionQuietCallback() { + BASH_FRAMEWORK_QUIET_MODE=1 +} + +# shellcheck disable=SC2317 # if function is overridden +optionNoColorCallback() { + UI::theme "noColor" +} + +# shellcheck disable=SC2317 # if function is overridden +optionThemeCallback() { + UI::theme "$2" +} + +displayConfig() { + echo "Config" + UI::drawLine "-" + local var + while read -r var; do + printf '%-40s = %s\n' "${var}" "$(declare -p "${var}" | sed -E -e 's/^[^=]+=(.*)/\1/')" + done < <(typeset -p | awk 'match($3, "^(BASH_FRAMEWORK_[^=]+)=", m) { print m[1] }' | sort) + exit 0 +} + +optionBashFrameworkConfigCallback() { + if [[ ! -f "$2" ]]; then + Log::fatal "Command ${SCRIPT_NAME} - Bash framework config file '$2' does not exists" + fi +} + +defaultFrameworkConfig="$( + cat <<'EOF' +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$" +# Source directories +FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" +) + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +EOF +)" + +commandOptionParseFinished() { + if [[ -z "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then + BASH_FRAMEWORK_ENV_FILES=() + fi + BASH_FRAMEWORK_ENV_FILES+=("${optionEnvFiles[@]}") + export BASH_FRAMEWORK_ENV_FILES + Env::requireLoad + Log::requireLoad + + # load .framework-config + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + BASH_FRAMEWORK_CONFIG_FILE="${optionBashFrameworkConfig}" + # shellcheck source=/.framework-config + source "${optionBashFrameworkConfig}" || + Log::fatal "Command ${SCRIPT_NAME} - error while loading specific .framework-config file: ${optionBashFrameworkConfig}" + else + # shellcheck disable=SC2034 + BASH_FRAMEWORK_CONFIG_FILE="" + # shellcheck source=/.framework-config + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || { + # load default template framework config + if [[ ! -f "${PERSISTENT_TMPDIR}/.framework-config" ]]; then + echo "${defaultFrameworkConfig}" > "${PERSISTENT_TMPDIR}/.framework-config" + fi + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${PERSISTENT_TMPDIR}" || { + Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config.default file" + } + Log::displayWarning "Command ${SCRIPT_NAME} - Load default .framework-config file - ${PERSISTENT_TMPDIR}/.framework-config" + } + fi + + if [[ "${optionConfig}" = "1" ]]; then + displayConfig + fi +} + +# default values +declare optionFromDsn="" + +optionJobsCallback() { + if ! [[ ${optionJobs} =~ ^[0-9]+$ ]]; then + Log::fatal "number of jobs is incorrect" + fi + + if [[ ${optionJobs} -lt 1 ]]; then + Log::fatal "number of jobs must be greater than 0" + fi +} + +declare -a PARALLEL_OPTIONS - cat < 0)); do + local options_parse_arg="$1" + local argOptDefaultBehavior=0 + case "${options_parse_arg}" in + # Option 1/20 + # Option optionJobs --jobs|-j variableType String min 0 max 1 authorizedValues '' regexp '' + --jobs | -j) + 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_optionParsedCountOptionJobs >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionJobs)) + # shellcheck disable=SC2034 + optionJobs="$1" + optionJobsCallback "${options_parse_arg}" "${optionJobs}" + ;; + # Option 2/20 + # Option optionProgressBar --bar|-b variableType Boolean min 0 max 1 authorizedValues '' regexp '' + --bar | -b) + # shellcheck disable=SC2034 + optionProgressBar="1" + if ((options_parse_optionParsedCountOptionProgressBar >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionProgressBar)) + optionProgressBarCallback "${options_parse_arg}" + ;; + # Option 3/20 + # Option optionFromDsn --from-dsn|-f variableType String min 0 max 1 authorizedValues '' regexp '' + --from-dsn | -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_optionParsedCountOptionFromDsn >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionFromDsn)) + # shellcheck disable=SC2034 + optionFromDsn="$1" + ;; + # Option 4/20 + # Option optionDatabases --database variableType StringArray min 0 max -1 authorizedValues '' regexp '' + --database) + shift + if (($# == 0)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - a value needs to be specified" + return 1 + fi + ((++options_parse_optionParsedCountOptionDatabases)) + optionDatabases+=("$1") + ;; + # Option 5/20 + # Option optionOutputDir --output|-o variableType String min 0 max 1 authorizedValues '' regexp '' + --output | -o) + 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_optionParsedCountOptionOutputDir >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionOutputDir)) + # shellcheck disable=SC2034 + optionOutputDir="$1" + outputDirectoryCallback "${options_parse_arg}" "${optionOutputDir}" + ;; + # Option 6/20 + # Option optionLogFormat --log-format|-l variableType String min 0 max 1 authorizedValues 'none|log' regexp '' + --log-format | -l) + shift + if (($# == 0)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - a value needs to be specified" + return 1 + fi + if [[ ! "$1" =~ none|log ]]; then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - value '$1' is not part of authorized values(none|log)" + return 1 + fi + if ((options_parse_optionParsedCountOptionLogFormat >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionLogFormat)) + # shellcheck disable=SC2034 + optionLogFormat="$1" + ;; + # Option 7/20 + # Option optionBashFrameworkConfig --bash-framework-config variableType String min 0 max 1 authorizedValues '' regexp '' + --bash-framework-config) + 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_optionParsedCountOptionBashFrameworkConfig >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 + optionBashFrameworkConfig="$1" + optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" + ;; + # Option 8/20 + # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' + --config) + # shellcheck disable=SC2034 + optionConfig="1" + if ((options_parse_optionParsedCountOptionConfig >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionConfig)) + ;; + # Option 9/20 + # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' + --verbose | -v) + # shellcheck disable=SC2034 + optionInfoVerbose="1" + if ((options_parse_optionParsedCountOptionInfoVerbose >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionInfoVerbose)) + optionInfoVerboseCallback "${options_parse_arg}" + updateArgListInfoVerboseCallback "${options_parse_arg}" + ;; + # Option 10/20 + # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' + -vv) + # shellcheck disable=SC2034 + optionDebugVerbose="1" + if ((options_parse_optionParsedCountOptionDebugVerbose >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionDebugVerbose)) + optionDebugVerboseCallback "${options_parse_arg}" + updateArgListDebugVerboseCallback "${options_parse_arg}" + ;; + # Option 11/20 + # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' + -vvv) + # shellcheck disable=SC2034 + optionTraceVerbose="1" + if ((options_parse_optionParsedCountOptionTraceVerbose >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionTraceVerbose)) + optionTraceVerboseCallback "${options_parse_arg}" + updateArgListTraceVerboseCallback "${options_parse_arg}" + ;; + # Option 12/20 + # Option optionEnvFiles --env-file variableType StringArray min 0 max -1 authorizedValues '' regexp '' + --env-file) + shift + if (($# == 0)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - a value needs to be specified" + return 1 + fi + ((++options_parse_optionParsedCountOptionEnvFiles)) + optionEnvFiles+=("$1") + optionEnvFileCallback "${options_parse_arg}" "${optionEnvFiles[@]}" + updateArgListEnvFileCallback "${options_parse_arg}" "${optionEnvFiles[@]}" + ;; + # Option 13/20 + # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' + --no-color) + # shellcheck disable=SC2034 + optionNoColor="1" + if ((options_parse_optionParsedCountOptionNoColor >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionNoColor)) + optionNoColorCallback "${options_parse_arg}" + updateArgListNoColorCallback "${options_parse_arg}" + ;; + # Option 14/20 + # Option optionTheme --theme variableType String min 0 max 1 authorizedValues 'default|default-force|noColor' regexp '' + --theme) + shift + if (($# == 0)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - a value needs to be specified" + return 1 + fi + if [[ ! "$1" =~ default|default-force|noColor ]]; then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - value '$1' is not part of authorized values(default|default-force|noColor)" + return 1 + fi + if ((options_parse_optionParsedCountOptionTheme >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 + optionTheme="$1" + optionThemeCallback "${options_parse_arg}" "${optionTheme}" + updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" + ;; + # Option 15/20 + # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' + --help | -h) + # shellcheck disable=SC2034 + optionHelp="1" + if ((options_parse_optionParsedCountOptionHelp >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionHelp)) + optionHelpCallback "${options_parse_arg}" + ;; + # Option 16/20 + # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' + --version) + # shellcheck disable=SC2034 + optionVersion="1" + if ((options_parse_optionParsedCountOptionVersion >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionVersion)) + optionVersionCallback "${options_parse_arg}" + ;; + # Option 17/20 + # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' + --quiet | -q) + # shellcheck disable=SC2034 + optionQuiet="1" + if ((options_parse_optionParsedCountOptionQuiet >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionQuiet)) + optionQuietCallback "${options_parse_arg}" + updateArgListQuietCallback "${options_parse_arg}" + ;; + # Option 18/20 + # Option optionLogLevel --log-level variableType String min 0 max 1 authorizedValues 'OFF|ERROR|WARNING|INFO|DEBUG|TRACE' regexp '' + --log-level) + shift + if (($# == 0)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - a value needs to be specified" + return 1 + fi + if [[ ! "$1" =~ OFF|ERROR|WARNING|INFO|DEBUG|TRACE ]]; then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - value '$1' is not part of authorized values(OFF|ERROR|WARNING|INFO|DEBUG|TRACE)" + return 1 + fi + if ((options_parse_optionParsedCountOptionLogLevel >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 + optionLogLevel="$1" + optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" + updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" + ;; + # Option 19/20 + # Option optionLogFile --log-file variableType String min 0 max 1 authorizedValues '' regexp '' + --log-file) + 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_optionParsedCountOptionLogFile >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 + optionLogFile="$1" + optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" + updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" + ;; + # Option 20/20 + # Option optionDisplayLevel --display-level variableType String min 0 max 1 authorizedValues 'OFF|ERROR|WARNING|INFO|DEBUG|TRACE' regexp '' + --display-level) + shift + if (($# == 0)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - a value needs to be specified" + return 1 + fi + if [[ ! "$1" =~ OFF|ERROR|WARNING|INFO|DEBUG|TRACE ]]; then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - value '$1' is not part of authorized values(OFF|ERROR|WARNING|INFO|DEBUG|TRACE)" + return 1 + fi + if ((options_parse_optionParsedCountOptionDisplayLevel >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 + optionDisplayLevel="$1" + optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" + updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" + ;; + -*) + if [[ "${argOptDefaultBehavior}" = "0" ]]; then + Log::displayError "Command ${SCRIPT_NAME} - Invalid option ${options_parse_arg}" + return 1 + fi + ;; + *) + if ((0)); then + # Technical if - never reached + : + # Argument 1/2 + # Argument argScriptToExecute min 1 max 1 authorizedValues '' regexp '' + elif ((options_parse_parsedArgIndex >= 0 && options_parse_parsedArgIndex < 1)); then + if ((options_parse_argParsedCountArgScriptToExecute >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Argument scriptToExecute - Maximum number of argument occurrences reached(1)" + return 1 + fi + ((++options_parse_argParsedCountArgScriptToExecute)) + # shellcheck disable=SC2034 + argScriptToExecute="${options_parse_arg}" + argScriptToExecuteCallback "${argScriptToExecute}" -- "${@:2}" + # Argument 2/2 + # Argument scriptArguments min 0 max -1 authorizedValues '' regexp '' + elif ((options_parse_parsedArgIndex >= 1)); then + ((++options_parse_argParsedCountScriptArguments)) + # shellcheck disable=SC2034 + scriptArguments+=("${options_parse_arg}") + else + if [[ "${argOptDefaultBehavior}" = "0" ]]; then + Log::displayError "Command ${SCRIPT_NAME} - Argument - too much arguments provided: $*" + return 1 + fi + fi + ((++options_parse_parsedArgIndex)) + ;; + esac + shift || true + done + if ((options_parse_argParsedCountArgScriptToExecute < 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Argument 'scriptToExecute' should be provided at least 1 time(s)" + return 1 + fi + commandOptionParseFinished + dbScriptAllDatabasesCommandCallback + 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 + echo -e "$(Array::wrap " " 80 0 "${__HELP_TITLE_COLOR}DESCRIPTION:${__RESET_COLOR}" "Allows to execute a script on each database of specified mysql server")" + echo + + echo -e "$(Array::wrap " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" "${SCRIPT_NAME}" "[OPTIONS]" "[ARGUMENTS]")" + echo -e "$(Array::wrap " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" \ + "${SCRIPT_NAME}" \ + "[--jobs|-j ]" "[--bar|-b]" "[--from-dsn|-f ]" "[--database ]" "[--output|-o ]" "[--log-format|-l ]" "[--bash-framework-config ]" "[--config]" "[--verbose|-v]" "[-vv]" "[-vvv]" "[--env-file ]" "[--no-color]" "[--theme ]" "[--help|-h]" "[--version]" "[--quiet|-q]" "[--log-level ]" "[--log-file ]" "[--display-level ]")" + echo + echo -e "${__HELP_TITLE_COLOR}ARGUMENTS:${__RESET_COLOR}" + echo -e " ${__HELP_OPTION_COLOR}scriptToExecute${__HELP_NORMAL} {single} (mandatory)" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(the\ script\ that\ will\ be\ executed\ on\ each\ databases) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " [${__HELP_OPTION_COLOR}scriptArguments${__HELP_NORMAL} {list} (optional)]" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(optional\ parameters\ to\ pass\ to\ the\ script) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo + echo -e "${__HELP_TITLE_COLOR}OPTIONS:${__RESET_COLOR}" + echo -e " ${__HELP_OPTION_COLOR}--jobs${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-j ${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(specify\ the\ number\ of\ db\ to\ query\ in\ parallel) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo ' Default value: 1' + echo -e " ${__HELP_OPTION_COLOR}--bar${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-b${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(Show\ progress\ as\ a\ progress\ bar.\ In\ the\ bar\ is\ shown:\ %\ of\ jobs\ completed\,\ estimated\ seconds\ left\,\ and\ number\ of\ jobs\ started.) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo + echo -e "${__HELP_TITLE_COLOR}SCRIPT OPTIONS:${__RESET_COLOR}" + echo -e " ${__HELP_OPTION_COLOR}--from-dsn${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-f ${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(target\ mysql\ server) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--database ${__HELP_NORMAL} {list} (optional)" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(if\ provided\ will\ check\ only\ this\ db\,\ otherwise\ script\ will\ be\ executed\ on\ all\ dbs\ of\ mysql\ server) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--output${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-o ${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(output\ directory\,\ see\ log-format\ option) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--log-format${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-l ${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(if\ output\ dir\ provided\,\ will\ log\ each\ db\ result\ to\ log\ file) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo ' Default value: none' + echo + echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(use\ alternate\ bash\ framework\ configuration.) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(Display\ configuration) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(Load\ the\ specified\ env\ file) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(choose\ color\ theme\ \(default\,\ default-force\ or\ noColor\)\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(Display\ this\ command\ help) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(Print\ version\ information\ and\ quit) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(Set\ log\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(Set\ log\ file) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054 + helpArray=(set\ display\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e """ +${__HELP_TITLE}NOTE:${__HELP_NORMAL} +the use of output, log-format, verbose options highly depends on the script used + +${__HELP_TITLE}LIST OF AVAILABLE DSN:${__HELP_NORMAL} +${dsnList} + +${__HELP_TITLE}DEFAULT QUERIES DIRECTORY:${__HELP_NORMAL} +${QUERIES_DIR-configuration error} -${__HELP_TITLE}Usage:${__HELP_NORMAL} ${SCRIPT_NAME} [-h|--help] prints this help and exits -${__HELP_TITLE}Usage:${__HELP_NORMAL} ${SCRIPT_NAME} [-j|--jobs ] [-o|--output ] [-d|--dsn ] [-v|--verbose] [-l|--log-format ] [--database ] [optional parameters to pass to the script] - the script that will be executed on each databases - -d|--dsn target mysql server (Default: ${DSN}) - --database if provided will check only this db, otherwise script will be executed on all dbs of mysql server - -j|--jobs the number of db to query in parallel (default: ${JOBS_NUMBER}) - -o|--output output directory, see log-format option (default : "${OUTPUT_DIR}") - -l|--log-format if log provided, will log each db result to log file, can be one of these values (none, log) (default: none) - -v|--verbose display more information +${__HELP_TITLE}USER QUERIES DIRECTORY:${__HELP_NORMAL} +${HOME_QUERIES_DIR-configuration error} +Allows to override queries defined in "Default queries directory" -${__HELP_TITLE}Note:${__HELP_NORMAL} the use of output, log-format, verbose options highly depends on the script used +${__HELP_TITLE}LIST OF AVAILABLE SCRIPTS:${__HELP_NORMAL} +${scriptsList} -${__HELP_TITLE}Example:${__HELP_NORMAL} script conf/dbScripts/extractData.sh +${__HELP_TITLE}EXAMPLES:${__HELP_NORMAL} script conf/dbScripts/extractData.sh executes query databaseSize (see conf/dbQueries/databaseSize.sql) on each db and log the result in log file in default output dir, call it using - $0 -j 10 extractData databaseSize + ${__HELP_EXAMPLE}$0 -j 10 extractData databaseSize${__HELP_NORMAL} executes query databaseSize on each db and display the result on stdout (2>/dev/null hides information messages) - $0 -j 10 --log-format none extractData databaseSize + ${__HELP_EXAMPLE}$0 -j 10 --log-format none extractData databaseSize${__HELP_NORMAL} use --verbose to get some debug information - $0 -j 10 --log-format none --verbose extractData databaseSize + ${__HELP_EXAMPLE}$0 -j 10 --log-format none --verbose extractData databaseSize${__HELP_NORMAL} -${__HELP_TITLE}Use cases:${__HELP_NORMAL} +${__HELP_TITLE}USE CASES:${__HELP_NORMAL} you can use this script in order to check that each db model conforms with your ORM schema simply create a new script in conf/dbQueries that will call your orm schema checker - update multiple db at once (simple to complex update script) - -${__HELP_TITLE}List of available dsn:${__HELP_NORMAL} -${dsnList} -${__HELP_TITLE}list of available scripts (${SCRIPTS_FOLDER}):${__HELP_NORMAL} -${scriptsList} + update multiple db at once (simple to complex update script)""" + echo + echo -n -e "${__HELP_TITLE_COLOR}VERSION: ${__RESET_COLOR}" + echo '2.0' + echo + echo -e "${__HELP_TITLE_COLOR}AUTHOR:${__RESET_COLOR}" + echo '[François Chastanet](https://github.com/fchastanet)' + echo + echo -e "${__HELP_TITLE_COLOR}SOURCE FILE:${__RESET_COLOR}" + echo 'https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/DbScriptAllDatabases/dbScriptAllDatabases.sh' + echo + echo -e "${__HELP_TITLE_COLOR}LICENSE:${__RESET_COLOR}" + echo 'MIT License' + echo + Array::wrap ' ' 76 4 "$(copyrightCallback)" + else + Log::displayError "Command ${SCRIPT_NAME} - Option command invalid: '${options_parse_cmd}'" + return 1 + fi +} -${__HELP_TITLE}Author:${__HELP_NORMAL} -[François Chastanet](https://github.com/fchastanet) +optionHelpCallback() { + local dsnList queriesList scriptsList + dsnList="$(Conf::getMergedList "dsn" "env")" + queriesList="$(Conf::getMergedList "dbQueries" "sql" || true)" + scriptsList="$(Conf::getMergedList "dbScripts" "sh")" -${__HELP_TITLE}Source file:${__HELP_NORMAL} -https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/DbScriptAllDatabases/dbScriptAllDatabases.sh + dbScriptAllDatabasesCommand help | envsubst + exit 0 +} -${__HELP_TITLE}License:${__HELP_NORMAL} -MIT License +outputDirectoryCallback() { + if [[ "${optionOutputDir:0:1}" != "/" ]]; then + # relative path + optionOutputDir="${PWD}/${optionOutputDir}" + fi + mkdir -p "${optionOutputDir}" || Log::fatal "unable to create directory ${optionOutputDir}" + if [[ ! -d "${optionOutputDir}" || ! -w "${optionOutputDir}" ]]; then + Log::fatal "output dir is not correct or not writable" + fi +} -Copyright (c) 2022 François Chastanet -EOF +argScriptToExecuteCallback() { + if [[ ! -f "${argScriptToExecute}" ]]; then + declare scriptAbsoluteFile + scriptAbsoluteFile="$(Conf::getAbsoluteFile "dbScripts" "${argScriptToExecute}" "sh")" && { + argScriptToExecute="${scriptAbsoluteFile}" + if (( BASH_FRAMEWORK_ARGS_VERBOSE >= __VERBOSE_LEVEL_DEBUG )); then + Log::displayInfo "Using script file ${scriptAbsoluteFile}" + fi + } + fi } -# read command parameters -# $@ is all command line parameters passed to the script. -# -o is for short options like -h -# -l is for long options with double dash like --help -# the comma separates different long options -options=$(getopt -l help,log-format:,dsn:,jobs:,database:,output:,verbose -o hd:j:l:o:v -- "$@" 2>/dev/null) || { - showHelp - Log::fatal "invalid options specified" +dbScriptAllDatabasesCommandCallback() { + if [[ -z "${optionFromDsn}" ]]; then + # default value for FROM_DSN if from-aws not set + optionFromDsn="default.remote" + fi } -eval set -- "${options}" -while true; do - case $1 in - -h | --help) - showHelp - exit 0 - ;; - --jobs | -j) - shift || true - JOBS_NUMBER=$1 - ;; - --output | -o) - shift || true - OUTPUT_DIR="$1" - ;; - --dsn | -d) - shift || true - DSN=${1:-:-default.local} - ;; - --database) - shift || true - DB_NAME="$1" - ;; - --log-format | -l) - shift || true - LOG_FORMAT="$1" - ;; - --) - shift || true - break - ;; - *) - showHelp - Log::fatal "invalid argument $1" - ;; - esac - shift || true -done +dbScriptAllDatabasesCommand parse "${BASH_FRAMEWORK_ARGV[@]}" -# check dependencies -Assert::commandExists mysql "sudo apt-get install -y mysql-client" -Assert::commandExists mysqlshow "sudo apt-get install -y mysql-client" -Assert::commandExists parallel "sudo apt-get install -y parallel" +# @require Linux::requireExecutedAsUser +run() { -# output-dir argument -if ! Array::contains "${LOG_FORMAT}" "none" "log"; then - Log::fatal "log format '${LOG_FORMAT}' not supported" -fi + # check dependencies + Assert::commandExists mysql "sudo apt-get install -y mysql-client" + Assert::commandExists mysqlshow "sudo apt-get install -y mysql-client" + Assert::commandExists parallel "sudo apt-get install -y parallel" -# additional arguments -shift $((OPTIND - 1)) || true -SCRIPT="$1" -shift || true -if [[ -z "${SCRIPT}" ]]; then - Log::fatal "You must provide the script file to be executed" -fi + # create db instance + declare -Agx dbInstance + Database::newInstance dbInstance "${optionFromDsn}" + Database::setQueryOptions dbInstance "${dbInstance['QUERY_OPTIONS']} --connect-timeout=5" + if ((BASH_FRAMEWORK_ARGS_VERBOSE >= __VERBOSE_LEVEL_DEBUG)); then + Log::displayInfo "Using dsn ${dbInstance['DSN_FILE']}" + fi -if [[ "${OUTPUT_DIR:0:1}" != "/" ]]; then - # relative path - OUTPUT_DIR="${PWD}/${OUTPUT_DIR}" -fi -mkdir -p "${OUTPUT_DIR}" || Log::fatal "unable to create directory ${OUTPUT_DIR}" -[[ -d "${OUTPUT_DIR}" && -w "${OUTPUT_DIR}" ]] || - Log::fatal "output dir is not correct or not writable" + # list of all databases + if ((BASH_FRAMEWORK_ARGS_VERBOSE >= __VERBOSE_LEVEL_DEBUG)); then + Log::displayInfo "get the list of all databases" + fi -if ! [[ ${JOBS_NUMBER} =~ ^[0-9]+$ ]]; then - Log::fatal "number of jobs is incorrect" -fi -[[ ${JOBS_NUMBER} -lt 1 ]] && Log::fatal "number of jobs must be greater than 0" - -# try script inside script folder -SCRIPT="$(Conf::getAbsoluteFile "dbScripts" "${SCRIPT}" "sh")" || exit 1 -[[ "${ARGS_VERBOSE}" = "1" ]] && Log::displayInfo "Using script ${SCRIPT}" -# create db instance -declare -Agx dbInstance - -Database::newInstance dbInstance "${DSN}" -Database::setQueryOptions dbInstance "${dbInstance['QUERY_OPTIONS']} --connect-timeout=5" -[[ "${ARGS_VERBOSE}" = "1" ]] && Log::displayInfo "Using dsn ${dbInstance['DSN_FILE']}" - -# list of all databases -[[ "${ARGS_VERBOSE}" = "1" ]] && Log::displayInfo "get the list of all databases" -if [[ -z "${DB_NAME}" ]]; then - allDbs="$(Database::getUserDbList dbInstance)" + if ((${#optionDatabases[@]} == 0)); then + mapfile -t optionDatabases < <(Database::getUserDbList dbInstance) + fi + + if ((BASH_FRAMEWORK_ARGS_VERBOSE >= __VERBOSE_LEVEL_DEBUG)); then + Log::displayInfo "processing ${#optionDatabases[@]} databases using ${optionJobs} jobs" + fi + + export selectedQueryFile + export MYSQL_OPTIONS + + printf '%s\n' "${optionDatabases[@]}" | parallel --eta --progress --tag --jobs="${optionJobs}" \ + "${argScriptToExecute}" "${optionFromDsn}" "${optionLogFormat}" "${BASH_FRAMEWORK_ARGS_VERBOSE}" \ + "${optionOutputDir}" "${PWD}" "${scriptArguments[@]}" +} + +if [[ "${BASH_FRAMEWORK_QUIET_MODE:-0}" = "1" ]]; then + run &>/dev/null else - allDbs="${DB_NAME}" + run fi -[[ "${ARGS_VERBOSE}" = "1" ]] && Log::displayInfo "processing $(echo "${allDbs}" | wc -l) databases using ${JOBS_NUMBER} jobs" - -export selectedQueryFile -export MYSQL_OPTIONS +} -echo "${allDbs}" | parallel --eta --progress --tag --jobs="${JOBS_NUMBER}" \ - "${SCRIPT}" "${DSN}" "${LOG_FORMAT}" "${ARGS_VERBOSE}" \ - "${OUTPUT_DIR}" "${PWD}" "$@" +facade_main_18f5ddcf2cbe4feba7e4cda5e715c153 "$@" diff --git a/bin/doc b/bin/doc index df8714de..be641d9d 100755 --- a/bin/doc +++ b/bin/doc @@ -47,6 +47,7 @@ declare -a BASH_FRAMEWORK_ARGV=("$@") declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") # @see https://unix.stackexchange.com/a/386856 +# shellcheck disable=SC2317 interruptManagement() { # restore SIGINT handler trap - INT @@ -76,6 +77,7 @@ TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX) export TMPDIR # temp dir cleaning +# shellcheck disable=SC2317 cleanOnExit() { if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" @@ -134,7 +136,7 @@ Array::wrap() { local -i argNoAnsiLength=0 while (($# > 0)); do - argNoAnsi="$(echo "${arg}" | Filters::removeAnsiCodes)" + argNoAnsi="$(echo "${arg%%*( )}" | Filters::removeAnsiCodes)" ((argNoAnsiLength = ${#argNoAnsi})) || true if (($# < 1 && argNoAnsiLength == 0)); then break @@ -157,7 +159,7 @@ Array::wrap() { if ((currentLineLength == 0 && firstLine == 0)); then echo -n "${indentStr}" fi - echo -e -n "${arg}" + echo -e -n "${arg}" | sed 's/[\t ]*$//g' needEcho="1" ((currentLineLength += argNoAnsiLength)) ((glueLength = ${#glue})) || true @@ -176,7 +178,7 @@ Array::wrap() { fi local -i length ((length = maxLineLength - currentLineLength)) || true - echo -e "${arg:0:${length}}" + echo -e "${arg:0:${length}}" | sed 's/[\t ]*$//g' ((currentLineLength = 0)) || true ((glueLength = 0)) || true arg="${arg:${length}}" @@ -184,7 +186,7 @@ Array::wrap() { else # arg cannot be stored on a whole line, so we add it on next line as a whole echo - echo -e -n "${indentStr}${arg}" + echo -e -n "${indentStr}${arg}" | sed 's/[\t ]*$//g' ((glueLength = ${#glue})) || true ((currentLineLength = argNoAnsiLength)) arg="" # allows to go to next arg @@ -312,6 +314,15 @@ Log::displayStatus() { Log::logStatus "$1" "${type}" } +# @description Display message using warning color (yellow) +# @arg $1 message:String the message to display +Log::displayWarning() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then + echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 + fi + Log::logWarning "$1" +} + # @description Display message using error color (red) and exit immediately with error status 1 # @arg $1 message:String the message to display Log::fatal() { @@ -474,6 +485,7 @@ UI::drawLine() { # @set __HELP_EXAMPLE String to remove # @set __HELP_TITLE String to remove # @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 UI::theme() { local theme="${1-default}" if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then @@ -489,46 +501,46 @@ UI::theme() { ;; esac if [[ "${theme}" = "default" ]]; then - export BASH_FRAMEWORK_THEME="default" + BASH_FRAMEWORK_THEME="default" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __SKIPPED_COLOR='\e[33m' # Yellow - export __DEBUG_COLOR='\e[37m' # Grey - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __HELP_TITLE_COLOR="\e[1;37m" # Bold - export __HELP_OPTION_COLOR="\e[1;34m" # Blue + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color + __RESET_COLOR='\e[0m' # Reset Color # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" + __HELP_TITLE="$(echo -e "\e[1;37m")" # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" + __HELP_NORMAL="$(echo -e "\033[0m")" else - export BASH_FRAMEWORK_THEME="noColor" + BASH_FRAMEWORK_THEME="noColor" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __DEBUG_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __HELP_TITLE_COLOR='' - export __HELP_OPTION_COLOR='' + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' fi } @@ -545,8 +557,8 @@ Compiler::Facade::requireCommandBinDir() { # @description check if tty (interactive mode) is active # @noargs # @exitcode 1 if tty not active -# @environment NON_INTERACTIVE if 1 consider as not interactive even if environement is interactive -# @environment INTERACTIVE if 1 consider as interactive even if environement is not interactive +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive # @stderr diagnostic information + help if second argument is provided Assert::tty() { if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then @@ -731,15 +743,6 @@ Log::displaySkipped() { Log::logSkipped "$1" } -# @description Display message using warning color (yellow) -# @arg $1 message:String the message to display -Log::displayWarning() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then - echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 - fi - Log::logWarning "$1" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -805,6 +808,14 @@ Log::logStatus() { fi } +# @description log message to file +# @arg $1 message:String the message to display +Log::logWarning() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then + Log::logMessage "${2:-WARNING}" "$1" + fi +} + # @description To be called before logging in the log file # @arg $1 file:string log file name # @arg $2 maxLogFilesCount:int maximum number of log files @@ -911,14 +922,6 @@ Log::logSkipped() { fi } -# @description log message to file -# @arg $1 message:String the message to display -Log::logWarning() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then - Log::logMessage "${2:-WARNING}" "$1" - fi -} - # @description check if an element is contained in an array # # @arg $1 needle:String @@ -960,18 +963,19 @@ Log::requireLoad Compiler::Facade::requireCommandBinDir # @require Compiler::Facade::requireCommandBinDir +# shellcheck disable=SC2034 DOC_DIR="${BASH_TOOLS_ROOT_DIR}/pages" +declare copyrightBeginYear="2020" +declare -a RUN_CONTAINER_ARGV_FILTERED=() declare -a BASH_FRAMEWORK_ARGV_FILTERED=() copyrightCallback() { - local years - years="$(date +%Y)" - if [[ -n "${copyrightBeginYear}" && "${copyrightBeginYear}" != "${years}" ]]; then - years="${copyrightBeginYear}-${years}" + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" fi - echo "Copyright (c) ${years} François Chastanet" + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" } # shellcheck disable=SC2317 # if function is overridden @@ -1145,6 +1149,29 @@ optionBashFrameworkConfigCallback() { fi } +defaultFrameworkConfig="$( + cat <<'EOF' +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$" +# Source directories +FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" +) + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +EOF +)" + commandOptionParseFinished() { if [[ -z "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then BASH_FRAMEWORK_ENV_FILES=() @@ -1164,8 +1191,16 @@ commandOptionParseFinished() { # shellcheck disable=SC2034 BASH_FRAMEWORK_CONFIG_FILE="" # shellcheck source=/.framework-config - Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || - Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config file" + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || { + # load default template framework config + if [[ ! -f "${PERSISTENT_TMPDIR}/.framework-config" ]]; then + echo "${defaultFrameworkConfig}" > "${PERSISTENT_TMPDIR}/.framework-config" + fi + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${PERSISTENT_TMPDIR}" || { + Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config.default file" + } + Log::displayWarning "Command ${SCRIPT_NAME} - Load default .framework-config file - ${PERSISTENT_TMPDIR}/.framework-config" + } fi if [[ "${optionConfig}" = "1" ]]; then @@ -1222,6 +1257,7 @@ docCommand() { ((options_parse_optionParsedCountOptionLogFile = 0)) || true local -i options_parse_optionParsedCountOptionDisplayLevel ((options_parse_optionParsedCountOptionDisplayLevel = 0)) || true + # shellcheck disable=SC2034 local -i options_parse_parsedArgIndex=0 while (($# > 0)); do local options_parse_arg="$1" @@ -1230,6 +1266,7 @@ docCommand() { # Option 1/15 # Option optionSkipDockerBuild --skip-docker-build variableType Boolean min 0 max 1 authorizedValues '' regexp '' --skip-docker-build) + # shellcheck disable=SC2034 optionSkipDockerBuild="1" if ((options_parse_optionParsedCountOptionSkipDockerBuild >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1251,12 +1288,14 @@ docCommand() { return 1 fi ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 optionBashFrameworkConfig="$1" optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" ;; # Option 3/15 # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' --config) + # shellcheck disable=SC2034 optionConfig="1" if ((options_parse_optionParsedCountOptionConfig >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1267,6 +1306,7 @@ docCommand() { # Option 4/15 # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' --verbose | -v) + # shellcheck disable=SC2034 optionInfoVerbose="1" if ((options_parse_optionParsedCountOptionInfoVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1279,6 +1319,7 @@ docCommand() { # Option 5/15 # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vv) + # shellcheck disable=SC2034 optionDebugVerbose="1" if ((options_parse_optionParsedCountOptionDebugVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1291,6 +1332,7 @@ docCommand() { # Option 6/15 # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vvv) + # shellcheck disable=SC2034 optionTraceVerbose="1" if ((options_parse_optionParsedCountOptionTraceVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1316,6 +1358,7 @@ docCommand() { # Option 8/15 # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' --no-color) + # shellcheck disable=SC2034 optionNoColor="1" if ((options_parse_optionParsedCountOptionNoColor >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1342,6 +1385,7 @@ docCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 optionTheme="$1" optionThemeCallback "${options_parse_arg}" "${optionTheme}" updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" @@ -1349,6 +1393,7 @@ docCommand() { # Option 10/15 # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' --help | -h) + # shellcheck disable=SC2034 optionHelp="1" if ((options_parse_optionParsedCountOptionHelp >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1360,6 +1405,7 @@ docCommand() { # Option 11/15 # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' --version) + # shellcheck disable=SC2034 optionVersion="1" if ((options_parse_optionParsedCountOptionVersion >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1371,6 +1417,7 @@ docCommand() { # Option 12/15 # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' --quiet | -q) + # shellcheck disable=SC2034 optionQuiet="1" if ((options_parse_optionParsedCountOptionQuiet >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1397,6 +1444,7 @@ docCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 optionLogLevel="$1" optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" @@ -1414,6 +1462,7 @@ docCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 optionLogFile="$1" optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" @@ -1435,6 +1484,7 @@ docCommand() { return 1 fi ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 optionDisplayLevel="$1" optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" @@ -1467,66 +1517,81 @@ docCommand() { "[--skip-docker-build]" "[--bash-framework-config ]" "[--config]" "[--verbose|-v]" "[-vv]" "[-vvv]" "[--env-file ]" "[--no-color]" "[--theme ]" "[--help|-h]" "[--version]" "[--quiet|-q]" "[--log-level ]" "[--log-file ]" "[--display-level ]")" echo echo -e "${__HELP_TITLE_COLOR}OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--skip-docker-build${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--skip-docker-build${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(skip\ docker\ image\ build\ if\ option\ provided) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(use\ alternate\ bash\ framework\ configuration.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ configuration) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} (optional)" + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Load\ the\ specified\ env\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(choose\ color\ theme\ \(default\,\ default-force\ or\ noColor\)\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ this\ command\ help) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Print\ version\ information\ and\ quit) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(set\ display\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo @@ -1548,8 +1613,7 @@ docCommand() { return 1 fi } -declare copyrightBeginYear="2020" -declare -a RUN_CONTAINER_ARGV_FILTERED=() + updateOptionSkipDockerBuildCallback() { if [[ "${IN_BASH_DOCKER:-}" != "You're in docker" ]]; then BASH_FRAMEWORK_ARGV_FILTERED+=("$1") @@ -1572,8 +1636,6 @@ updateArgListTraceVerboseCallback() { BASH_FRAMEWORK_ARGV_FILTERED+=(-vvv) } -docCommand parse "${BASH_FRAMEWORK_ARGV[@]}" - run() { if [[ "${IN_BASH_DOCKER:-}" != "You're in docker" ]]; then DOCKER_RUN_OPTIONS=$"-e ORIGINAL_DOC_DIR=${DOC_DIR}" \ @@ -1609,7 +1671,7 @@ run() { "${DOC_DIR}/Commands.md" \ "${FRAMEWORK_BIN_DIR}" \ TOKEN_NOT_FOUND_COUNT \ - '(bash-tpl|plantuml|definitionLint|compile)$' + '(bash-tpl|plantuml|definitionLint|compile|installFacadeExample)$' # inject plantuml diagram source code into command sed -E -i \ diff --git a/bin/gitIsAncestorOf b/bin/gitIsAncestorOf index b15c7df3..15206ac2 100755 --- a/bin/gitIsAncestorOf +++ b/bin/gitIsAncestorOf @@ -47,6 +47,7 @@ declare -a BASH_FRAMEWORK_ARGV=("$@") declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") # @see https://unix.stackexchange.com/a/386856 +# shellcheck disable=SC2317 interruptManagement() { # restore SIGINT handler trap - INT @@ -76,6 +77,7 @@ TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX) export TMPDIR # temp dir cleaning +# shellcheck disable=SC2317 cleanOnExit() { if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" @@ -134,7 +136,7 @@ Array::wrap() { local -i argNoAnsiLength=0 while (($# > 0)); do - argNoAnsi="$(echo "${arg}" | Filters::removeAnsiCodes)" + argNoAnsi="$(echo "${arg%%*( )}" | Filters::removeAnsiCodes)" ((argNoAnsiLength = ${#argNoAnsi})) || true if (($# < 1 && argNoAnsiLength == 0)); then break @@ -157,7 +159,7 @@ Array::wrap() { if ((currentLineLength == 0 && firstLine == 0)); then echo -n "${indentStr}" fi - echo -e -n "${arg}" + echo -e -n "${arg}" | sed 's/[\t ]*$//g' needEcho="1" ((currentLineLength += argNoAnsiLength)) ((glueLength = ${#glue})) || true @@ -176,7 +178,7 @@ Array::wrap() { fi local -i length ((length = maxLineLength - currentLineLength)) || true - echo -e "${arg:0:${length}}" + echo -e "${arg:0:${length}}" | sed 's/[\t ]*$//g' ((currentLineLength = 0)) || true ((glueLength = 0)) || true arg="${arg:${length}}" @@ -184,7 +186,7 @@ Array::wrap() { else # arg cannot be stored on a whole line, so we add it on next line as a whole echo - echo -e -n "${indentStr}${arg}" + echo -e -n "${indentStr}${arg}" | sed 's/[\t ]*$//g' ((glueLength = ${#glue})) || true ((currentLineLength = argNoAnsiLength)) arg="" # allows to go to next arg @@ -302,6 +304,15 @@ Log::displayInfo() { Log::logInfo "$1" "${type}" } +# @description Display message using warning color (yellow) +# @arg $1 message:String the message to display +Log::displayWarning() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then + echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 + fi + Log::logWarning "$1" +} + # @description Display message using error color (red) and exit immediately with error status 1 # @arg $1 message:String the message to display Log::fatal() { @@ -383,6 +394,7 @@ UI::drawLine() { # @set __HELP_EXAMPLE String to remove # @set __HELP_TITLE String to remove # @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 UI::theme() { local theme="${1-default}" if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then @@ -398,46 +410,46 @@ UI::theme() { ;; esac if [[ "${theme}" = "default" ]]; then - export BASH_FRAMEWORK_THEME="default" + BASH_FRAMEWORK_THEME="default" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __SKIPPED_COLOR='\e[33m' # Yellow - export __DEBUG_COLOR='\e[37m' # Grey - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __HELP_TITLE_COLOR="\e[1;37m" # Bold - export __HELP_OPTION_COLOR="\e[1;34m" # Blue + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color + __RESET_COLOR='\e[0m' # Reset Color # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" + __HELP_TITLE="$(echo -e "\e[1;37m")" # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" + __HELP_NORMAL="$(echo -e "\033[0m")" else - export BASH_FRAMEWORK_THEME="noColor" + BASH_FRAMEWORK_THEME="noColor" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __DEBUG_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __HELP_TITLE_COLOR='' - export __HELP_OPTION_COLOR='' + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' fi } @@ -463,8 +475,8 @@ Linux::requireExecutedAsUser() { # @description check if tty (interactive mode) is active # @noargs # @exitcode 1 if tty not active -# @environment NON_INTERACTIVE if 1 consider as not interactive even if environement is interactive -# @environment INTERACTIVE if 1 consider as interactive even if environement is not interactive +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive # @stderr diagnostic information + help if second argument is provided Assert::tty() { if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then @@ -683,6 +695,14 @@ Log::logMessage() { fi } +# @description log message to file +# @arg $1 message:String the message to display +Log::logWarning() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then + Log::logMessage "${2:-WARNING}" "$1" + fi +} + # @description To be called before logging in the log file # @arg $1 file:string log file name # @arg $2 maxLogFilesCount:int maximum number of log files @@ -781,15 +801,6 @@ Framework::createTempFile() { mktemp -p "${TMPDIR:-/tmp}" -t "${1:-}.XXXXXXXXXXXX" } -# @description Display message using warning color (yellow) -# @arg $1 message:String the message to display -Log::displayWarning() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then - echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 - fi - Log::logWarning "$1" -} - # @description log message to file # @arg $1 message:String the message to display Log::logSkipped() { @@ -814,14 +825,6 @@ Array::contains() { return 1 } -# @description log message to file -# @arg $1 message:String the message to display -Log::logWarning() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then - Log::logMessage "${2:-WARNING}" "$1" - fi -} - # FUNCTIONS facade_main_8f16db2b392c4f27975664b203757d91() { @@ -848,16 +851,19 @@ Compiler::Facade::requireCommandBinDir Linux::requireExecutedAsUser # @require Compiler::Facade::requireCommandBinDir +# shellcheck disable=SC2034 + +declare copyrightBeginYear="2020" +declare claimedBranchArg="" +declare commitArg="" declare -a BASH_FRAMEWORK_ARGV_FILTERED=() copyrightCallback() { - local years - years="$(date +%Y)" - if [[ -n "${copyrightBeginYear}" && "${copyrightBeginYear}" != "${years}" ]]; then - years="${copyrightBeginYear}-${years}" + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" fi - echo "Copyright (c) ${years} François Chastanet" + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" } # shellcheck disable=SC2317 # if function is overridden @@ -1031,6 +1037,29 @@ optionBashFrameworkConfigCallback() { fi } +defaultFrameworkConfig="$( + cat <<'EOF' +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$" +# Source directories +FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" +) + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +EOF +)" + commandOptionParseFinished() { if [[ -z "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then BASH_FRAMEWORK_ENV_FILES=() @@ -1050,8 +1079,16 @@ commandOptionParseFinished() { # shellcheck disable=SC2034 BASH_FRAMEWORK_CONFIG_FILE="" # shellcheck source=/.framework-config - Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || - Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config file" + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || { + # load default template framework config + if [[ ! -f "${PERSISTENT_TMPDIR}/.framework-config" ]]; then + echo "${defaultFrameworkConfig}" > "${PERSISTENT_TMPDIR}/.framework-config" + fi + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${PERSISTENT_TMPDIR}" || { + Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config.default file" + } + Log::displayWarning "Command ${SCRIPT_NAME} - Load default .framework-config file - ${PERSISTENT_TMPDIR}/.framework-config" + } fi if [[ "${optionConfig}" = "1" ]]; then @@ -1102,6 +1139,7 @@ gitIsAncestorOfCommand() { ((options_parse_argParsedCountClaimedBranchArg = 0)) || true local -i options_parse_argParsedCountCommitArg ((options_parse_argParsedCountCommitArg = 0)) || true + # shellcheck disable=SC2034 local -i options_parse_parsedArgIndex=0 while (($# > 0)); do local options_parse_arg="$1" @@ -1120,12 +1158,14 @@ gitIsAncestorOfCommand() { return 1 fi ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 optionBashFrameworkConfig="$1" optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" ;; # Option 2/14 # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' --config) + # shellcheck disable=SC2034 optionConfig="1" if ((options_parse_optionParsedCountOptionConfig >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1136,6 +1176,7 @@ gitIsAncestorOfCommand() { # Option 3/14 # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' --verbose | -v) + # shellcheck disable=SC2034 optionInfoVerbose="1" if ((options_parse_optionParsedCountOptionInfoVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1148,6 +1189,7 @@ gitIsAncestorOfCommand() { # Option 4/14 # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vv) + # shellcheck disable=SC2034 optionDebugVerbose="1" if ((options_parse_optionParsedCountOptionDebugVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1160,6 +1202,7 @@ gitIsAncestorOfCommand() { # Option 5/14 # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vvv) + # shellcheck disable=SC2034 optionTraceVerbose="1" if ((options_parse_optionParsedCountOptionTraceVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1185,6 +1228,7 @@ gitIsAncestorOfCommand() { # Option 7/14 # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' --no-color) + # shellcheck disable=SC2034 optionNoColor="1" if ((options_parse_optionParsedCountOptionNoColor >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1211,6 +1255,7 @@ gitIsAncestorOfCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 optionTheme="$1" optionThemeCallback "${options_parse_arg}" "${optionTheme}" updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" @@ -1218,6 +1263,7 @@ gitIsAncestorOfCommand() { # Option 9/14 # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' --help | -h) + # shellcheck disable=SC2034 optionHelp="1" if ((options_parse_optionParsedCountOptionHelp >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1229,6 +1275,7 @@ gitIsAncestorOfCommand() { # Option 10/14 # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' --version) + # shellcheck disable=SC2034 optionVersion="1" if ((options_parse_optionParsedCountOptionVersion >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1240,6 +1287,7 @@ gitIsAncestorOfCommand() { # Option 11/14 # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' --quiet | -q) + # shellcheck disable=SC2034 optionQuiet="1" if ((options_parse_optionParsedCountOptionQuiet >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1266,6 +1314,7 @@ gitIsAncestorOfCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 optionLogLevel="$1" optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" @@ -1283,6 +1332,7 @@ gitIsAncestorOfCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 optionLogFile="$1" optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" @@ -1304,6 +1354,7 @@ gitIsAncestorOfCommand() { return 1 fi ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 optionDisplayLevel="$1" optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" @@ -1326,6 +1377,7 @@ gitIsAncestorOfCommand() { return 1 fi ((++options_parse_argParsedCountClaimedBranchArg)) + # shellcheck disable=SC2034 claimedBranchArg="${options_parse_arg}" # Argument 2/2 # Argument commitArg min 1 max 1 authorizedValues '' regexp '' @@ -1335,6 +1387,7 @@ gitIsAncestorOfCommand() { return 1 fi ((++options_parse_argParsedCountCommitArg)) + # shellcheck disable=SC2034 commitArg="${options_parse_arg}" else if [[ "${argOptDefaultBehavior}" = "0" ]]; then @@ -1370,68 +1423,84 @@ gitIsAncestorOfCommand() { echo -e "${__HELP_TITLE_COLOR}ARGUMENTS:${__RESET_COLOR}" echo -e " ${__HELP_OPTION_COLOR}claimedBranch${__HELP_NORMAL} {single} (mandatory)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(the\ branch\ in\ which\ the\ commit\ will\ be\ searched) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}commit${__HELP_NORMAL} {single} (mandatory)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(the\ commit\ oid\ to\ check) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(use\ alternate\ bash\ framework\ configuration.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ configuration) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} (optional)" + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Load\ the\ specified\ env\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(choose\ color\ theme\ \(default\,\ default-force\ or\ noColor\)\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ this\ command\ help) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Print\ version\ information\ and\ quit) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(set\ display\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e """ @@ -1457,9 +1526,6 @@ ${__HELP_OPTION_COLOR}2${__HELP_NORMAL}: if commit is not included in given bran return 1 fi } -declare copyrightBeginYear="2020" -declare claimedBranchArg="" -declare commitArg="" gitIsAncestorOfCommand parse "${BASH_FRAMEWORK_ARGV[@]}" @@ -1471,7 +1537,6 @@ run() { exit 1 fi - # shellcheck disable=SC2154 merge_base="$(git merge-base "${commitArg}" "${claimedBranchArg}")" if [[ -z "${merge_base}" || "${merge_base}" != "$(git rev-parse --verify "${commitArg}")" ]]; then Log::displayError "Commit ${commitArg} is not an ancestor of branch ${claimedBranchArg}" diff --git a/bin/gitIsBranch b/bin/gitIsBranch index e720fe59..8295f9de 100755 --- a/bin/gitIsBranch +++ b/bin/gitIsBranch @@ -47,6 +47,7 @@ declare -a BASH_FRAMEWORK_ARGV=("$@") declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") # @see https://unix.stackexchange.com/a/386856 +# shellcheck disable=SC2317 interruptManagement() { # restore SIGINT handler trap - INT @@ -76,6 +77,7 @@ TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX) export TMPDIR # temp dir cleaning +# shellcheck disable=SC2317 cleanOnExit() { if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" @@ -134,7 +136,7 @@ Array::wrap() { local -i argNoAnsiLength=0 while (($# > 0)); do - argNoAnsi="$(echo "${arg}" | Filters::removeAnsiCodes)" + argNoAnsi="$(echo "${arg%%*( )}" | Filters::removeAnsiCodes)" ((argNoAnsiLength = ${#argNoAnsi})) || true if (($# < 1 && argNoAnsiLength == 0)); then break @@ -157,7 +159,7 @@ Array::wrap() { if ((currentLineLength == 0 && firstLine == 0)); then echo -n "${indentStr}" fi - echo -e -n "${arg}" + echo -e -n "${arg}" | sed 's/[\t ]*$//g' needEcho="1" ((currentLineLength += argNoAnsiLength)) ((glueLength = ${#glue})) || true @@ -176,7 +178,7 @@ Array::wrap() { fi local -i length ((length = maxLineLength - currentLineLength)) || true - echo -e "${arg:0:${length}}" + echo -e "${arg:0:${length}}" | sed 's/[\t ]*$//g' ((currentLineLength = 0)) || true ((glueLength = 0)) || true arg="${arg:${length}}" @@ -184,7 +186,7 @@ Array::wrap() { else # arg cannot be stored on a whole line, so we add it on next line as a whole echo - echo -e -n "${indentStr}${arg}" + echo -e -n "${indentStr}${arg}" | sed 's/[\t ]*$//g' ((glueLength = ${#glue})) || true ((currentLineLength = argNoAnsiLength)) arg="" # allows to go to next arg @@ -302,6 +304,15 @@ Log::displayInfo() { Log::logInfo "$1" "${type}" } +# @description Display message using warning color (yellow) +# @arg $1 message:String the message to display +Log::displayWarning() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then + echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 + fi + Log::logWarning "$1" +} + # @description Display message using error color (red) and exit immediately with error status 1 # @arg $1 message:String the message to display Log::fatal() { @@ -383,6 +394,7 @@ UI::drawLine() { # @set __HELP_EXAMPLE String to remove # @set __HELP_TITLE String to remove # @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 UI::theme() { local theme="${1-default}" if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then @@ -398,46 +410,46 @@ UI::theme() { ;; esac if [[ "${theme}" = "default" ]]; then - export BASH_FRAMEWORK_THEME="default" + BASH_FRAMEWORK_THEME="default" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __SKIPPED_COLOR='\e[33m' # Yellow - export __DEBUG_COLOR='\e[37m' # Grey - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __HELP_TITLE_COLOR="\e[1;37m" # Bold - export __HELP_OPTION_COLOR="\e[1;34m" # Blue + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color + __RESET_COLOR='\e[0m' # Reset Color # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" + __HELP_TITLE="$(echo -e "\e[1;37m")" # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" + __HELP_NORMAL="$(echo -e "\033[0m")" else - export BASH_FRAMEWORK_THEME="noColor" + BASH_FRAMEWORK_THEME="noColor" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __DEBUG_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __HELP_TITLE_COLOR='' - export __HELP_OPTION_COLOR='' + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' fi } @@ -463,8 +475,8 @@ Linux::requireExecutedAsUser() { # @description check if tty (interactive mode) is active # @noargs # @exitcode 1 if tty not active -# @environment NON_INTERACTIVE if 1 consider as not interactive even if environement is interactive -# @environment INTERACTIVE if 1 consider as interactive even if environement is not interactive +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive # @stderr diagnostic information + help if second argument is provided Assert::tty() { if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then @@ -683,6 +695,14 @@ Log::logMessage() { fi } +# @description log message to file +# @arg $1 message:String the message to display +Log::logWarning() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then + Log::logMessage "${2:-WARNING}" "$1" + fi +} + # @description To be called before logging in the log file # @arg $1 file:string log file name # @arg $2 maxLogFilesCount:int maximum number of log files @@ -781,15 +801,6 @@ Framework::createTempFile() { mktemp -p "${TMPDIR:-/tmp}" -t "${1:-}.XXXXXXXXXXXX" } -# @description Display message using warning color (yellow) -# @arg $1 message:String the message to display -Log::displayWarning() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then - echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 - fi - Log::logWarning "$1" -} - # @description log message to file # @arg $1 message:String the message to display Log::logSkipped() { @@ -814,14 +825,6 @@ Array::contains() { return 1 } -# @description log message to file -# @arg $1 message:String the message to display -Log::logWarning() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then - Log::logMessage "${2:-WARNING}" "$1" - fi -} - # FUNCTIONS facade_main_5c164a34fb6c464a90667d094e1f3bef() { @@ -848,16 +851,18 @@ Compiler::Facade::requireCommandBinDir Linux::requireExecutedAsUser # @require Compiler::Facade::requireCommandBinDir +# shellcheck disable=SC2034 + +declare copyrightBeginYear="2020" +declare branchNameArg="" declare -a BASH_FRAMEWORK_ARGV_FILTERED=() copyrightCallback() { - local years - years="$(date +%Y)" - if [[ -n "${copyrightBeginYear}" && "${copyrightBeginYear}" != "${years}" ]]; then - years="${copyrightBeginYear}-${years}" + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" fi - echo "Copyright (c) ${years} François Chastanet" + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" } # shellcheck disable=SC2317 # if function is overridden @@ -1031,6 +1036,29 @@ optionBashFrameworkConfigCallback() { fi } +defaultFrameworkConfig="$( + cat <<'EOF' +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$" +# Source directories +FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" +) + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +EOF +)" + commandOptionParseFinished() { if [[ -z "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then BASH_FRAMEWORK_ENV_FILES=() @@ -1050,8 +1078,16 @@ commandOptionParseFinished() { # shellcheck disable=SC2034 BASH_FRAMEWORK_CONFIG_FILE="" # shellcheck source=/.framework-config - Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || - Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config file" + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || { + # load default template framework config + if [[ ! -f "${PERSISTENT_TMPDIR}/.framework-config" ]]; then + echo "${defaultFrameworkConfig}" > "${PERSISTENT_TMPDIR}/.framework-config" + fi + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${PERSISTENT_TMPDIR}" || { + Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config.default file" + } + Log::displayWarning "Command ${SCRIPT_NAME} - Load default .framework-config file - ${PERSISTENT_TMPDIR}/.framework-config" + } fi if [[ "${optionConfig}" = "1" ]]; then @@ -1100,6 +1136,7 @@ gitIsBranchCommand() { ((options_parse_optionParsedCountOptionDisplayLevel = 0)) || true local -i options_parse_argParsedCountBranchNameArg ((options_parse_argParsedCountBranchNameArg = 0)) || true + # shellcheck disable=SC2034 local -i options_parse_parsedArgIndex=0 while (($# > 0)); do local options_parse_arg="$1" @@ -1118,12 +1155,14 @@ gitIsBranchCommand() { return 1 fi ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 optionBashFrameworkConfig="$1" optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" ;; # Option 2/14 # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' --config) + # shellcheck disable=SC2034 optionConfig="1" if ((options_parse_optionParsedCountOptionConfig >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1134,6 +1173,7 @@ gitIsBranchCommand() { # Option 3/14 # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' --verbose | -v) + # shellcheck disable=SC2034 optionInfoVerbose="1" if ((options_parse_optionParsedCountOptionInfoVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1146,6 +1186,7 @@ gitIsBranchCommand() { # Option 4/14 # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vv) + # shellcheck disable=SC2034 optionDebugVerbose="1" if ((options_parse_optionParsedCountOptionDebugVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1158,6 +1199,7 @@ gitIsBranchCommand() { # Option 5/14 # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vvv) + # shellcheck disable=SC2034 optionTraceVerbose="1" if ((options_parse_optionParsedCountOptionTraceVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1183,6 +1225,7 @@ gitIsBranchCommand() { # Option 7/14 # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' --no-color) + # shellcheck disable=SC2034 optionNoColor="1" if ((options_parse_optionParsedCountOptionNoColor >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1209,6 +1252,7 @@ gitIsBranchCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 optionTheme="$1" optionThemeCallback "${options_parse_arg}" "${optionTheme}" updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" @@ -1216,6 +1260,7 @@ gitIsBranchCommand() { # Option 9/14 # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' --help | -h) + # shellcheck disable=SC2034 optionHelp="1" if ((options_parse_optionParsedCountOptionHelp >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1227,6 +1272,7 @@ gitIsBranchCommand() { # Option 10/14 # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' --version) + # shellcheck disable=SC2034 optionVersion="1" if ((options_parse_optionParsedCountOptionVersion >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1238,6 +1284,7 @@ gitIsBranchCommand() { # Option 11/14 # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' --quiet | -q) + # shellcheck disable=SC2034 optionQuiet="1" if ((options_parse_optionParsedCountOptionQuiet >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1264,6 +1311,7 @@ gitIsBranchCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 optionLogLevel="$1" optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" @@ -1281,6 +1329,7 @@ gitIsBranchCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 optionLogFile="$1" optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" @@ -1302,6 +1351,7 @@ gitIsBranchCommand() { return 1 fi ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 optionDisplayLevel="$1" optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" @@ -1324,6 +1374,7 @@ gitIsBranchCommand() { return 1 fi ((++options_parse_argParsedCountBranchNameArg)) + # shellcheck disable=SC2034 branchNameArg="${options_parse_arg}" else if [[ "${argOptDefaultBehavior}" = "0" ]]; then @@ -1355,64 +1406,79 @@ gitIsBranchCommand() { echo -e "${__HELP_TITLE_COLOR}ARGUMENTS:${__RESET_COLOR}" echo -e " ${__HELP_OPTION_COLOR}branchName${__HELP_NORMAL} {single} (mandatory)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(the\ branch\ name\ to\ check) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(use\ alternate\ bash\ framework\ configuration.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ configuration) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} (optional)" + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Load\ the\ specified\ env\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(choose\ color\ theme\ \(default\,\ default-force\ or\ noColor\)\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ this\ command\ help) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Print\ version\ information\ and\ quit) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(set\ display\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo @@ -1434,10 +1500,6 @@ gitIsBranchCommand() { return 1 fi } -declare copyrightBeginYear="2020" -declare branchNameArg="" - -gitIsBranchCommand parse "${BASH_FRAMEWORK_ARGV[@]}" # @require Linux::requireExecutedAsUser run() { diff --git a/bin/gitRenameBranch b/bin/gitRenameBranch index 3ae2f637..29b3af53 100755 --- a/bin/gitRenameBranch +++ b/bin/gitRenameBranch @@ -47,6 +47,7 @@ declare -a BASH_FRAMEWORK_ARGV=("$@") declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") # @see https://unix.stackexchange.com/a/386856 +# shellcheck disable=SC2317 interruptManagement() { # restore SIGINT handler trap - INT @@ -76,6 +77,7 @@ TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX) export TMPDIR # temp dir cleaning +# shellcheck disable=SC2317 cleanOnExit() { if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" @@ -134,7 +136,7 @@ Array::wrap() { local -i argNoAnsiLength=0 while (($# > 0)); do - argNoAnsi="$(echo "${arg}" | Filters::removeAnsiCodes)" + argNoAnsi="$(echo "${arg%%*( )}" | Filters::removeAnsiCodes)" ((argNoAnsiLength = ${#argNoAnsi})) || true if (($# < 1 && argNoAnsiLength == 0)); then break @@ -157,7 +159,7 @@ Array::wrap() { if ((currentLineLength == 0 && firstLine == 0)); then echo -n "${indentStr}" fi - echo -e -n "${arg}" + echo -e -n "${arg}" | sed 's/[\t ]*$//g' needEcho="1" ((currentLineLength += argNoAnsiLength)) ((glueLength = ${#glue})) || true @@ -176,7 +178,7 @@ Array::wrap() { fi local -i length ((length = maxLineLength - currentLineLength)) || true - echo -e "${arg:0:${length}}" + echo -e "${arg:0:${length}}" | sed 's/[\t ]*$//g' ((currentLineLength = 0)) || true ((glueLength = 0)) || true arg="${arg:${length}}" @@ -184,7 +186,7 @@ Array::wrap() { else # arg cannot be stored on a whole line, so we add it on next line as a whole echo - echo -e -n "${indentStr}${arg}" + echo -e -n "${indentStr}${arg}" | sed 's/[\t ]*$//g' ((glueLength = ${#glue})) || true ((currentLineLength = argNoAnsiLength)) arg="" # allows to go to next arg @@ -208,8 +210,8 @@ Array::wrap() { # @description check if tty (interactive mode) is active # @noargs # @exitcode 1 if tty not active -# @environment NON_INTERACTIVE if 1 consider as not interactive even if environement is interactive -# @environment INTERACTIVE if 1 consider as interactive even if environement is not interactive +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive # @stderr diagnostic information + help if second argument is provided Assert::tty() { if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then @@ -318,6 +320,15 @@ Log::displayInfo() { Log::logInfo "$1" "${type}" } +# @description Display message using warning color (yellow) +# @arg $1 message:String the message to display +Log::displayWarning() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then + echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 + fi + Log::logWarning "$1" +} + # @description Display message using error color (red) and exit immediately with error status 1 # @arg $1 message:String the message to display Log::fatal() { @@ -421,6 +432,7 @@ UI::drawLine() { # @set __HELP_EXAMPLE String to remove # @set __HELP_TITLE String to remove # @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 UI::theme() { local theme="${1-default}" if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then @@ -436,46 +448,46 @@ UI::theme() { ;; esac if [[ "${theme}" = "default" ]]; then - export BASH_FRAMEWORK_THEME="default" + BASH_FRAMEWORK_THEME="default" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __SKIPPED_COLOR='\e[33m' # Yellow - export __DEBUG_COLOR='\e[37m' # Grey - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __HELP_TITLE_COLOR="\e[1;37m" # Bold - export __HELP_OPTION_COLOR="\e[1;34m" # Blue + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color + __RESET_COLOR='\e[0m' # Reset Color # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" + __HELP_TITLE="$(echo -e "\e[1;37m")" # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" + __HELP_NORMAL="$(echo -e "\033[0m")" else - export BASH_FRAMEWORK_THEME="noColor" + BASH_FRAMEWORK_THEME="noColor" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __DEBUG_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __HELP_TITLE_COLOR='' - export __HELP_OPTION_COLOR='' + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' fi } @@ -705,6 +717,14 @@ Log::logMessage() { fi } +# @description log message to file +# @arg $1 message:String the message to display +Log::logWarning() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then + Log::logMessage "${2:-WARNING}" "$1" + fi +} + # @description To be called before logging in the log file # @arg $1 file:string log file name # @arg $2 maxLogFilesCount:int maximum number of log files @@ -803,15 +823,6 @@ Framework::createTempFile() { mktemp -p "${TMPDIR:-/tmp}" -t "${1:-}.XXXXXXXXXXXX" } -# @description Display message using warning color (yellow) -# @arg $1 message:String the message to display -Log::displayWarning() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then - echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 - fi - Log::logWarning "$1" -} - # @description log message to file # @arg $1 message:String the message to display Log::logSkipped() { @@ -836,14 +847,6 @@ Array::contains() { return 1 } -# @description log message to file -# @arg $1 message:String the message to display -Log::logWarning() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then - Log::logMessage "${2:-WARNING}" "$1" - fi -} - # FUNCTIONS facade_main_a7456d3b93ad4a0cb0c417608cfc15d7() { @@ -870,17 +873,25 @@ Compiler::Facade::requireCommandBinDir Linux::requireExecutedAsUser # @require Compiler::Facade::requireCommandBinDir -# shellcheck disable=SC2154 +# shellcheck disable=SC2034 + +# variables values +declare optionPush="0" +declare optionDelete="0" +declare optionAssumeYes="0" +declare newBranchNameArg="" +declare oldBranchNameArg="" + +# other values +declare copyrightBeginYear="2020" declare -a BASH_FRAMEWORK_ARGV_FILTERED=() copyrightCallback() { - local years - years="$(date +%Y)" - if [[ -n "${copyrightBeginYear}" && "${copyrightBeginYear}" != "${years}" ]]; then - years="${copyrightBeginYear}-${years}" + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" fi - echo "Copyright (c) ${years} François Chastanet" + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" } # shellcheck disable=SC2317 # if function is overridden @@ -1054,6 +1065,29 @@ optionBashFrameworkConfigCallback() { fi } +defaultFrameworkConfig="$( + cat <<'EOF' +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$" +# Source directories +FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" +) + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +EOF +)" + commandOptionParseFinished() { if [[ -z "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then BASH_FRAMEWORK_ENV_FILES=() @@ -1073,8 +1107,16 @@ commandOptionParseFinished() { # shellcheck disable=SC2034 BASH_FRAMEWORK_CONFIG_FILE="" # shellcheck source=/.framework-config - Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || - Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config file" + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || { + # load default template framework config + if [[ ! -f "${PERSISTENT_TMPDIR}/.framework-config" ]]; then + echo "${defaultFrameworkConfig}" > "${PERSISTENT_TMPDIR}/.framework-config" + fi + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${PERSISTENT_TMPDIR}" || { + Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config.default file" + } + Log::displayWarning "Command ${SCRIPT_NAME} - Load default .framework-config file - ${PERSISTENT_TMPDIR}/.framework-config" + } fi if [[ "${optionConfig}" = "1" ]]; then @@ -1134,6 +1176,7 @@ gitRenameBranchCommand() { ((options_parse_argParsedCountNewBranchNameArg = 0)) || true local -i options_parse_argParsedCountOldBranchNameArg ((options_parse_argParsedCountOldBranchNameArg = 0)) || true + # shellcheck disable=SC2034 local -i options_parse_parsedArgIndex=0 while (($# > 0)); do local options_parse_arg="$1" @@ -1142,6 +1185,7 @@ gitRenameBranchCommand() { # Option 1/17 # Option optionAssumeYes --assume-yes|--yes|-y variableType Boolean min 0 max 1 authorizedValues '' regexp '' --assume-yes | --yes | -y) + # shellcheck disable=SC2034 optionAssumeYes="1" if ((options_parse_optionParsedCountOptionAssumeYes >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1152,6 +1196,7 @@ gitRenameBranchCommand() { # Option 2/17 # Option optionPush --push|-p variableType Boolean min 0 max 1 authorizedValues '' regexp '' --push | -p) + # shellcheck disable=SC2034 optionPush="1" if ((options_parse_optionParsedCountOptionPush >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1162,6 +1207,7 @@ gitRenameBranchCommand() { # Option 3/17 # Option optionDelete --delete|-d variableType Boolean min 0 max 1 authorizedValues '' regexp '' --delete | -d) + # shellcheck disable=SC2034 optionDelete="1" if ((options_parse_optionParsedCountOptionDelete >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1182,12 +1228,14 @@ gitRenameBranchCommand() { return 1 fi ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 optionBashFrameworkConfig="$1" optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" ;; # Option 5/17 # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' --config) + # shellcheck disable=SC2034 optionConfig="1" if ((options_parse_optionParsedCountOptionConfig >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1198,6 +1246,7 @@ gitRenameBranchCommand() { # Option 6/17 # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' --verbose | -v) + # shellcheck disable=SC2034 optionInfoVerbose="1" if ((options_parse_optionParsedCountOptionInfoVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1210,6 +1259,7 @@ gitRenameBranchCommand() { # Option 7/17 # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vv) + # shellcheck disable=SC2034 optionDebugVerbose="1" if ((options_parse_optionParsedCountOptionDebugVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1222,6 +1272,7 @@ gitRenameBranchCommand() { # Option 8/17 # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vvv) + # shellcheck disable=SC2034 optionTraceVerbose="1" if ((options_parse_optionParsedCountOptionTraceVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1247,6 +1298,7 @@ gitRenameBranchCommand() { # Option 10/17 # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' --no-color) + # shellcheck disable=SC2034 optionNoColor="1" if ((options_parse_optionParsedCountOptionNoColor >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1273,6 +1325,7 @@ gitRenameBranchCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 optionTheme="$1" optionThemeCallback "${options_parse_arg}" "${optionTheme}" updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" @@ -1280,6 +1333,7 @@ gitRenameBranchCommand() { # Option 12/17 # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' --help | -h) + # shellcheck disable=SC2034 optionHelp="1" if ((options_parse_optionParsedCountOptionHelp >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1291,6 +1345,7 @@ gitRenameBranchCommand() { # Option 13/17 # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' --version) + # shellcheck disable=SC2034 optionVersion="1" if ((options_parse_optionParsedCountOptionVersion >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1302,6 +1357,7 @@ gitRenameBranchCommand() { # Option 14/17 # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' --quiet | -q) + # shellcheck disable=SC2034 optionQuiet="1" if ((options_parse_optionParsedCountOptionQuiet >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1328,6 +1384,7 @@ gitRenameBranchCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 optionLogLevel="$1" optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" @@ -1345,6 +1402,7 @@ gitRenameBranchCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 optionLogFile="$1" optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" @@ -1366,6 +1424,7 @@ gitRenameBranchCommand() { return 1 fi ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 optionDisplayLevel="$1" optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" @@ -1388,6 +1447,7 @@ gitRenameBranchCommand() { return 1 fi ((++options_parse_argParsedCountNewBranchNameArg)) + # shellcheck disable=SC2034 newBranchNameArg="${options_parse_arg}" # Argument 2/2 # Argument oldBranchNameArg min 0 max 1 authorizedValues '' regexp '' @@ -1397,6 +1457,7 @@ gitRenameBranchCommand() { return 1 fi ((++options_parse_argParsedCountOldBranchNameArg)) + # shellcheck disable=SC2034 oldBranchNameArg="${options_parse_arg}" else if [[ "${argOptDefaultBehavior}" = "0" ]]; then @@ -1429,80 +1490,101 @@ gitRenameBranchCommand() { echo -e "${__HELP_TITLE_COLOR}ARGUMENTS:${__RESET_COLOR}" echo -e " ${__HELP_OPTION_COLOR}newBranchName${__HELP_NORMAL} {single} (mandatory)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(the\ branch\ name\ to\ check) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " [${__HELP_OPTION_COLOR}oldBranchName${__HELP_NORMAL} {single}]" local -a helpArray + # shellcheck disable=SC2054 helpArray=(the\ name\ of\ the\ old\ branch\ if\ not\ current\ one) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--assume-yes${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}--yes${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-y${__HELP_NORMAL} (optional) (at most 1 times)" - echo -e " $(Array::wrap ' ' 76 4 $(assumeYesHelpCallback))" - printf " %b\n" "${__HELP_OPTION_COLOR}--push${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-p${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--assume-yes${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}--yes${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-y${__HELP_NORMAL} {single}" + local -a helpArray + # shellcheck disable=SC2054,SC2206 + mapfile -t helpArray < <(assumeYesHelpCallback) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo -e " ${__HELP_OPTION_COLOR}--push${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-p${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(push\ the\ new\ branch) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--delete${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-d${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--delete${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-d${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(delete\ the\ old\ remote\ branch) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(use\ alternate\ bash\ framework\ configuration.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ configuration) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} (optional)" + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Load\ the\ specified\ env\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(choose\ color\ theme\ \(default\,\ default-force\ or\ noColor\)\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ this\ command\ help) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Print\ version\ information\ and\ quit) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(set\ display\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e """ @@ -1537,14 +1619,6 @@ ${__HELP_OPTION_COLOR}9${__HELP_NORMAL} : if failed to push the new branch""" return 1 fi } -declare copyrightBeginYear="2020" - -#default values -declare optionPush="0" -declare optionDelete="0" -declare optionAssumeYes="0" -declare newBranchNameArg="" -declare oldBranchNameArg="" assumeYesHelpCallback() { echo "do not ask for confirmation (use with caution)" $'\n' diff --git a/bin/installRequirements b/bin/installRequirements index f7cc131b..f6a3b8f5 100755 --- a/bin/installRequirements +++ b/bin/installRequirements @@ -47,6 +47,7 @@ declare -a BASH_FRAMEWORK_ARGV=("$@") declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") # @see https://unix.stackexchange.com/a/386856 +# shellcheck disable=SC2317 interruptManagement() { # restore SIGINT handler trap - INT @@ -76,6 +77,7 @@ TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX) export TMPDIR # temp dir cleaning +# shellcheck disable=SC2317 cleanOnExit() { if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" @@ -134,7 +136,7 @@ Array::wrap() { local -i argNoAnsiLength=0 while (($# > 0)); do - argNoAnsi="$(echo "${arg}" | Filters::removeAnsiCodes)" + argNoAnsi="$(echo "${arg%%*( )}" | Filters::removeAnsiCodes)" ((argNoAnsiLength = ${#argNoAnsi})) || true if (($# < 1 && argNoAnsiLength == 0)); then break @@ -157,7 +159,7 @@ Array::wrap() { if ((currentLineLength == 0 && firstLine == 0)); then echo -n "${indentStr}" fi - echo -e -n "${arg}" + echo -e -n "${arg}" | sed 's/[\t ]*$//g' needEcho="1" ((currentLineLength += argNoAnsiLength)) ((glueLength = ${#glue})) || true @@ -176,7 +178,7 @@ Array::wrap() { fi local -i length ((length = maxLineLength - currentLineLength)) || true - echo -e "${arg:0:${length}}" + echo -e "${arg:0:${length}}" | sed 's/[\t ]*$//g' ((currentLineLength = 0)) || true ((glueLength = 0)) || true arg="${arg:${length}}" @@ -184,7 +186,7 @@ Array::wrap() { else # arg cannot be stored on a whole line, so we add it on next line as a whole echo - echo -e -n "${indentStr}${arg}" + echo -e -n "${indentStr}${arg}" | sed 's/[\t ]*$//g' ((glueLength = ${#glue})) || true ((currentLineLength = argNoAnsiLength)) arg="" # allows to go to next arg @@ -342,6 +344,15 @@ Log::displayInfo() { Log::logInfo "$1" "${type}" } +# @description Display message using warning color (yellow) +# @arg $1 message:String the message to display +Log::displayWarning() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then + echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 + fi + Log::logWarning "$1" +} + # @description Display message using error color (red) and exit immediately with error status 1 # @arg $1 message:String the message to display Log::fatal() { @@ -423,6 +434,7 @@ UI::drawLine() { # @set __HELP_EXAMPLE String to remove # @set __HELP_TITLE String to remove # @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 UI::theme() { local theme="${1-default}" if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then @@ -438,46 +450,46 @@ UI::theme() { ;; esac if [[ "${theme}" = "default" ]]; then - export BASH_FRAMEWORK_THEME="default" + BASH_FRAMEWORK_THEME="default" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __SKIPPED_COLOR='\e[33m' # Yellow - export __DEBUG_COLOR='\e[37m' # Grey - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __HELP_TITLE_COLOR="\e[1;37m" # Bold - export __HELP_OPTION_COLOR="\e[1;34m" # Blue + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color + __RESET_COLOR='\e[0m' # Reset Color # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" + __HELP_TITLE="$(echo -e "\e[1;37m")" # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" + __HELP_NORMAL="$(echo -e "\033[0m")" else - export BASH_FRAMEWORK_THEME="noColor" + BASH_FRAMEWORK_THEME="noColor" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __DEBUG_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __HELP_TITLE_COLOR='' - export __HELP_OPTION_COLOR='' + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' fi } @@ -503,8 +515,8 @@ Linux::requireExecutedAsUser() { # @description check if tty (interactive mode) is active # @noargs # @exitcode 1 if tty not active -# @environment NON_INTERACTIVE if 1 consider as not interactive even if environement is interactive -# @environment INTERACTIVE if 1 consider as interactive even if environement is not interactive +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive # @stderr diagnostic information + help if second argument is provided Assert::tty() { if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then @@ -749,6 +761,14 @@ Log::logMessage() { fi } +# @description log message to file +# @arg $1 message:String the message to display +Log::logWarning() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then + Log::logMessage "${2:-WARNING}" "$1" + fi +} + # @description To be called before logging in the log file # @arg $1 file:string log file name # @arg $2 maxLogFilesCount:int maximum number of log files @@ -847,15 +867,6 @@ Framework::createTempFile() { mktemp -p "${TMPDIR:-/tmp}" -t "${1:-}.XXXXXXXXXXXX" } -# @description Display message using warning color (yellow) -# @arg $1 message:String the message to display -Log::displayWarning() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then - echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 - fi - Log::logWarning "$1" -} - # @description log message to file # @arg $1 message:String the message to display Log::logSkipped() { @@ -909,14 +920,6 @@ Assert::commandExists() { return 0 } -# @description log message to file -# @arg $1 message:String the message to display -Log::logWarning() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then - Log::logMessage "${2:-WARNING}" "$1" - fi -} - # FUNCTIONS facade_main_ae2d7ee85d7a47bdbda1838afc5f3c2c() { @@ -944,16 +947,18 @@ Compiler::Facade::requireCommandBinDir Linux::requireExecutedAsUser # @require Compiler::Facade::requireCommandBinDir +# shellcheck disable=SC2034,SC2154 + +declare copyrightBeginYear="2020" +declare optionBashFrameworkConfig="${BASH_TOOLS_ROOT_DIR}/.framework-config" declare -a BASH_FRAMEWORK_ARGV_FILTERED=() copyrightCallback() { - local years - years="$(date +%Y)" - if [[ -n "${copyrightBeginYear}" && "${copyrightBeginYear}" != "${years}" ]]; then - years="${copyrightBeginYear}-${years}" + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" fi - echo "Copyright (c) ${years} François Chastanet" + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" } # shellcheck disable=SC2317 # if function is overridden @@ -1127,6 +1132,29 @@ optionBashFrameworkConfigCallback() { fi } +defaultFrameworkConfig="$( + cat <<'EOF' +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$" +# Source directories +FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" +) + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +EOF +)" + commandOptionParseFinished() { if [[ -z "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then BASH_FRAMEWORK_ENV_FILES=() @@ -1146,8 +1174,16 @@ commandOptionParseFinished() { # shellcheck disable=SC2034 BASH_FRAMEWORK_CONFIG_FILE="" # shellcheck source=/.framework-config - Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || - Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config file" + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || { + # load default template framework config + if [[ ! -f "${PERSISTENT_TMPDIR}/.framework-config" ]]; then + echo "${defaultFrameworkConfig}" > "${PERSISTENT_TMPDIR}/.framework-config" + fi + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${PERSISTENT_TMPDIR}" || { + Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config.default file" + } + Log::displayWarning "Command ${SCRIPT_NAME} - Load default .framework-config file - ${PERSISTENT_TMPDIR}/.framework-config" + } fi if [[ "${optionConfig}" = "1" ]]; then @@ -1194,6 +1230,7 @@ installRequirementsCommand() { ((options_parse_optionParsedCountOptionLogFile = 0)) || true local -i options_parse_optionParsedCountOptionDisplayLevel ((options_parse_optionParsedCountOptionDisplayLevel = 0)) || true + # shellcheck disable=SC2034 local -i options_parse_parsedArgIndex=0 while (($# > 0)); do local options_parse_arg="$1" @@ -1212,12 +1249,14 @@ installRequirementsCommand() { return 1 fi ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 optionBashFrameworkConfig="$1" optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" ;; # Option 2/14 # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' --config) + # shellcheck disable=SC2034 optionConfig="1" if ((options_parse_optionParsedCountOptionConfig >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1228,6 +1267,7 @@ installRequirementsCommand() { # Option 3/14 # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' --verbose | -v) + # shellcheck disable=SC2034 optionInfoVerbose="1" if ((options_parse_optionParsedCountOptionInfoVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1240,6 +1280,7 @@ installRequirementsCommand() { # Option 4/14 # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vv) + # shellcheck disable=SC2034 optionDebugVerbose="1" if ((options_parse_optionParsedCountOptionDebugVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1252,6 +1293,7 @@ installRequirementsCommand() { # Option 5/14 # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vvv) + # shellcheck disable=SC2034 optionTraceVerbose="1" if ((options_parse_optionParsedCountOptionTraceVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1277,6 +1319,7 @@ installRequirementsCommand() { # Option 7/14 # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' --no-color) + # shellcheck disable=SC2034 optionNoColor="1" if ((options_parse_optionParsedCountOptionNoColor >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1303,6 +1346,7 @@ installRequirementsCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 optionTheme="$1" optionThemeCallback "${options_parse_arg}" "${optionTheme}" updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" @@ -1310,6 +1354,7 @@ installRequirementsCommand() { # Option 9/14 # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' --help | -h) + # shellcheck disable=SC2034 optionHelp="1" if ((options_parse_optionParsedCountOptionHelp >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1321,6 +1366,7 @@ installRequirementsCommand() { # Option 10/14 # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' --version) + # shellcheck disable=SC2034 optionVersion="1" if ((options_parse_optionParsedCountOptionVersion >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1332,6 +1378,7 @@ installRequirementsCommand() { # Option 11/14 # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' --quiet | -q) + # shellcheck disable=SC2034 optionQuiet="1" if ((options_parse_optionParsedCountOptionQuiet >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1358,6 +1405,7 @@ installRequirementsCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 optionLogLevel="$1" optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" @@ -1375,6 +1423,7 @@ installRequirementsCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 optionLogFile="$1" optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" @@ -1396,6 +1445,7 @@ installRequirementsCommand() { return 1 fi ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 optionDisplayLevel="$1" optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" @@ -1428,60 +1478,74 @@ installRequirementsCommand() { "[--bash-framework-config ]" "[--config]" "[--verbose|-v]" "[-vv]" "[-vvv]" "[--env-file ]" "[--no-color]" "[--theme ]" "[--help|-h]" "[--version]" "[--quiet|-q]" "[--log-level ]" "[--log-file ]" "[--display-level ]")" echo echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(use\ alternate\ bash\ framework\ configuration.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ configuration) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} (optional)" + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Load\ the\ specified\ env\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(choose\ color\ theme\ \(default\,\ default-force\ or\ noColor\)\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ this\ command\ help) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Print\ version\ information\ and\ quit) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(set\ display\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e """ @@ -1509,15 +1573,9 @@ installRequirementsCommand() { fi } declare -a externalBinaries=([0]="bin/awkLint" [1]="bin/buildBinFiles" [2]="bin/frameworkLint" [3]="bin/findShebangFiles" [4]="bin/megalinter" [5]="bin/runBuildContainer" [6]="bin/shellcheckLint" [7]="bin/test" [8]="bin/buildPushDockerImage") -declare copyrightBeginYear="2020" -declare optionBashFrameworkConfig="${BASH_TOOLS_ROOT_DIR}/.framework-config" installRequirementsCommand parse "${BASH_FRAMEWORK_ARGV[@]}" -if [[ "$(id -u)" = "0" ]]; then - Log::fatal "this script should be executed as normal user" -fi - # @require Linux::requireExecutedAsUser run() { mkdir -p "${BASH_TOOLS_ROOT_DIR}/vendor" || true @@ -1527,7 +1585,6 @@ run() { Log::displayInfo "Copying useful binaries from bash-tools-framework" local externalBinary - # shellcheck disable=SC2154 for externalBinary in "${externalBinaries[@]}"; do cp -v "${BASH_TOOLS_ROOT_DIR}/vendor/bash-tools-framework/${externalBinary}" \ "${BASH_TOOLS_ROOT_DIR}/bin" diff --git a/bin/mysql2puml b/bin/mysql2puml index ede50447..eccbd84e 100755 --- a/bin/mysql2puml +++ b/bin/mysql2puml @@ -47,6 +47,7 @@ declare -a BASH_FRAMEWORK_ARGV=("$@") declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") # @see https://unix.stackexchange.com/a/386856 +# shellcheck disable=SC2317 interruptManagement() { # restore SIGINT handler trap - INT @@ -76,6 +77,7 @@ TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX) export TMPDIR # temp dir cleaning +# shellcheck disable=SC2317 cleanOnExit() { if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" @@ -150,7 +152,7 @@ Array::wrap() { local -i argNoAnsiLength=0 while (($# > 0)); do - argNoAnsi="$(echo "${arg}" | Filters::removeAnsiCodes)" + argNoAnsi="$(echo "${arg%%*( )}" | Filters::removeAnsiCodes)" ((argNoAnsiLength = ${#argNoAnsi})) || true if (($# < 1 && argNoAnsiLength == 0)); then break @@ -173,7 +175,7 @@ Array::wrap() { if ((currentLineLength == 0 && firstLine == 0)); then echo -n "${indentStr}" fi - echo -e -n "${arg}" + echo -e -n "${arg}" | sed 's/[\t ]*$//g' needEcho="1" ((currentLineLength += argNoAnsiLength)) ((glueLength = ${#glue})) || true @@ -192,7 +194,7 @@ Array::wrap() { fi local -i length ((length = maxLineLength - currentLineLength)) || true - echo -e "${arg:0:${length}}" + echo -e "${arg:0:${length}}" | sed 's/[\t ]*$//g' ((currentLineLength = 0)) || true ((glueLength = 0)) || true arg="${arg:${length}}" @@ -200,7 +202,7 @@ Array::wrap() { else # arg cannot be stored on a whole line, so we add it on next line as a whole echo - echo -e -n "${indentStr}${arg}" + echo -e -n "${indentStr}${arg}" | sed 's/[\t ]*$//g' ((glueLength = ${#glue})) || true ((currentLineLength = argNoAnsiLength)) arg="" # allows to go to next arg @@ -433,6 +435,15 @@ Log::displayInfo() { Log::logInfo "$1" "${type}" } +# @description Display message using warning color (yellow) +# @arg $1 message:String the message to display +Log::displayWarning() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then + echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 + fi + Log::logWarning "$1" +} + # @description Display message using error color (red) and exit immediately with error status 1 # @arg $1 message:String the message to display Log::fatal() { @@ -514,6 +525,7 @@ UI::drawLine() { # @set __HELP_EXAMPLE String to remove # @set __HELP_TITLE String to remove # @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 UI::theme() { local theme="${1-default}" if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then @@ -529,46 +541,46 @@ UI::theme() { ;; esac if [[ "${theme}" = "default" ]]; then - export BASH_FRAMEWORK_THEME="default" + BASH_FRAMEWORK_THEME="default" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __SKIPPED_COLOR='\e[33m' # Yellow - export __DEBUG_COLOR='\e[37m' # Grey - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __HELP_TITLE_COLOR="\e[1;37m" # Bold - export __HELP_OPTION_COLOR="\e[1;34m" # Blue + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color + __RESET_COLOR='\e[0m' # Reset Color # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" + __HELP_TITLE="$(echo -e "\e[1;37m")" # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" + __HELP_NORMAL="$(echo -e "\033[0m")" else - export BASH_FRAMEWORK_THEME="noColor" + BASH_FRAMEWORK_THEME="noColor" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __DEBUG_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __HELP_TITLE_COLOR='' - export __HELP_OPTION_COLOR='' + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' fi } @@ -585,8 +597,8 @@ Compiler::Facade::requireCommandBinDir() { # @description check if tty (interactive mode) is active # @noargs # @exitcode 1 if tty not active -# @environment NON_INTERACTIVE if 1 consider as not interactive even if environement is interactive -# @environment INTERACTIVE if 1 consider as interactive even if environement is not interactive +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive # @stderr diagnostic information + help if second argument is provided Assert::tty() { if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then @@ -852,6 +864,14 @@ Log::logMessage() { fi } +# @description log message to file +# @arg $1 message:String the message to display +Log::logWarning() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then + Log::logMessage "${2:-WARNING}" "$1" + fi +} + # @description To be called before logging in the log file # @arg $1 file:string log file name # @arg $2 maxLogFilesCount:int maximum number of log files @@ -942,15 +962,6 @@ Filters::commentLines() { grep -vxE "[[:blank:]]*(${commentLinePrefix:-#}.*)?" "$@" || test $? = 1 } -# @description Display message using warning color (yellow) -# @arg $1 message:String the message to display -Log::displayWarning() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then - echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 - fi - Log::logWarning "$1" -} - # @description log message to file # @arg $1 message:String the message to display Log::logSkipped() { @@ -988,14 +999,6 @@ Assert::commandExists() { return 0 } -# @description log message to file -# @arg $1 message:String the message to display -Log::logWarning() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then - Log::logMessage "${2:-WARNING}" "$1" - fi -} - # FUNCTIONS facade_main_0c81f03399944490b21f8ac30d7e073b() { @@ -1022,16 +1025,19 @@ Log::requireLoad Compiler::Facade::requireCommandBinDir # @require Compiler::Facade::requireCommandBinDir +# shellcheck disable=SC2034 + +declare copyrightBeginYear="2020" +declare optionBashFrameworkConfig="${BASH_TOOLS_ROOT_DIR}/.framework-config" +declare optionSkin="default" declare -a BASH_FRAMEWORK_ARGV_FILTERED=() copyrightCallback() { - local years - years="$(date +%Y)" - if [[ -n "${copyrightBeginYear}" && "${copyrightBeginYear}" != "${years}" ]]; then - years="${copyrightBeginYear}-${years}" + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" fi - echo "Copyright (c) ${years} François Chastanet" + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" } # shellcheck disable=SC2317 # if function is overridden @@ -1205,6 +1211,29 @@ optionBashFrameworkConfigCallback() { fi } +defaultFrameworkConfig="$( + cat <<'EOF' +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$" +# Source directories +FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" +) + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +EOF +)" + commandOptionParseFinished() { if [[ -z "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then BASH_FRAMEWORK_ENV_FILES=() @@ -1224,8 +1253,16 @@ commandOptionParseFinished() { # shellcheck disable=SC2034 BASH_FRAMEWORK_CONFIG_FILE="" # shellcheck source=/.framework-config - Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || - Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config file" + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || { + # load default template framework config + if [[ ! -f "${PERSISTENT_TMPDIR}/.framework-config" ]]; then + echo "${defaultFrameworkConfig}" > "${PERSISTENT_TMPDIR}/.framework-config" + fi + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${PERSISTENT_TMPDIR}" || { + Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config.default file" + } + Log::displayWarning "Command ${SCRIPT_NAME} - Load default .framework-config file - ${PERSISTENT_TMPDIR}/.framework-config" + } fi if [[ "${optionConfig}" = "1" ]]; then @@ -1238,6 +1275,7 @@ mysql2pumlCommand() { shift || true if [[ "${options_parse_cmd}" = "parse" ]]; then + optionSkin="default" local -i options_parse_optionParsedCountOptionSkin ((options_parse_optionParsedCountOptionSkin = 0)) || true local -i options_parse_optionParsedCountOptionBashFrameworkConfig @@ -1276,6 +1314,7 @@ mysql2pumlCommand() { ((options_parse_optionParsedCountOptionDisplayLevel = 0)) || true local -i options_parse_argParsedCountInputSqlFile ((options_parse_argParsedCountInputSqlFile = 0)) || true + # shellcheck disable=SC2034 local -i options_parse_parsedArgIndex=0 while (($# > 0)); do local options_parse_arg="$1" @@ -1294,6 +1333,7 @@ mysql2pumlCommand() { return 1 fi ((++options_parse_optionParsedCountOptionSkin)) + # shellcheck disable=SC2034 optionSkin="$1" optionSkinCallback "${options_parse_arg}" "${optionSkin}" ;; @@ -1310,12 +1350,14 @@ mysql2pumlCommand() { return 1 fi ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 optionBashFrameworkConfig="$1" optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" ;; # Option 3/15 # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' --config) + # shellcheck disable=SC2034 optionConfig="1" if ((options_parse_optionParsedCountOptionConfig >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1326,6 +1368,7 @@ mysql2pumlCommand() { # Option 4/15 # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' --verbose | -v) + # shellcheck disable=SC2034 optionInfoVerbose="1" if ((options_parse_optionParsedCountOptionInfoVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1338,6 +1381,7 @@ mysql2pumlCommand() { # Option 5/15 # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vv) + # shellcheck disable=SC2034 optionDebugVerbose="1" if ((options_parse_optionParsedCountOptionDebugVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1350,6 +1394,7 @@ mysql2pumlCommand() { # Option 6/15 # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vvv) + # shellcheck disable=SC2034 optionTraceVerbose="1" if ((options_parse_optionParsedCountOptionTraceVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1375,6 +1420,7 @@ mysql2pumlCommand() { # Option 8/15 # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' --no-color) + # shellcheck disable=SC2034 optionNoColor="1" if ((options_parse_optionParsedCountOptionNoColor >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1401,6 +1447,7 @@ mysql2pumlCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 optionTheme="$1" optionThemeCallback "${options_parse_arg}" "${optionTheme}" updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" @@ -1408,6 +1455,7 @@ mysql2pumlCommand() { # Option 10/15 # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' --help | -h) + # shellcheck disable=SC2034 optionHelp="1" if ((options_parse_optionParsedCountOptionHelp >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1419,6 +1467,7 @@ mysql2pumlCommand() { # Option 11/15 # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' --version) + # shellcheck disable=SC2034 optionVersion="1" if ((options_parse_optionParsedCountOptionVersion >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1430,6 +1479,7 @@ mysql2pumlCommand() { # Option 12/15 # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' --quiet | -q) + # shellcheck disable=SC2034 optionQuiet="1" if ((options_parse_optionParsedCountOptionQuiet >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1456,6 +1506,7 @@ mysql2pumlCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 optionLogLevel="$1" optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" @@ -1473,6 +1524,7 @@ mysql2pumlCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 optionLogFile="$1" optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" @@ -1494,6 +1546,7 @@ mysql2pumlCommand() { return 1 fi ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 optionDisplayLevel="$1" optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" @@ -1516,6 +1569,7 @@ mysql2pumlCommand() { return 1 fi ((++options_parse_argParsedCountInputSqlFile)) + # shellcheck disable=SC2034 inputSqlFile="${options_parse_arg}" inputSqlFileCallback "${inputSqlFile}" -- "${@:2}" else @@ -1544,70 +1598,87 @@ mysql2pumlCommand() { echo -e "${__HELP_TITLE_COLOR}ARGUMENTS:${__RESET_COLOR}" echo -e " [${__HELP_OPTION_COLOR}inputSqlFile${__HELP_NORMAL} {single}]" local -a helpArray + # shellcheck disable=SC2054 helpArray=(sql\ filepath\ to\ parse\ \(read\ from\ stdin\ if\ not\ provided\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--skin ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--skin ${__HELP_NORMAL} {single}" local -a helpArray - helpArray=(header\ configuration\ of\ the\ plant\ uml\ file\ \(default:\ default\)) + # shellcheck disable=SC2054 + helpArray=(header\ configuration\ of\ the\ plant\ uml\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo ' Default value: default' echo echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(use\ alternate\ bash\ framework\ configuration.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ configuration) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} (optional)" + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Load\ the\ specified\ env\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(choose\ color\ theme\ \(default\,\ default-force\ or\ noColor\)\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ this\ command\ help) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Print\ version\ information\ and\ quit) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(set\ display\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e """ @@ -1637,9 +1708,6 @@ mysqldump --skip-add-drop-table --skip-add-locks --skip-disable-keys --ski return 1 fi } -declare copyrightBeginYear="2020" -declare optionBashFrameworkConfig="${BASH_TOOLS_ROOT_DIR}/.framework-config" -declare optionSkin="default" optionHelpCallback() { local skinListHelpFile @@ -1671,7 +1739,6 @@ inputSqlFileCallback() { } mysql2pumlCommand parse "${BASH_FRAMEWORK_ARGV[@]}" - declare awkScript awkScript="$( cat <<'EOF' @@ -1896,7 +1963,6 @@ EOF )" run() { - # shellcheck disable=SC2154 absSkinFile="$(Conf::getAbsoluteFile "mysql2pumlSkins" "${optionSkin}" "puml")" || Log::fatal "the skin ${optionSkin} does not exist" diff --git a/bin/upgradeGithubRelease b/bin/upgradeGithubRelease index d644be01..0edf5c04 100755 --- a/bin/upgradeGithubRelease +++ b/bin/upgradeGithubRelease @@ -47,6 +47,7 @@ declare -a BASH_FRAMEWORK_ARGV=("$@") declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") # @see https://unix.stackexchange.com/a/386856 +# shellcheck disable=SC2317 interruptManagement() { # restore SIGINT handler trap - INT @@ -76,6 +77,7 @@ TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX) export TMPDIR # temp dir cleaning +# shellcheck disable=SC2317 cleanOnExit() { if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" @@ -134,7 +136,7 @@ Array::wrap() { local -i argNoAnsiLength=0 while (($# > 0)); do - argNoAnsi="$(echo "${arg}" | Filters::removeAnsiCodes)" + argNoAnsi="$(echo "${arg%%*( )}" | Filters::removeAnsiCodes)" ((argNoAnsiLength = ${#argNoAnsi})) || true if (($# < 1 && argNoAnsiLength == 0)); then break @@ -157,7 +159,7 @@ Array::wrap() { if ((currentLineLength == 0 && firstLine == 0)); then echo -n "${indentStr}" fi - echo -e -n "${arg}" + echo -e -n "${arg}" | sed 's/[\t ]*$//g' needEcho="1" ((currentLineLength += argNoAnsiLength)) ((glueLength = ${#glue})) || true @@ -176,7 +178,7 @@ Array::wrap() { fi local -i length ((length = maxLineLength - currentLineLength)) || true - echo -e "${arg:0:${length}}" + echo -e "${arg:0:${length}}" | sed 's/[\t ]*$//g' ((currentLineLength = 0)) || true ((glueLength = 0)) || true arg="${arg:${length}}" @@ -184,7 +186,7 @@ Array::wrap() { else # arg cannot be stored on a whole line, so we add it on next line as a whole echo - echo -e -n "${indentStr}${arg}" + echo -e -n "${indentStr}${arg}" | sed 's/[\t ]*$//g' ((glueLength = ${#glue})) || true ((currentLineLength = argNoAnsiLength)) arg="" # allows to go to next arg @@ -532,6 +534,7 @@ UI::drawLine() { # @set __HELP_EXAMPLE String to remove # @set __HELP_TITLE String to remove # @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 UI::theme() { local theme="${1-default}" if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then @@ -547,46 +550,46 @@ UI::theme() { ;; esac if [[ "${theme}" = "default" ]]; then - export BASH_FRAMEWORK_THEME="default" + BASH_FRAMEWORK_THEME="default" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __SKIPPED_COLOR='\e[33m' # Yellow - export __DEBUG_COLOR='\e[37m' # Grey - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __HELP_TITLE_COLOR="\e[1;37m" # Bold - export __HELP_OPTION_COLOR="\e[1;34m" # Blue + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color + __RESET_COLOR='\e[0m' # Reset Color # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" + __HELP_TITLE="$(echo -e "\e[1;37m")" # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" + __HELP_NORMAL="$(echo -e "\033[0m")" else - export BASH_FRAMEWORK_THEME="noColor" + BASH_FRAMEWORK_THEME="noColor" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __DEBUG_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __HELP_TITLE_COLOR='' - export __HELP_OPTION_COLOR='' + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' fi } @@ -645,8 +648,8 @@ Compiler::Facade::requireCommandBinDir() { # @description check if tty (interactive mode) is active # @noargs # @exitcode 1 if tty not active -# @environment NON_INTERACTIVE if 1 consider as not interactive even if environement is interactive -# @environment INTERACTIVE if 1 consider as interactive even if environement is not interactive +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive # @stderr diagnostic information + help if second argument is provided Assert::tty() { if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then @@ -1156,17 +1159,29 @@ Linux::requireJqCommand Compiler::Facade::requireCommandBinDir # @require Compiler::Facade::requireCommandBinDir -# shellcheck disable=SC2154 +# shellcheck disable=SC2034 + +# default +declare defaultVersionArg="--version" + +# option values +declare targetFileArg="" +declare githubUrlPatternArg="" +declare optionVersionArg="${defaultVersionArg}" +declare optionCurrentVersion="" +declare optionMinimalVersion="" +declare optionExactVersion="" + +# other values +declare copyrightBeginYear="2020" declare -a BASH_FRAMEWORK_ARGV_FILTERED=() copyrightCallback() { - local years - years="$(date +%Y)" - if [[ -n "${copyrightBeginYear}" && "${copyrightBeginYear}" != "${years}" ]]; then - years="${copyrightBeginYear}-${years}" + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" fi - echo "Copyright (c) ${years} François Chastanet" + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" } # shellcheck disable=SC2317 # if function is overridden @@ -1340,6 +1355,29 @@ optionBashFrameworkConfigCallback() { fi } +defaultFrameworkConfig="$( + cat <<'EOF' +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$" +# Source directories +FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" +) + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +EOF +)" + commandOptionParseFinished() { if [[ -z "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then BASH_FRAMEWORK_ENV_FILES=() @@ -1359,8 +1397,16 @@ commandOptionParseFinished() { # shellcheck disable=SC2034 BASH_FRAMEWORK_CONFIG_FILE="" # shellcheck source=/.framework-config - Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || - Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config file" + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || { + # load default template framework config + if [[ ! -f "${PERSISTENT_TMPDIR}/.framework-config" ]]; then + echo "${defaultFrameworkConfig}" > "${PERSISTENT_TMPDIR}/.framework-config" + fi + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${PERSISTENT_TMPDIR}" || { + Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config.default file" + } + Log::displayWarning "Command ${SCRIPT_NAME} - Load default .framework-config file - ${PERSISTENT_TMPDIR}/.framework-config" + } fi if [[ "${optionConfig}" = "1" ]]; then @@ -1373,7 +1419,7 @@ upgradeGithubReleaseCommand() { shift || true if [[ "${options_parse_cmd}" = "parse" ]]; then - optionVersionArg=--version + optionVersionArg="--version" local -i options_parse_optionParsedCountOptionVersionArg ((options_parse_optionParsedCountOptionVersionArg = 0)) || true local -i options_parse_optionParsedCountOptionCurrentVersion @@ -1420,6 +1466,7 @@ upgradeGithubReleaseCommand() { ((options_parse_argParsedCountTargetFileArg = 0)) || true local -i options_parse_argParsedCountGithubUrlPatternArg ((options_parse_argParsedCountGithubUrlPatternArg = 0)) || true + # shellcheck disable=SC2034 local -i options_parse_parsedArgIndex=0 while (($# > 0)); do local options_parse_arg="$1" @@ -1438,6 +1485,7 @@ upgradeGithubReleaseCommand() { return 1 fi ((++options_parse_optionParsedCountOptionVersionArg)) + # shellcheck disable=SC2034 optionVersionArg="$1" ;; # Option 2/18 @@ -1453,6 +1501,7 @@ upgradeGithubReleaseCommand() { return 1 fi ((++options_parse_optionParsedCountOptionCurrentVersion)) + # shellcheck disable=SC2034 optionCurrentVersion="$1" ;; # Option 3/18 @@ -1468,6 +1517,7 @@ upgradeGithubReleaseCommand() { return 1 fi ((++options_parse_optionParsedCountOptionExactVersion)) + # shellcheck disable=SC2034 optionExactVersion="$1" ;; # Option 4/18 @@ -1483,6 +1533,7 @@ upgradeGithubReleaseCommand() { return 1 fi ((++options_parse_optionParsedCountOptionMinimalVersion)) + # shellcheck disable=SC2034 optionMinimalVersion="$1" ;; # Option 5/18 @@ -1498,12 +1549,14 @@ upgradeGithubReleaseCommand() { return 1 fi ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 optionBashFrameworkConfig="$1" optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" ;; # Option 6/18 # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' --config) + # shellcheck disable=SC2034 optionConfig="1" if ((options_parse_optionParsedCountOptionConfig >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1514,6 +1567,7 @@ upgradeGithubReleaseCommand() { # Option 7/18 # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' --verbose | -v) + # shellcheck disable=SC2034 optionInfoVerbose="1" if ((options_parse_optionParsedCountOptionInfoVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1526,6 +1580,7 @@ upgradeGithubReleaseCommand() { # Option 8/18 # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vv) + # shellcheck disable=SC2034 optionDebugVerbose="1" if ((options_parse_optionParsedCountOptionDebugVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1538,6 +1593,7 @@ upgradeGithubReleaseCommand() { # Option 9/18 # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vvv) + # shellcheck disable=SC2034 optionTraceVerbose="1" if ((options_parse_optionParsedCountOptionTraceVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1563,6 +1619,7 @@ upgradeGithubReleaseCommand() { # Option 11/18 # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' --no-color) + # shellcheck disable=SC2034 optionNoColor="1" if ((options_parse_optionParsedCountOptionNoColor >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1589,6 +1646,7 @@ upgradeGithubReleaseCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 optionTheme="$1" optionThemeCallback "${options_parse_arg}" "${optionTheme}" updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" @@ -1596,6 +1654,7 @@ upgradeGithubReleaseCommand() { # Option 13/18 # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' --help | -h) + # shellcheck disable=SC2034 optionHelp="1" if ((options_parse_optionParsedCountOptionHelp >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1607,6 +1666,7 @@ upgradeGithubReleaseCommand() { # Option 14/18 # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' --version) + # shellcheck disable=SC2034 optionVersion="1" if ((options_parse_optionParsedCountOptionVersion >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1618,6 +1678,7 @@ upgradeGithubReleaseCommand() { # Option 15/18 # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' --quiet | -q) + # shellcheck disable=SC2034 optionQuiet="1" if ((options_parse_optionParsedCountOptionQuiet >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1644,6 +1705,7 @@ upgradeGithubReleaseCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 optionLogLevel="$1" optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" @@ -1661,6 +1723,7 @@ upgradeGithubReleaseCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 optionLogFile="$1" optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" @@ -1682,6 +1745,7 @@ upgradeGithubReleaseCommand() { return 1 fi ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 optionDisplayLevel="$1" optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" @@ -1704,6 +1768,7 @@ upgradeGithubReleaseCommand() { return 1 fi ((++options_parse_argParsedCountTargetFileArg)) + # shellcheck disable=SC2034 targetFileArg="${options_parse_arg}" targetFileArgCallback "${targetFileArg}" -- "${@:2}" # Argument 2/2 @@ -1714,6 +1779,7 @@ upgradeGithubReleaseCommand() { return 1 fi ((++options_parse_argParsedCountGithubUrlPatternArg)) + # shellcheck disable=SC2034 githubUrlPatternArg="${options_parse_arg}" githubUrlPatternArgCallback "${githubUrlPatternArg}" -- "${@:2}" else @@ -1751,87 +1817,107 @@ upgradeGithubReleaseCommand() { echo -e "${__HELP_TITLE_COLOR}ARGUMENTS:${__RESET_COLOR}" echo -e " ${__HELP_OPTION_COLOR}targetFile${__HELP_NORMAL} {single} (mandatory)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(the\ binary\ downloaded\ will\ e\ written\ to\ this\ file\ path.\ Ensure\ the\ path\ is\ writable.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}githubUrlPattern${__HELP_NORMAL} {single} (mandatory)" local -a helpArray + # shellcheck disable=SC2054 helpArray=($'the url pattern to use to download the binary, see examples below. \n @version@ is template variable that will be replaced by the latest \n version tag found on github.') echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}VERSION MANAGEMENT:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--version-arg ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--version-arg ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=($'The argument that will be provided to the currently installed binary to check the version of the software. \n This parameter is needed if \E[2;97m--minimal-version\E[0m argument is used and is different than default value (\E[2;97m--version\E[0m).') echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo ' Default value: --version' - printf " %b\n" "${__HELP_OPTION_COLOR}--current-version${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-c ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--current-version${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-c ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=($'Sometimes the command to retrieve the version is complicated. \n Some command needs you to parse json or other commands provides multiple sub command versions. In this case you can provide the version you currently have, see examples below.') echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--exact-version${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-e ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--exact-version${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-e ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=($'if provided and currently installed binary is not this \E[2;97mexactVersion\E[0m, \n This exact version of the binary will be installed.') echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--minimal-version${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-m ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--minimal-version${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-m ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=($'if provided and currently installed binary is below this \E[2;97mminimalVersion\E[0m, \n a new version of the binary will be installed. \n If this argument is not provided, the latest binary is unconditionally downloaded from github.') echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(use\ alternate\ bash\ framework\ configuration.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ configuration) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} (optional)" + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Load\ the\ specified\ env\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(choose\ color\ theme\ \(default\,\ default-force\ or\ noColor\)\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ this\ command\ help) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Print\ version\ information\ and\ quit) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(set\ display\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e """ @@ -1888,14 +1974,6 @@ Download oq specific version correctly retrieving the oq version and not the jq return 1 fi } -declare copyrightBeginYear="2020" -# default values -declare targetFileArg="" -declare githubUrlPatternArg="" -declare optionVersionArg="--version" -declare optionCurrentVersion="" -declare optionMinimalVersion="" -declare optionExactVersion="" upgradeGithubReleaseCommandCallback() { if [[ -n "${optionExactVersion}" && -n "${optionMinimalVersion}" ]]; then diff --git a/bin/waitForIt b/bin/waitForIt index cdd869f5..41068c7f 100755 --- a/bin/waitForIt +++ b/bin/waitForIt @@ -47,6 +47,7 @@ declare -a BASH_FRAMEWORK_ARGV=("$@") declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") # @see https://unix.stackexchange.com/a/386856 +# shellcheck disable=SC2317 interruptManagement() { # restore SIGINT handler trap - INT @@ -76,6 +77,7 @@ TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX) export TMPDIR # temp dir cleaning +# shellcheck disable=SC2317 cleanOnExit() { if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" @@ -150,7 +152,7 @@ Array::wrap() { local -i argNoAnsiLength=0 while (($# > 0)); do - argNoAnsi="$(echo "${arg}" | Filters::removeAnsiCodes)" + argNoAnsi="$(echo "${arg%%*( )}" | Filters::removeAnsiCodes)" ((argNoAnsiLength = ${#argNoAnsi})) || true if (($# < 1 && argNoAnsiLength == 0)); then break @@ -173,7 +175,7 @@ Array::wrap() { if ((currentLineLength == 0 && firstLine == 0)); then echo -n "${indentStr}" fi - echo -e -n "${arg}" + echo -e -n "${arg}" | sed 's/[\t ]*$//g' needEcho="1" ((currentLineLength += argNoAnsiLength)) ((glueLength = ${#glue})) || true @@ -192,7 +194,7 @@ Array::wrap() { fi local -i length ((length = maxLineLength - currentLineLength)) || true - echo -e "${arg:0:${length}}" + echo -e "${arg:0:${length}}" | sed 's/[\t ]*$//g' ((currentLineLength = 0)) || true ((glueLength = 0)) || true arg="${arg:${length}}" @@ -200,7 +202,7 @@ Array::wrap() { else # arg cannot be stored on a whole line, so we add it on next line as a whole echo - echo -e -n "${indentStr}${arg}" + echo -e -n "${indentStr}${arg}" | sed 's/[\t ]*$//g' ((glueLength = ${#glue})) || true ((currentLineLength = argNoAnsiLength)) arg="" # allows to go to next arg @@ -340,6 +342,15 @@ Log::displayInfo() { Log::logInfo "$1" "${type}" } +# @description Display message using warning color (yellow) +# @arg $1 message:String the message to display +Log::displayWarning() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then + echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 + fi + Log::logWarning "$1" +} + # @description Display message using error color (red) and exit immediately with error status 1 # @arg $1 message:String the message to display Log::fatal() { @@ -421,6 +432,7 @@ UI::drawLine() { # @set __HELP_EXAMPLE String to remove # @set __HELP_TITLE String to remove # @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 UI::theme() { local theme="${1-default}" if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then @@ -436,46 +448,46 @@ UI::theme() { ;; esac if [[ "${theme}" = "default" ]]; then - export BASH_FRAMEWORK_THEME="default" + BASH_FRAMEWORK_THEME="default" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __SKIPPED_COLOR='\e[33m' # Yellow - export __DEBUG_COLOR='\e[37m' # Grey - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __HELP_TITLE_COLOR="\e[1;37m" # Bold - export __HELP_OPTION_COLOR="\e[1;34m" # Blue + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color + __RESET_COLOR='\e[0m' # Reset Color # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" + __HELP_TITLE="$(echo -e "\e[1;37m")" # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" + __HELP_NORMAL="$(echo -e "\033[0m")" else - export BASH_FRAMEWORK_THEME="noColor" + BASH_FRAMEWORK_THEME="noColor" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __DEBUG_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __HELP_TITLE_COLOR='' - export __HELP_OPTION_COLOR='' + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' fi } @@ -492,8 +504,8 @@ Compiler::Facade::requireCommandBinDir() { # @description check if tty (interactive mode) is active # @noargs # @exitcode 1 if tty not active -# @environment NON_INTERACTIVE if 1 consider as not interactive even if environement is interactive -# @environment INTERACTIVE if 1 consider as interactive even if environement is not interactive +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive # @stderr diagnostic information + help if second argument is provided Assert::tty() { if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then @@ -712,6 +724,14 @@ Log::logMessage() { fi } +# @description log message to file +# @arg $1 message:String the message to display +Log::logWarning() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then + Log::logMessage "${2:-WARNING}" "$1" + fi +} + # @description To be called before logging in the log file # @arg $1 file:string log file name # @arg $2 maxLogFilesCount:int maximum number of log files @@ -810,15 +830,6 @@ Framework::createTempFile() { mktemp -p "${TMPDIR:-/tmp}" -t "${1:-}.XXXXXXXXXXXX" } -# @description Display message using warning color (yellow) -# @arg $1 message:String the message to display -Log::displayWarning() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then - echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 - fi - Log::logWarning "$1" -} - # @description log message to file # @arg $1 message:String the message to display Log::logSkipped() { @@ -827,14 +838,6 @@ Log::logSkipped() { fi } -# @description log message to file -# @arg $1 message:String the message to display -Log::logWarning() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then - Log::logMessage "${2:-WARNING}" "$1" - fi -} - # FUNCTIONS facade_main_d396e2bc1f6e43a7b79e8a25ad41ac25() { @@ -860,18 +863,33 @@ Log::requireLoad Compiler::Facade::requireCommandBinDir # @require Compiler::Facade::requireCommandBinDir -# shellcheck disable=SC2154 -# shellcheck disable=SC2317 +# shellcheck disable=SC2034 + +# option values +declare -a commandArgs=() +declare optionHostOrIp="" +declare optionPort="" +declare optionStrict="0" +declare optionTimeout="15" +declare optionAlgo="" +declare -a availableAlgos=( + "timeoutV1WithNc" + "timeoutV2WithNc" + "whileLoopWithNc" + "timeoutV1WithTcp" + "timeoutV2WithTcp" + "whileLoopWithTcp" +) +# other values +declare copyrightBeginYear="2020" declare -a BASH_FRAMEWORK_ARGV_FILTERED=() copyrightCallback() { - local years - years="$(date +%Y)" - if [[ -n "${copyrightBeginYear}" && "${copyrightBeginYear}" != "${years}" ]]; then - years="${copyrightBeginYear}-${years}" + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" fi - echo "Copyright (c) ${years} François Chastanet" + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" } # shellcheck disable=SC2317 # if function is overridden @@ -1045,6 +1063,29 @@ optionBashFrameworkConfigCallback() { fi } +defaultFrameworkConfig="$( + cat <<'EOF' +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$" +# Source directories +FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" +) + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +EOF +)" + commandOptionParseFinished() { if [[ -z "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then BASH_FRAMEWORK_ENV_FILES=() @@ -1064,8 +1105,16 @@ commandOptionParseFinished() { # shellcheck disable=SC2034 BASH_FRAMEWORK_CONFIG_FILE="" # shellcheck source=/.framework-config - Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || - Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config file" + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || { + # load default template framework config + if [[ ! -f "${PERSISTENT_TMPDIR}/.framework-config" ]]; then + echo "${defaultFrameworkConfig}" > "${PERSISTENT_TMPDIR}/.framework-config" + fi + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${PERSISTENT_TMPDIR}" || { + Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config.default file" + } + Log::displayWarning "Command ${SCRIPT_NAME} - Load default .framework-config file - ${PERSISTENT_TMPDIR}/.framework-config" + } fi if [[ "${optionConfig}" = "1" ]]; then @@ -1073,11 +1122,20 @@ commandOptionParseFinished() { fi } +optionTimeoutCallback() { + if [[ ! "${optionTimeout}" =~ ^[0-9]+$ ]]; then + Log::fatal "${SCRIPT_NAME} - invalid timeout option - must be greater or equal to 0" + fi +} + waitForItCommand() { local options_parse_cmd="$1" shift || true if [[ "${options_parse_cmd}" = "parse" ]]; then + optionTimeout="15" + local -i options_parse_optionParsedCountOptionTimeout + ((options_parse_optionParsedCountOptionTimeout = 0)) || true local -i options_parse_optionParsedCountOptionHostOrIp ((options_parse_optionParsedCountOptionHostOrIp = 0)) || true local -i options_parse_optionParsedCountOptionPort @@ -1087,9 +1145,6 @@ waitForItCommand() { optionStrict="0" local -i options_parse_optionParsedCountOptionStrict ((options_parse_optionParsedCountOptionStrict = 0)) || true - optionTimeout=15 - local -i options_parse_optionParsedCountOptionTimeout - ((options_parse_optionParsedCountOptionTimeout = 0)) || true local -i options_parse_optionParsedCountOptionBashFrameworkConfig ((options_parse_optionParsedCountOptionBashFrameworkConfig = 0)) || true optionConfig="0" @@ -1126,12 +1181,30 @@ waitForItCommand() { ((options_parse_optionParsedCountOptionDisplayLevel = 0)) || true local -i options_parse_argParsedCountCommandArgs ((options_parse_argParsedCountCommandArgs = 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/19 + # Option optionTimeout --timeout|-t variableType String min 0 max 1 authorizedValues '' regexp '' + --timeout | -t) + 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_optionParsedCountOptionTimeout >= 1)); then + Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" + return 1 + fi + ((++options_parse_optionParsedCountOptionTimeout)) + # shellcheck disable=SC2034 + optionTimeout="$1" + optionTimeoutCallback "${options_parse_arg}" "${optionTimeout}" + ;; + # Option 2/19 # Option optionHostOrIp --host|-i variableType String min 1 max 1 authorizedValues '' regexp '' --host | -i) shift @@ -1144,9 +1217,10 @@ waitForItCommand() { return 1 fi ((++options_parse_optionParsedCountOptionHostOrIp)) + # shellcheck disable=SC2034 optionHostOrIp="$1" ;; - # Option 2/19 + # Option 3/19 # Option optionPort --port|-p variableType String min 1 max 1 authorizedValues '' regexp '' --port | -p) shift @@ -1159,10 +1233,11 @@ waitForItCommand() { return 1 fi ((++options_parse_optionParsedCountOptionPort)) + # shellcheck disable=SC2034 optionPort="$1" optionPortCallback "${options_parse_arg}" "${optionPort}" ;; - # Option 3/19 + # Option 4/19 # Option optionAlgo --algorithm|--algo variableType String min 0 max 1 authorizedValues '' regexp '' --algorithm | --algo) shift @@ -1175,12 +1250,14 @@ waitForItCommand() { return 1 fi ((++options_parse_optionParsedCountOptionAlgo)) + # shellcheck disable=SC2034 optionAlgo="$1" optionAlgoCallback "${options_parse_arg}" "${optionAlgo}" ;; - # Option 4/19 + # Option 5/19 # Option optionStrict --exec-command-on-success-only|--strict|-s variableType Boolean min 0 max 1 authorizedValues '' regexp '' --exec-command-on-success-only | --strict | -s) + # shellcheck disable=SC2034 optionStrict="1" if ((options_parse_optionParsedCountOptionStrict >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1188,22 +1265,6 @@ waitForItCommand() { fi ((++options_parse_optionParsedCountOptionStrict)) ;; - # Option 5/19 - # Option optionTimeout --timeout|-t variableType String min 0 max 1 authorizedValues '' regexp '' - --timeout | -t) - 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_optionParsedCountOptionTimeout >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" - return 1 - fi - ((++options_parse_optionParsedCountOptionTimeout)) - optionTimeout="$1" - optionTimeoutCallback "${options_parse_arg}" "${optionTimeout}" - ;; # Option 6/19 # Option optionBashFrameworkConfig --bash-framework-config variableType String min 0 max 1 authorizedValues '' regexp '' --bash-framework-config) @@ -1217,12 +1278,14 @@ waitForItCommand() { return 1 fi ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 optionBashFrameworkConfig="$1" optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" ;; # Option 7/19 # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' --config) + # shellcheck disable=SC2034 optionConfig="1" if ((options_parse_optionParsedCountOptionConfig >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1233,6 +1296,7 @@ waitForItCommand() { # Option 8/19 # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' --verbose | -v) + # shellcheck disable=SC2034 optionInfoVerbose="1" if ((options_parse_optionParsedCountOptionInfoVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1245,6 +1309,7 @@ waitForItCommand() { # Option 9/19 # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vv) + # shellcheck disable=SC2034 optionDebugVerbose="1" if ((options_parse_optionParsedCountOptionDebugVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1257,6 +1322,7 @@ waitForItCommand() { # Option 10/19 # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vvv) + # shellcheck disable=SC2034 optionTraceVerbose="1" if ((options_parse_optionParsedCountOptionTraceVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1282,6 +1348,7 @@ waitForItCommand() { # Option 12/19 # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' --no-color) + # shellcheck disable=SC2034 optionNoColor="1" if ((options_parse_optionParsedCountOptionNoColor >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1308,6 +1375,7 @@ waitForItCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 optionTheme="$1" optionThemeCallback "${options_parse_arg}" "${optionTheme}" updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" @@ -1315,6 +1383,7 @@ waitForItCommand() { # Option 14/19 # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' --help | -h) + # shellcheck disable=SC2034 optionHelp="1" if ((options_parse_optionParsedCountOptionHelp >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1326,6 +1395,7 @@ waitForItCommand() { # Option 15/19 # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' --version) + # shellcheck disable=SC2034 optionVersion="1" if ((options_parse_optionParsedCountOptionVersion >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1337,6 +1407,7 @@ waitForItCommand() { # Option 16/19 # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' --quiet | -q) + # shellcheck disable=SC2034 optionQuiet="1" if ((options_parse_optionParsedCountOptionQuiet >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1363,6 +1434,7 @@ waitForItCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 optionLogLevel="$1" optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" @@ -1380,6 +1452,7 @@ waitForItCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 optionLogFile="$1" optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" @@ -1401,6 +1474,7 @@ waitForItCommand() { return 1 fi ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 optionDisplayLevel="$1" optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" @@ -1413,14 +1487,11 @@ waitForItCommand() { # Technical if - never reached : # Argument 1/1 - # Argument commandArgs min 0 max 1 authorizedValues '' regexp '' - elif ((options_parse_parsedArgIndex >= 0 && options_parse_parsedArgIndex < 1)); then - if ((options_parse_argParsedCountCommandArgs >= 1)); then - Log::displayError "Command ${SCRIPT_NAME} - Argument commandArgs - Maximum number of argument occurrences reached(1)" - return 1 - fi + # Argument commandArgs min 0 max -1 authorizedValues '' regexp '' + elif ((options_parse_parsedArgIndex >= 0)); then ((++options_parse_argParsedCountCommandArgs)) - commandArgs="${options_parse_arg}" + # shellcheck disable=SC2034 + commandArgs+=("${options_parse_arg}") else unknownOption "${options_parse_arg}" fi @@ -1448,92 +1519,112 @@ waitForItCommand() { echo -e "$(Array::wrap " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" "${SCRIPT_NAME}" "[OPTIONS]" "[ARGUMENTS]")" echo -e "$(Array::wrap " " 80 2 "${__HELP_TITLE_COLOR}USAGE:${__RESET_COLOR}" \ "${SCRIPT_NAME}" \ - "--host|-i " "--port|-p " "[--algorithm|--algo ]" "[--exec-command-on-success-only|--strict|-s]" "[--timeout|-t ]" "[--bash-framework-config ]" "[--config]" "[--verbose|-v]" "[-vv]" "[-vvv]" "[--env-file ]" "[--no-color]" "[--theme ]" "[--help|-h]" "[--version]" "[--quiet|-q]" "[--log-level ]" "[--log-file ]" "[--display-level ]")" + "[--timeout|-t ]" "--host|-i " "--port|-p " "[--algorithm|--algo ]" "[--exec-command-on-success-only|--strict|-s]" "[--bash-framework-config ]" "[--config]" "[--verbose|-v]" "[-vv]" "[-vvv]" "[--env-file ]" "[--no-color]" "[--theme ]" "[--help|-h]" "[--version]" "[--quiet|-q]" "[--log-level ]" "[--log-file ]" "[--display-level ]")" echo echo -e "${__HELP_TITLE_COLOR}ARGUMENTS:${__RESET_COLOR}" - echo -e " [${__HELP_OPTION_COLOR}commandArgs${__HELP_NORMAL} {single}]" + echo -e " [${__HELP_OPTION_COLOR}commandArgs${__HELP_NORMAL} {list} (optional)]" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Execute\ command\ with\ args\ after\ the\ test\ finishes\ or\ exit\ with\ status\ code\ if\ no\ command\ provided.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--host${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-i ${__HELP_NORMAL} (mandatory)" + echo -e " ${__HELP_OPTION_COLOR}--timeout${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-t ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 + helpArray=(Timeout\ in\ seconds\,\ zero\ for\ no\ timeout.) + echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" + echo ' Default value: 15' + echo -e " ${__HELP_OPTION_COLOR}--host${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-i ${__HELP_NORMAL} {single} (mandatory)" + local -a helpArray + # shellcheck disable=SC2054 helpArray=(Host\ or\ IP\ under\ test.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--port${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-p ${__HELP_NORMAL} (mandatory)" + echo -e " ${__HELP_OPTION_COLOR}--port${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-p ${__HELP_NORMAL} {single} (mandatory)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(TCP\ port\ under\ test.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--algorithm${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}--algo ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--algorithm${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}--algo ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=($'Algorithm to use Check algorithms list below. \n (default: automatic selection based on commands availability and timeout option value).') echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--exec-command-on-success-only${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}--strict${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-s${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--exec-command-on-success-only${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}--strict${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-s${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Only\ execute\ sub-command\ if\ the\ test\ succeeds.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--timeout${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-t ${__HELP_NORMAL} (optional) (at most 1 times)" - local -a helpArray - helpArray=(Timeout\ in\ seconds\,\ zero\ for\ no\ timeout.) - echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - echo ' Default value: 15' echo echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(use\ alternate\ bash\ framework\ configuration.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ configuration) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} (optional)" + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Load\ the\ specified\ env\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(choose\ color\ theme\ \(default\,\ default-force\ or\ noColor\)\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ this\ command\ help) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Print\ version\ information\ and\ quit) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(set\ display\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e """ @@ -1580,12 +1671,6 @@ optionPortCallback() { fi } -optionTimeoutCallback() { - if [[ ! "${optionTimeout}" =~ ^[0-9]+$ ]]; then - Log::fatal "${SCRIPT_NAME} - invalid timeout option - must be greater or equal to 0" - fi -} - optionAlgoCallback() { if ! Array::contains "${optionAlgo}" "${availableAlgos[@]}"; then Log::fatal "${SCRIPT_NAME} - invalid algorithm '${optionAlgo}'" @@ -1598,23 +1683,13 @@ commandCallback() { fi } -# default values -declare -a commandArgs=() -declare copyrightBeginYear="2020" -declare optionTimeout="15" -declare optionAlgo="" -declare -a availableAlgos=(timeoutV1WithNc -timeoutV2WithNc -whileLoopWithNc -timeoutV1WithTcp -timeoutV2WithTcp -whileLoopWithTcp) +waitForItCommand parse "${BASH_FRAMEWORK_ARGV[@]}" # Use this script to test if a given TCP host/port are available # https://github.com/vishnubob/wait-for-it -waitForItCommand parse "${BASH_FRAMEWORK_ARGV[@]}" run() { + # shellcheck disable=SC2317 usingTcp() { # couldn't find another way to mock this part if [[ -n "${WAIT_FOR_IT_MOCKED_TCP:-}" ]]; then @@ -1624,6 +1699,7 @@ run() { fi } + # shellcheck disable=SC2317 usingNc() { nc -z "${optionHostOrIp}" "${optionPort}" -w 1 2>&1 } @@ -1641,7 +1717,7 @@ run() { Log::displayInfo "${SCRIPT_NAME} - ${optionHostOrIp}:${optionPort} is available after $((SECONDS - start_ts)) seconds" break fi - if (( optionTimeout!=0 && SECONDS - start_ts >= optionTimeout)); then + if ((optionTimeout != 0 && SECONDS - start_ts >= optionTimeout)); then if [[ "${reportTimeout}" = "1" ]]; then Log::displayError "${SCRIPT_NAME} - timeout for ${optionHostOrIp}:${optionPort} occurred after $((SECONDS - start_ts)) seconds" fi @@ -1652,6 +1728,7 @@ run() { return 0 } + # shellcheck disable=SC2317 timeoutCommand() { local timeoutVersion="$1" local commandToUse="$2" @@ -1664,7 +1741,7 @@ run() { # compute timeout command local -a timeoutCmd=(timeout) - if [[ "${timeoutVersion}" = "v1" ]]; then + if [[ "${timeoutVersion}" = "v1" ]]; then # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 timeoutCmd+=("-t") fi @@ -1688,21 +1765,27 @@ run() { # -------------------------------------- # ALGORITHMS + # shellcheck disable=SC2317 timeoutV1WithNc() { timeoutCommand "v1" "usingNc" } + # shellcheck disable=SC2317 timeoutV2WithNc() { timeoutCommand "v2" "usingNc" } + # shellcheck disable=SC2317 whileLoopWithNc() { whileLoop "usingNc" "1" } + # shellcheck disable=SC2317 timeoutV1WithTcp() { timeoutCommand "v1" "usingTcp" } + # shellcheck disable=SC2317 timeoutV2WithTcp() { timeoutCommand "v2" "usingTcp" } + # shellcheck disable=SC2317 whileLoopWithTcp() { whileLoop "usingTcp" "1" } @@ -1720,9 +1803,9 @@ run() { command="WithNc" fi - if (( optionTimeout > 0 )); then + if ((optionTimeout > 0)); then if Assert::commandExists timeout &>/dev/null; then - if timeout --help 2>&1 | grep -q -E -e '--timeout '; then + if timeout --help 2>&1 | grep -q -E -e '--timeout '; then echo "timeoutV1${command}" else echo "timeoutV2${command}" diff --git a/bin/waitForMysql b/bin/waitForMysql index 2f3cd713..6516a2c9 100755 --- a/bin/waitForMysql +++ b/bin/waitForMysql @@ -47,6 +47,7 @@ declare -a BASH_FRAMEWORK_ARGV=("$@") declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") # @see https://unix.stackexchange.com/a/386856 +# shellcheck disable=SC2317 interruptManagement() { # restore SIGINT handler trap - INT @@ -76,6 +77,7 @@ TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX) export TMPDIR # temp dir cleaning +# shellcheck disable=SC2317 cleanOnExit() { if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" @@ -134,7 +136,7 @@ Array::wrap() { local -i argNoAnsiLength=0 while (($# > 0)); do - argNoAnsi="$(echo "${arg}" | Filters::removeAnsiCodes)" + argNoAnsi="$(echo "${arg%%*( )}" | Filters::removeAnsiCodes)" ((argNoAnsiLength = ${#argNoAnsi})) || true if (($# < 1 && argNoAnsiLength == 0)); then break @@ -157,7 +159,7 @@ Array::wrap() { if ((currentLineLength == 0 && firstLine == 0)); then echo -n "${indentStr}" fi - echo -e -n "${arg}" + echo -e -n "${arg}" | sed 's/[\t ]*$//g' needEcho="1" ((currentLineLength += argNoAnsiLength)) ((glueLength = ${#glue})) || true @@ -176,7 +178,7 @@ Array::wrap() { fi local -i length ((length = maxLineLength - currentLineLength)) || true - echo -e "${arg:0:${length}}" + echo -e "${arg:0:${length}}" | sed 's/[\t ]*$//g' ((currentLineLength = 0)) || true ((glueLength = 0)) || true arg="${arg:${length}}" @@ -184,7 +186,7 @@ Array::wrap() { else # arg cannot be stored on a whole line, so we add it on next line as a whole echo - echo -e -n "${indentStr}${arg}" + echo -e -n "${indentStr}${arg}" | sed 's/[\t ]*$//g' ((glueLength = ${#glue})) || true ((currentLineLength = argNoAnsiLength)) arg="" # allows to go to next arg @@ -324,6 +326,15 @@ Log::displayInfo() { Log::logInfo "$1" "${type}" } +# @description Display message using warning color (yellow) +# @arg $1 message:String the message to display +Log::displayWarning() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then + echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 + fi + Log::logWarning "$1" +} + # @description Display message using error color (red) and exit immediately with error status 1 # @arg $1 message:String the message to display Log::fatal() { @@ -405,6 +416,7 @@ UI::drawLine() { # @set __HELP_EXAMPLE String to remove # @set __HELP_TITLE String to remove # @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 UI::theme() { local theme="${1-default}" if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then @@ -420,46 +432,46 @@ UI::theme() { ;; esac if [[ "${theme}" = "default" ]]; then - export BASH_FRAMEWORK_THEME="default" + BASH_FRAMEWORK_THEME="default" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __SKIPPED_COLOR='\e[33m' # Yellow - export __DEBUG_COLOR='\e[37m' # Grey - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __HELP_TITLE_COLOR="\e[1;37m" # Bold - export __HELP_OPTION_COLOR="\e[1;34m" # Blue + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color + __RESET_COLOR='\e[0m' # Reset Color # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" + __HELP_TITLE="$(echo -e "\e[1;37m")" # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" + __HELP_NORMAL="$(echo -e "\033[0m")" else - export BASH_FRAMEWORK_THEME="noColor" + BASH_FRAMEWORK_THEME="noColor" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __DEBUG_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __HELP_TITLE_COLOR='' - export __HELP_OPTION_COLOR='' + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' fi } @@ -476,8 +488,8 @@ Compiler::Facade::requireCommandBinDir() { # @description check if tty (interactive mode) is active # @noargs # @exitcode 1 if tty not active -# @environment NON_INTERACTIVE if 1 consider as not interactive even if environement is interactive -# @environment INTERACTIVE if 1 consider as interactive even if environement is not interactive +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive # @stderr diagnostic information + help if second argument is provided Assert::tty() { if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then @@ -696,6 +708,14 @@ Log::logMessage() { fi } +# @description log message to file +# @arg $1 message:String the message to display +Log::logWarning() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then + Log::logMessage "${2:-WARNING}" "$1" + fi +} + # @description To be called before logging in the log file # @arg $1 file:string log file name # @arg $2 maxLogFilesCount:int maximum number of log files @@ -794,15 +814,6 @@ Framework::createTempFile() { mktemp -p "${TMPDIR:-/tmp}" -t "${1:-}.XXXXXXXXXXXX" } -# @description Display message using warning color (yellow) -# @arg $1 message:String the message to display -Log::displayWarning() { - if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then - echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 - fi - Log::logWarning "$1" -} - # @description log message to file # @arg $1 message:String the message to display Log::logSkipped() { @@ -827,14 +838,6 @@ Array::contains() { return 1 } -# @description log message to file -# @arg $1 message:String the message to display -Log::logWarning() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then - Log::logMessage "${2:-WARNING}" "$1" - fi -} - # FUNCTIONS facade_main_665f5dabe75f418ea1c10f53fac6da5e() { @@ -860,18 +863,26 @@ Log::requireLoad Compiler::Facade::requireCommandBinDir # @require Compiler::Facade::requireCommandBinDir -# shellcheck disable=SC2154 -# shellcheck disable=SC2317 +# shellcheck disable=SC2034 + +# default values +declare defaultTimeout="15" +# option values +declare optionTimeout="${defaultTimeout}" +declare mysqlHostArg="" +declare mysqlPortArg="" +declare mysqlUserArg="" +declare mysqlPasswordArg="" +# default values +declare copyrightBeginYear="2020" declare -a BASH_FRAMEWORK_ARGV_FILTERED=() copyrightCallback() { - local years - years="$(date +%Y)" - if [[ -n "${copyrightBeginYear}" && "${copyrightBeginYear}" != "${years}" ]]; then - years="${copyrightBeginYear}-${years}" + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" fi - echo "Copyright (c) ${years} François Chastanet" + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" } # shellcheck disable=SC2317 # if function is overridden @@ -1045,6 +1056,29 @@ optionBashFrameworkConfigCallback() { fi } +defaultFrameworkConfig="$( + cat <<'EOF' +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$" +# Source directories +FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" +) + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +EOF +)" + commandOptionParseFinished() { if [[ -z "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then BASH_FRAMEWORK_ENV_FILES=() @@ -1064,8 +1098,16 @@ commandOptionParseFinished() { # shellcheck disable=SC2034 BASH_FRAMEWORK_CONFIG_FILE="" # shellcheck source=/.framework-config - Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || - Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config file" + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || { + # load default template framework config + if [[ ! -f "${PERSISTENT_TMPDIR}/.framework-config" ]]; then + echo "${defaultFrameworkConfig}" > "${PERSISTENT_TMPDIR}/.framework-config" + fi + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${PERSISTENT_TMPDIR}" || { + Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config.default file" + } + Log::displayWarning "Command ${SCRIPT_NAME} - Load default .framework-config file - ${PERSISTENT_TMPDIR}/.framework-config" + } fi if [[ "${optionConfig}" = "1" ]]; then @@ -1073,12 +1115,18 @@ commandOptionParseFinished() { fi } +optionTimeoutCallback() { + if [[ ! "${optionTimeout}" =~ ^[0-9]+$ ]]; then + Log::fatal "${SCRIPT_NAME} - invalid timeout option - must be greater or equal to 0" + fi +} + waitForMysqlCommand() { local options_parse_cmd="$1" shift || true if [[ "${options_parse_cmd}" = "parse" ]]; then - optionTimeout=15 + optionTimeout="15" local -i options_parse_optionParsedCountOptionTimeout ((options_parse_optionParsedCountOptionTimeout = 0)) || true local -i options_parse_optionParsedCountOptionBashFrameworkConfig @@ -1123,6 +1171,7 @@ waitForMysqlCommand() { ((options_parse_argParsedCountMysqlUserArg = 0)) || true local -i options_parse_argParsedCountMysqlPasswordArg ((options_parse_argParsedCountMysqlPasswordArg = 0)) || true + # shellcheck disable=SC2034 local -i options_parse_parsedArgIndex=0 while (($# > 0)); do local options_parse_arg="$1" @@ -1141,6 +1190,7 @@ waitForMysqlCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTimeout)) + # shellcheck disable=SC2034 optionTimeout="$1" optionTimeoutCallback "${options_parse_arg}" "${optionTimeout}" ;; @@ -1157,12 +1207,14 @@ waitForMysqlCommand() { return 1 fi ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 optionBashFrameworkConfig="$1" optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" ;; # Option 3/15 # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' --config) + # shellcheck disable=SC2034 optionConfig="1" if ((options_parse_optionParsedCountOptionConfig >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1173,6 +1225,7 @@ waitForMysqlCommand() { # Option 4/15 # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' --verbose | -v) + # shellcheck disable=SC2034 optionInfoVerbose="1" if ((options_parse_optionParsedCountOptionInfoVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1185,6 +1238,7 @@ waitForMysqlCommand() { # Option 5/15 # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vv) + # shellcheck disable=SC2034 optionDebugVerbose="1" if ((options_parse_optionParsedCountOptionDebugVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1197,6 +1251,7 @@ waitForMysqlCommand() { # Option 6/15 # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vvv) + # shellcheck disable=SC2034 optionTraceVerbose="1" if ((options_parse_optionParsedCountOptionTraceVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1222,6 +1277,7 @@ waitForMysqlCommand() { # Option 8/15 # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' --no-color) + # shellcheck disable=SC2034 optionNoColor="1" if ((options_parse_optionParsedCountOptionNoColor >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1248,6 +1304,7 @@ waitForMysqlCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 optionTheme="$1" optionThemeCallback "${options_parse_arg}" "${optionTheme}" updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" @@ -1255,6 +1312,7 @@ waitForMysqlCommand() { # Option 10/15 # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' --help | -h) + # shellcheck disable=SC2034 optionHelp="1" if ((options_parse_optionParsedCountOptionHelp >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1266,6 +1324,7 @@ waitForMysqlCommand() { # Option 11/15 # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' --version) + # shellcheck disable=SC2034 optionVersion="1" if ((options_parse_optionParsedCountOptionVersion >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1277,6 +1336,7 @@ waitForMysqlCommand() { # Option 12/15 # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' --quiet | -q) + # shellcheck disable=SC2034 optionQuiet="1" if ((options_parse_optionParsedCountOptionQuiet >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1303,6 +1363,7 @@ waitForMysqlCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 optionLogLevel="$1" optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" @@ -1320,6 +1381,7 @@ waitForMysqlCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 optionLogFile="$1" optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" @@ -1341,6 +1403,7 @@ waitForMysqlCommand() { return 1 fi ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 optionDisplayLevel="$1" optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" @@ -1363,6 +1426,7 @@ waitForMysqlCommand() { return 1 fi ((++options_parse_argParsedCountMysqlHostArg)) + # shellcheck disable=SC2034 mysqlHostArg="${options_parse_arg}" # Argument 2/4 # Argument mysqlPortArg min 1 max 1 authorizedValues '' regexp '' @@ -1372,6 +1436,7 @@ waitForMysqlCommand() { return 1 fi ((++options_parse_argParsedCountMysqlPortArg)) + # shellcheck disable=SC2034 mysqlPortArg="${options_parse_arg}" mysqlPortArgCallback "${mysqlPortArg}" -- "${@:2}" # Argument 3/4 @@ -1382,6 +1447,7 @@ waitForMysqlCommand() { return 1 fi ((++options_parse_argParsedCountMysqlUserArg)) + # shellcheck disable=SC2034 mysqlUserArg="${options_parse_arg}" # Argument 4/4 # Argument mysqlPasswordArg min 1 max 1 authorizedValues '' regexp '' @@ -1391,6 +1457,7 @@ waitForMysqlCommand() { return 1 fi ((++options_parse_argParsedCountMysqlPasswordArg)) + # shellcheck disable=SC2034 mysqlPasswordArg="${options_parse_arg}" else if [[ "${argOptDefaultBehavior}" = "0" ]]; then @@ -1434,83 +1501,102 @@ waitForMysqlCommand() { echo -e "${__HELP_TITLE_COLOR}ARGUMENTS:${__RESET_COLOR}" echo -e " ${__HELP_OPTION_COLOR}mysqlHost${__HELP_NORMAL} {single} (mandatory)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Mysql\ host\ name) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}mysqlPort${__HELP_NORMAL} {single} (mandatory)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Mysql\ port) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}mysqlUserArg${__HELP_NORMAL} {single} (mandatory)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Mysql\ user\ name) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}mysqlPasswordArg${__HELP_NORMAL} {single} (mandatory)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Mysql\ password) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo echo -e "${__HELP_TITLE_COLOR}OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--timeout${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-t ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--timeout${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-t ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Timeout\ in\ seconds\,\ zero\ for\ no\ timeout.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo ' Default value: 15' echo echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(use\ alternate\ bash\ framework\ configuration.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ configuration) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} (optional)" + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Load\ the\ specified\ env\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(choose\ color\ theme\ \(default\,\ default-force\ or\ noColor\)\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ this\ command\ help) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Print\ version\ information\ and\ quit) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(set\ display\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e """ @@ -1544,16 +1630,6 @@ mysqlPortArgCallback() { fi } -optionTimeoutCallback() { - if [[ ! "${optionTimeout}" =~ ^[0-9]+$ ]]; then - Log::fatal "${SCRIPT_NAME} - invalid timeout option - must be greater or equal to 0" - fi -} - -# default values -declare copyrightBeginYear="2020" -declare optionTimeout="15" - waitForMysqlCommand parse "${BASH_FRAMEWORK_ARGV[@]}" run() { @@ -1567,7 +1643,7 @@ run() { -u"${mysqlUserArg}" \ -p"${mysqlPasswordArg}" &>/dev/null); do (printf >&2 ".") - if (( optionTimeout!=0 && SECONDS - start_ts >= optionTimeout)); then + if ((optionTimeout != 0 && SECONDS - start_ts >= optionTimeout)); then (echo >&2 "") Log::displayError "${SCRIPT_NAME} - timeout for ${mysqlHostArg}:${mysqlPortArg} occurred after $((SECONDS - start_ts)) seconds" return 2 diff --git a/conf/cliProfiles/default.sh b/conf/cliProfiles/default.sh index de077a8d..4c8cf0d8 100755 --- a/conf/cliProfiles/default.sh +++ b/conf/cliProfiles/default.sh @@ -14,4 +14,4 @@ finalUserArg="www-data" # shellcheck disable=SC2034 # shellcheck disable=SC2154 -finalCommandArg=("//bin/bash") +finalCommandArg=("/bin/bash") diff --git a/conf/dbScripts/extractData b/conf/dbScripts/extractData index 6be11006..f31cf2d2 100755 --- a/conf/dbScripts/extractData +++ b/conf/dbScripts/extractData @@ -1,114 +1,28 @@ #!/usr/bin/env bash - -############################################################ -# INTERNAL USE ONLY -# USED BY bin/dbScriptAllDatabases sub scripts -# eg: src/DbScriptAllDatabases/extractData.sh -############################################################ - -##################################### -# GENERATED FILE FROM https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/DbScriptAllDatabases/extractData.sh +############################################################################### +# GENERATED FACADE FROM https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/DbScriptAllDatabases/extractData.sh # DO NOT EDIT IT -##################################### +# @generated +############################################################################### +# shellcheck disable=SC2288,SC2034 +# BIN_FILE=${FRAMEWORK_ROOT_DIR}/conf/dbScripts/extractData +# FACADE # ensure that no user aliases could interfere with # commands used in this script unalias -a || true - -# shellcheck disable=SC2034 -SCRIPT_NAME=${0##*/} -REAL_SCRIPT_FILE="$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")" -# shellcheck disable=SC2034 -CURRENT_DIR="$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")" && pwd -P)" -# shellcheck disable=SC2034 -COMMAND_BIN_DIR="${CURRENT_DIR}" - -if [[ -t 1 || -t 2 ]]; then - # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __SKIPPED_COLOR='\e[33m' # Yellow - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __DEBUG_COLOR='\e[37m' # Grey - # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color - # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[1;30m")" - # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" - # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" -else - # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __DEBUG_COLOR='' - # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' -fi - -################################################ -# Temp dir management -################################################ - -KEEP_TEMP_FILES="${KEEP_TEMP_FILES:-0}" -export KEEP_TEMP_FILES - -# PERSISTENT_TMPDIR is not deleted by traps -PERSISTENT_TMPDIR="${TMPDIR:-/tmp}/bash-framework" -export PERSISTENT_TMPDIR -mkdir -p "${PERSISTENT_TMPDIR}" - -# shellcheck disable=SC2034 -TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX)" -export TMPDIR - -# temp dir cleaning -cleanOnExit() { - if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then - Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" - elif [[ -n "${TMPDIR+xxx}" ]]; then - Log::displayDebug "KEEP_TEMP_FILES=0 removing temp files '${TMPDIR}'" - rm -Rf "${TMPDIR:-/tmp/fake}" >/dev/null 2>&1 - fi -} -trap cleanOnExit EXIT HUP QUIT ABRT TERM - -# @see https://unix.stackexchange.com/a/386856 -interruptManagement() { - # restore SIGINT handler - trap - INT - # ensure that Ctrl-C is trapped by this script and not by sub process - # report to the parent that we have indeed been interrupted - kill -s INT "$$" -} -trap interruptManagement INT +shopt -u expand_aliases # shellcheck disable=SC2034 ((failures = 0)) || true -shopt -s expand_aliases - # Bash will remember & return the highest exit code in a chain of pipes. # This way you can catch the error inside pipes, e.g. mysqldump | gzip set -o pipefail set -o errexit # Command Substitution can inherit errexit option since bash v4.4 -(shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit +shopt -s inherit_errexit || true # a log is generated when a command fails set -o errtrace @@ -121,111 +35,80 @@ export LC_ALL=POSIX export TERM=xterm-256color -#avoid interactive install +# avoid interactive install export DEBIAN_FRONTEND=noninteractive export DEBCONF_NONINTERACTIVE_SEEN=true -BASH_TOOLS_ROOT_DIR="$(cd "${CURRENT_DIR}/.." && pwd -P)" -if [[ -d "${BASH_TOOLS_ROOT_DIR}/vendor/bash-tools-framework/" ]]; then - FRAMEWORK_ROOT_DIR="$(cd "${BASH_TOOLS_ROOT_DIR}/vendor/bash-tools-framework" && pwd -P)" -else - # if the directory does not exist yet, give a value to FRAMEWORK_ROOT_DIR - FRAMEWORK_ROOT_DIR="${BASH_TOOLS_ROOT_DIR}/vendor/bash-tools-framework" -fi -export BASH_TOOLS_ROOT_DIR FRAMEWORK_ROOT_DIR - -if [[ -f "${HOME}/.bash-tools/.env" ]]; then - export BASH_FRAMEWORK_ENV_FILEPATH="${HOME}/.bash-tools/.env" -fi - +# store command arguments for later usage +# shellcheck disable=SC2034 +declare -a BASH_FRAMEWORK_ARGV=("$@") # shellcheck disable=SC2034 +declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") -COMMAND_BIN_DIR="${CURRENT_DIR}" -if [[ -n "${FRAMEWORK_ROOT_DIR+xxx}" ]]; then - # shellcheck disable=SC2034 - FRAMEWORK_SRC_DIR="${FRAMEWORK_ROOT_DIR}/src" - # shellcheck disable=SC2034 - FRAMEWORK_BIN_DIR="${FRAMEWORK_ROOT_DIR}/bin" - # shellcheck disable=SC2034 - FRAMEWORK_VENDOR_DIR="${FRAMEWORK_ROOT_DIR}/vendor" - # shellcheck disable=SC2034 - FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_ROOT_DIR}/vendor/bin" -fi - -Args::defaultHelp() { - local helpArg=$1 - shift || true - if ! Args::defaultHelpNoExit "${helpArg}" "$@"; then - exit 0 - fi -} - -# change display level to level argument -# if --verbose|-v option is parsed in arguments -Args::parseVerbose() { - local verboseDisplayLevel="$1" - declare -gx ARGS_VERBOSE=0 - shift || true - local status=1 - while true; do - if [[ "$1" = "--verbose" || "$1" = "-v" ]]; then - status=0 - ARGS_VERBOSE=1 - break - fi - shift || break - done - if [[ "${status}" = "0" ]]; then - export BASH_FRAMEWORK_DISPLAY_LEVEL=${verboseDisplayLevel} - fi - return "${status}" +# @see https://unix.stackexchange.com/a/386856 +# shellcheck disable=SC2317 +interruptManagement() { + # restore SIGINT handler + trap - INT + # ensure that Ctrl-C is trapped by this script and not by sub process + # report to the parent that we have indeed been interrupted + kill -s INT "$$" } +trap interruptManagement INT +SCRIPT_NAME=${0##*/} +REAL_SCRIPT_FILE="$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")" +CURRENT_DIR="$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")" && pwd -P)" -# remove elements from array -# Performance1 : version taken from https://stackoverflow.com/a/59030460 -# Performance2 : for multiple values to remove, prefer using Array::removeIf -Array::remove() { - local -n arrayRemoveArray=$1 - shift || true # $@ contains elements to remove - local -A valuesToRemoveKeys=() +################################################ +# Temp dir management +################################################ - # Tag items to remove - local del - for del in "$@"; do valuesToRemoveKeys[${del}]=1; done +KEEP_TEMP_FILES="${KEEP_TEMP_FILES:-0}" +export KEEP_TEMP_FILES - # remove items - local k - for k in "${!arrayRemoveArray[@]}"; do - if [[ -n "${valuesToRemoveKeys[${arrayRemoveArray[k]}]+xxx}" ]]; then - unset 'arrayRemoveArray[k]' - fi - done +# PERSISTENT_TMPDIR is not deleted by traps +PERSISTENT_TMPDIR="${TMPDIR:-/tmp}/bash-framework" +export PERSISTENT_TMPDIR +mkdir -p "${PERSISTENT_TMPDIR}" - # compaction (element re-indexing, because unset makes "holes" in array ) - arrayRemoveArray=("${arrayRemoveArray[@]}") +# shellcheck disable=SC2034 +TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX)" +export TMPDIR + +# temp dir cleaning +# shellcheck disable=SC2317 +cleanOnExit() { + if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then + Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" + elif [[ -n "${TMPDIR+xxx}" ]]; then + Log::displayDebug "KEEP_TEMP_FILES=0 removing temp files '${TMPDIR}'" + rm -Rf "${TMPDIR:-/tmp/fake}" >/dev/null 2>&1 + fi } +trap cleanOnExit EXIT HUP QUIT ABRT TERM -# Public: exits with message if current user is root -# -# **Exit**: code 1 if current user is root +# @description exits with message if current user is root +# @noargs +# @exitcode 1 if current user is root +# @stderr diagnostics information is displayed Assert::expectNonRootUser() { if [[ "$(id -u)" = "0" ]]; then Log::fatal "The script must not be run as root" fi } -# Public: get absolute conf file from specified conf folder deduced using these rules +# @description get absolute conf file from specified conf folder deduced using these rules # * from absolute file (ignores and ) # * relative to where script is executed (ignores and ) # * from home/.bash-tools/ # * from framework conf/ # -# **Arguments**: -# * $1 confFolder the directory name (not the path) to list -# * $2 conf file to use without extension -# * $3 the extension (sh by default) +# @arg $1 confFolder:String the directory name (not the path) to list +# @arg $2 conf:String file to use without extension +# @arg $3 extension:String the extension (.sh by default) # -# Returns absolute conf filename +# @stdout absolute conf filename +# @exitcode 1 if file is not found in any location Conf::getAbsoluteFile() { local confFolder="$1" local conf="$2" @@ -275,20 +158,17 @@ Conf::getAbsoluteFile() { return 1 } -# Public: create a new db instance +# @description create a new db instance +# Returns immediately if the instance is already initialized # -# **Arguments**: -# * $1 - (passed by reference) database instance to create -# * $2 - dsn profile - load the dsn.env profile -# absolute file is deduced using rules defined in Conf::getAbsoluteFile +# @arg $1 instanceNewInstance:&Map (passed by reference) database instance to use +# @arg $2 dsn:String dsn profile - load the dsn.env profile deduced using rules defined in Conf::getAbsoluteFile # -# **Example:** -# ```shell -# declare -Agx dbInstance -# Database::newInstance dbInstance "default.local" -# ``` +# @example +# declare -Agx dbInstance +# Database::newInstance dbInstance "default.local" # -# Returns immediately if the instance is already initialized +# @exitcode 1 if dns file not able to loaded Database::newInstance() { local -n instanceNewInstance=$1 local dsn="$2" @@ -336,18 +216,19 @@ Database::newInstance() { instanceNewInstance['INITIALIZED']=1 } -# Public: mysql query on a given db -# -# **Arguments**: -# * $1 (passed by reference) database instance to use -# * $2 sql query to execute. -# if not provided or empty, the command can be piped (eg: cat file.sql | Database::query ...) -# * _$3 (optional)_ the db name +# @description mysql query on a given db +# @warning could use QUERY_OPTIONS variable from dsn if defined +# @example +# cat file.sql | Database::query ... +# @arg $1 instanceQuery:&Map (passed by reference) database instance to use +# @arg $2 sqlQuery:String (optional) sql query or sql file to execute. if not provided or empty, the command can be piped +# @arg $3 dbName:String (optional) the db name # -# **Returns**: mysql command status code +# @exitcode mysql command status code Database::query() { local -n instanceQuery=$1 local -a mysqlCommand=() + local -a queryOptions mysqlCommand+=(mysql) mysqlCommand+=("--defaults-extra-file=${instanceQuery['AUTH_FILE']}") @@ -361,11 +242,9 @@ Database::query() { mysqlCommand+=("$3") fi # add optional sql query - if [[ -n "${2+x}" && -n "$2" ]]; then - if [[ ! -f "$2" ]]; then - mysqlCommand+=("-e") - mysqlCommand+=("$2") - fi + if [[ -n "${2+x}" && -n "$2" && ! -f "$2" ]]; then + mysqlCommand+=("-e") + mysqlCommand+=("$2") fi Log::displayDebug "$(printf "execute command: '%s'" "${mysqlCommand[*]}")" @@ -376,420 +255,446 @@ Database::query() { fi } -# Public: set the general options to use on mysql command to query the database +# @description set the general options to use on mysql command to query the database # Differs than setOptions in the way that these options could change each time # -# **Arguments**: -# * $1 - (passed by reference) database instance to use -# * $2 - options list +# @arg $1 instanceSetQueryOptions:&Map (passed by reference) database instance to use +# @arg $2 optionsList:String query options list Database::setQueryOptions() { local -n instanceSetQueryOptions=$1 # shellcheck disable=SC2034 instanceSetQueryOptions['QUERY_OPTIONS']="$2" } -# Public: by default we skip the column names +# @description by default we skip the column names # but sometimes we need column names to display some results # disable this option temporarily and then restore it to true # -# **Arguments**: -# * $1 - (passed by reference) database instance to use -# * $2 - 0 to disable, 1 to enable (hide column names) +# @arg $1 instanceSetQueryOptions:&Map (passed by reference) database instance to use +# @arg $2 skipColumnNames:Boolean 0 to disable, 1 to enable (hide column names) Database::skipColumnNames() { local -n instanceSkipColumnNames=$1 # shellcheck disable=SC2034 instanceSkipColumnNames['SKIP_COLUMN_NAMES']="$2" } -# lazy initialization -declare -g BASH_FRAMEWORK_CACHED_ENV_FILE -declare -g BASH_FRAMEWORK_DEFAULT_ENV_FILE +# @description Log namespace provides 2 kind of functions +# - Log::display* allows to display given message with +# given display level +# - Log::log* allows to log given message with +# given log level +# Log::display* functions automatically log the message too +# @see Env::requireLoad to load the display and log level from .env file -# load variables in order(from less specific to more specific) from : -# - ${FRAMEWORK_ROOT_DIR}/src/Env/testsData/.env file -# - ${FRAMEWORK_ROOT_DIR}/conf/.env file if exists -# - ~/.env file if exists -# - ~/.bash-tools/.env file if exists -# - BASH_FRAMEWORK_ENV_FILEPATH= -Env::load() { - if [[ "${BASH_FRAMEWORK_INITIALIZED:-0}" = "1" ]]; then - return 0 - fi - BASH_FRAMEWORK_CACHED_ENV_FILE="$(mktemp -p "${TMPDIR:-/tmp}" -t "env_vars.XXXXXXX")" - BASH_FRAMEWORK_DEFAULT_ENV_FILE="$(mktemp -p "${TMPDIR:-/tmp}" -t "default_env_file.XXXXXXX")" - # shellcheck source=src/Env/testsData/.env - ( - echo "BASH_FRAMEWORK_LOG_LEVEL=${BASH_FRAMEWORK_LOG_LEVEL:-0}" - echo "BASH_FRAMEWORK_DISPLAY_LEVEL=${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" - echo "BASH_FRAMEWORK_LOG_FILE=${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/${SCRIPT_NAME}.log}" - echo "BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" - ) >"${BASH_FRAMEWORK_DEFAULT_ENV_FILE}" - - ( - # reset temp file - echo >"${BASH_FRAMEWORK_CACHED_ENV_FILE}" - - # list .env files that need to be loaded - local -a files=() - if [[ -f "${BASH_FRAMEWORK_DEFAULT_ENV_FILE}" ]]; then - files+=("${BASH_FRAMEWORK_DEFAULT_ENV_FILE}") - fi - if [[ -f "${FRAMEWORK_ROOT_DIR}/conf/.env" && -r "${FRAMEWORK_ROOT_DIR}/conf/.env" ]]; then - files+=("${FRAMEWORK_ROOT_DIR}/conf/.env") - fi - if [[ -f "${HOME}/.env" && -r "${HOME}/.env" ]]; then - files+=("${HOME}/.env") - fi - local file - for file in "$@"; do - if [[ -f "${file}" && -r "${file}" ]]; then - files+=("${file}") - fi - done - # import custom .env file - if [[ -n "${BASH_FRAMEWORK_ENV_FILEPATH+xxx}" ]]; then - # load BASH_FRAMEWORK_ENV_FILEPATH - if [[ -f "${BASH_FRAMEWORK_ENV_FILEPATH}" && -r "${BASH_FRAMEWORK_ENV_FILEPATH}" ]]; then - files+=("${BASH_FRAMEWORK_ENV_FILEPATH}") - else - Log::displayWarning "env file not not found - ${BASH_FRAMEWORK_ENV_FILEPATH}" - fi - fi - - # add all files added as parameters - files+=("$@") - - # source each file in order - local file - for file in "${files[@]}"; do - # shellcheck source=src/Env/testsData/.env - source "${file}" || { - Log::displayWarning "Cannot load '${file}'" - } - done - - # copy only the variables to the tmp file - local varName overrideVarName - while IFS=$'\n' read -r varName; do - overrideVarName="OVERRIDE_${varName}" - if [[ -z ${!overrideVarName+xxx} ]]; then - echo "${varName}='${!varName}'" >>"${BASH_FRAMEWORK_CACHED_ENV_FILE}" - else - # variable is overridden - echo "${varName}='${!overrideVarName}'" >>"${BASH_FRAMEWORK_CACHED_ENV_FILE}" - fi - - # using awk deduce all variables that need to be copied in tmp file - # from less specific file to the most - done < <(awk -F= '!a[$1]++' "${files[@]}" | grep -v '^$\|^\s*\#' | cut -d= -f1) - ) || exit 1 - - # ensure all sourced variables will be exported - set -o allexport - - # Finally load the temp file to make the variables available in current script - # shellcheck source=src/Env/testsData/.env - source "${BASH_FRAMEWORK_CACHED_ENV_FILE}" - - set +o allexport - BASH_FRAMEWORK_INITIALIZED=1 -} - -Env::pathPrepend() { - local arg - for arg in "$@"; do - if [[ -d "${arg}" && ":${PATH}:" != *":${arg}:"* ]]; then - PATH="$(realpath "${arg}"):${PATH}" - fi - done -} - -# Public: log level off +# @description log level off export __LEVEL_OFF=0 -# Public: log level error +# @description log level error export __LEVEL_ERROR=1 -# Public: log level warning +# @description log level warning export __LEVEL_WARNING=2 -# Public: log level info +# @description log level info export __LEVEL_INFO=3 -# Public: log level success +# @description log level success export __LEVEL_SUCCESS=3 -# Public: log level debug +# @description log level debug export __LEVEL_DEBUG=4 -export __LEVEL_OFF -export __LEVEL_ERROR -export __LEVEL_WARNING -export __LEVEL_INFO -export __LEVEL_SUCCESS -export __LEVEL_DEBUG - -# Display message using debug color (grey) -# @param {String} $1 message +# @description verbose level off +export __VERBOSE_LEVEL_OFF=0 +# @description verbose level info +export __VERBOSE_LEVEL_INFO=1 +# @description verbose level info +export __VERBOSE_LEVEL_DEBUG=2 +# @description verbose level info +export __VERBOSE_LEVEL_TRACE=3 + +# @description Display message using debug color (grey) +# @arg $1 message:String the message to display Log::displayDebug() { - echo -e "${__DEBUG_COLOR}DEBUG - ${1}${__RESET_COLOR}" >&2 + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_DEBUG)); then + echo -e "${__DEBUG_COLOR}DEBUG - ${1}${__RESET_COLOR}" >&2 + fi Log::logDebug "$1" } -# Display message using info color (bg light blue/fg white) -# @param {String} $1 message +# @description Display message using info color (bg light blue/fg white) +# @arg $1 message:String the message to display Log::displayInfo() { local type="${2:-INFO}" - echo -e "${__INFO_COLOR}${type} - ${1}${__RESET_COLOR}" >&2 + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO)); then + echo -e "${__INFO_COLOR}${type} - ${1}${__RESET_COLOR}" >&2 + fi Log::logInfo "$1" "${type}" } -# Display message using error color (red) and exit immediately with error status 1 -# @param {String} $1 message +# @description Display message using error color (red) and exit immediately with error status 1 +# @arg $1 message:String the message to display Log::fatal() { echo -e "${__ERROR_COLOR}FATAL - ${1}${__RESET_COLOR}" >&2 Log::logFatal "$1" exit 1 } -# shellcheck disable=SC2317 - -Log::load() { - # disable display methods following display level - if ((BASH_FRAMEWORK_DISPLAY_LEVEL < __LEVEL_DEBUG)); then - Log::displayDebug() { :; } - fi - if ((BASH_FRAMEWORK_DISPLAY_LEVEL < __LEVEL_INFO)); then - Log::displayHelp() { :; } - Log::displayInfo() { :; } - Log::displaySkipped() { :; } - Log::displaySuccess() { :; } - fi - if ((BASH_FRAMEWORK_DISPLAY_LEVEL < __LEVEL_WARNING)); then - Log::displayWarning() { :; } - Log::displayStatus() { :; } - fi - if ((BASH_FRAMEWORK_DISPLAY_LEVEL < __LEVEL_ERROR)); then - Log::displayError() { :; } - fi - # disable log methods following log level - if ((BASH_FRAMEWORK_LOG_LEVEL < __LEVEL_DEBUG)); then - Log::logDebug() { :; } - fi - if ((BASH_FRAMEWORK_LOG_LEVEL < __LEVEL_INFO)); then - Log::logHelp() { :; } - Log::logInfo() { :; } - Log::logSkipped() { :; } - Log::logSuccess() { :; } - fi - if ((BASH_FRAMEWORK_LOG_LEVEL < __LEVEL_WARNING)); then - Log::logWarning() { :; } - Log::logStatus() { :; } - fi - if ((BASH_FRAMEWORK_LOG_LEVEL < __LEVEL_ERROR)); then - Log::logError() { :; } - fi - - if ((BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF)); then - if [[ -z "${BASH_FRAMEWORK_LOG_FILE}" ]]; then - BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} - export BASH_FRAMEWORK_LOG_LEVEL - elif [[ ! -f "${BASH_FRAMEWORK_LOG_FILE}" ]]; then - if ! mkdir -p "$(dirname "${BASH_FRAMEWORK_LOG_FILE}")" 2>/dev/null; then - BASH_FRAMEWORK_LOG_LEVEL=__LEVEL_OFF - Log::displayWarning "Log dir cannot be created $(dirname "${BASH_FRAMEWORK_LOG_FILE}")" - fi - if ! touch --no-create "${BASH_FRAMEWORK_LOG_FILE}" 2>/dev/null; then - BASH_FRAMEWORK_LOG_LEVEL=__LEVEL_OFF - Log::displayWarning "Log file '${BASH_FRAMEWORK_LOG_FILE}' cannot be created" - fi - fi - Log::displayInfo "Logging to file ${BASH_FRAMEWORK_LOG_FILE}" - if ((BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION > 0)); then - Log::rotate "${BASH_FRAMEWORK_LOG_FILE}" "${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION}" - fi - fi +# @description ensure COMMAND_BIN_DIR env var is set +# and PATH correctly prepared +# @noargs +# @set COMMAND_BIN_DIR string the directory where to find this command +# @set PATH string add directory where to find this command binary +Compiler::Facade::requireCommandBinDir() { + COMMAND_BIN_DIR="${CURRENT_DIR}" + Env::pathPrepend "${COMMAND_BIN_DIR}" } -Args::defaultHelpNoExit() { - local helpArg=$1 - shift || true - # shellcheck disable=SC2034 - local args - args="$(getopt -l help -o h -- "$@" 2>/dev/null)" || true - eval set -- "${args}" - - while true; do - case $1 in - -h | --help) - if [[ "$(type -t "${helpArg}")" = "function" ]]; then - "${helpArg}" "$@" - else - Args::showHelp "${helpArg}" - fi - return 1 - ;; - --) - break - ;; - *) - # ignore - ;; - esac - done -} - -# Internal: check if dsn file has all the mandatory variables set +# @description check if dsn file has all the mandatory variables set # Mandatory variables are: HOSTNAME, USER, PASSWORD, PORT # -# **Arguments**: -# * $1 - dsn absolute filename -# -# Returns 0 on valid file, 1 otherwise with log output +# @arg $1 dsnFileName:String dsn absolute filename +# @set HOSTNAME loaded from dsn file +# @set PORT loaded from dsn file +# @set USER loaded from dsn file +# @set PASSWORD loaded from dsn file +# @exitcode 0 on valid file +# @exitcode 1 if one of the properties of the conf file is invalid or if file not found +# @stderr log output if error found in conf file Database::checkDsnFile() { - local DSN_FILENAME="$1" - if [[ ! -f "${DSN_FILENAME}" ]]; then - Log::displayError "dsn file ${DSN_FILENAME} not found" + local dsnFileName="$1" + if [[ ! -f "${dsnFileName}" ]]; then + Log::displayError "dsn file ${dsnFileName} not found" return 1 fi ( unset HOSTNAME PORT PASSWORD USER # shellcheck source=/src/Database/testsData/dsn_valid.env - source "${DSN_FILENAME}" + source "${dsnFileName}" if [[ -z ${HOSTNAME+x} ]]; then - Log::displayError "dsn file ${DSN_FILENAME} : HOSTNAME not provided" + Log::displayError "dsn file ${dsnFileName} : HOSTNAME not provided" return 1 fi if [[ -z "${HOSTNAME}" ]]; then - Log::displayWarning "dsn file ${DSN_FILENAME} : HOSTNAME value not provided" + Log::displayWarning "dsn file ${dsnFileName} : HOSTNAME value not provided" fi if [[ "${HOSTNAME}" = "localhost" ]]; then - Log::displayWarning "dsn file ${DSN_FILENAME} : check that HOSTNAME should not be 127.0.0.1 instead of localhost" + Log::displayWarning "dsn file ${dsnFileName} : check that HOSTNAME should not be 127.0.0.1 instead of localhost" fi if [[ -z "${PORT+x}" ]]; then - Log::displayError "dsn file ${DSN_FILENAME} : PORT not provided" + Log::displayError "dsn file ${dsnFileName} : PORT not provided" return 1 fi if ! [[ ${PORT} =~ ^[0-9]+$ ]]; then - Log::displayError "dsn file ${DSN_FILENAME} : PORT invalid" + Log::displayError "dsn file ${dsnFileName} : PORT invalid" return 1 fi if [[ -z "${USER+x}" ]]; then - Log::displayError "dsn file ${DSN_FILENAME} : USER not provided" + Log::displayError "dsn file ${dsnFileName} : USER not provided" return 1 fi if [[ -z "${PASSWORD+x}" ]]; then - Log::displayError "dsn file ${DSN_FILENAME} : PASSWORD not provided" + Log::displayError "dsn file ${dsnFileName} : PASSWORD not provided" return 1 fi ) } +# @description prepend directories to the PATH environment variable +# @arg $@ args:String[] list of directories to prepend +# @set PATH update PATH with the directories prepended +Env::pathPrepend() { + local arg + for arg in "$@"; do + if [[ -d "${arg}" && ":${PATH}:" != *":${arg}:"* ]]; then + PATH="$(realpath "${arg}"):${PATH}" + fi + done +} + +# @description concatenate 2 paths and ensure the path is correct using realpath -m +# @arg $1 basePath:String +# @arg $2 subPath:String +# @require Linux::requireRealpathCommand File::concatenatePath() { - local basePath="${1}" - local subPath=${2} + local basePath="$1" + local subPath="$2" local fullPath="${basePath:+${basePath}/}${subPath}" realpath -m "${fullPath}" 2>/dev/null } -# Display message using error color (red) -# @param {String} $1 message +# @description Display message using error color (red) +# @arg $1 message:String the message to display Log::displayError() { - echo -e "${__ERROR_COLOR}ERROR - ${1}${__RESET_COLOR}" >&2 + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_ERROR)); then + echo -e "${__ERROR_COLOR}ERROR - ${1}${__RESET_COLOR}" >&2 + fi Log::logError "$1" } -# Display message using info color (bg light blue/fg white) -# @param {String} $1 message -Log::displayHelp() { - local type="${2:-HELP}" - echo -e "${__HELP_COLOR}${type} - ${1}${__RESET_COLOR}" >&2 - Log::logHelp "$1" "${type}" -} - -# Display message using skip color (yellow) -# @param {String} $1 message -Log::displaySkipped() { - echo -e "${__SKIPPED_COLOR}SKIPPED - ${1}${__RESET_COLOR}" >&2 - Log::logSkipped "$1" +# @description log message to file +# @arg $1 message:String the message to display +Log::logDebug() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_DEBUG)); then + Log::logMessage "${2:-DEBUG}" "$1" + fi } -# Display message using info color (blue) but warning level -# @param {String} $1 message -Log::displayStatus() { - local type="${2:-STATUS}" - echo -e "${__INFO_COLOR}${type} - ${1}${__RESET_COLOR}" >&2 - Log::logStatus "$1" "${type}" +# @description log message to file +# @arg $1 message:String the message to display +Log::logFatal() { + Log::logMessage "${2:-FATAL}" "$1" } -# Display message using success color (bg green/fg white) -# @param {String} $1 message -Log::displaySuccess() { - echo -e "${__SUCCESS_COLOR}SUCCESS - ${1}${__RESET_COLOR}" >&2 - Log::logSuccess "$1" +# @description log message to file +# @arg $1 message:String the message to display +Log::logInfo() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO)); then + Log::logMessage "${2:-INFO}" "$1" + fi } -# Display message using warning color (yellow) -# @param {String} $1 message +# @description Display message using warning color (yellow) +# @arg $1 message:String the message to display Log::displayWarning() { - echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING)); then + echo -e "${__WARNING_COLOR}WARN - ${1}${__RESET_COLOR}" >&2 + fi Log::logWarning "$1" } -# log message to file -# @param {String} $1 message -Log::logDebug() { - Log::logMessage "${2:-DEBUG}" "$1" +# @description log message to file +# @arg $1 message:String the message to display +Log::logError() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_ERROR)); then + Log::logMessage "${2:-ERROR}" "$1" + fi } -# log message to file -# @param {String} $1 message -Log::logError() { - Log::logMessage "${2:-ERROR}" "$1" +# @description Internal: common log message +# @example text +# [date]|[levelMsg]|message +# +# @example text +# 2020-01-19 19:20:21|ERROR |log error +# 2020-01-19 19:20:21|SKIPPED|log skipped +# +# @arg $1 levelMsg:String message's level description (eg: STATUS, ERROR, ...) +# @arg $2 msg:String the message to display +# @env BASH_FRAMEWORK_LOG_FILE String log file to use, do nothing if empty +# @env BASH_FRAMEWORK_LOG_LEVEL int log level log only if > OFF or fatal messages +# @stderr diagnostics information is displayed +# @require Env::requireLoad +# @require Log::requireLoad +Log::logMessage() { + local levelMsg="$1" + local msg="$2" + local date + + if [[ -n "${BASH_FRAMEWORK_LOG_FILE}" ]] && ((BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF)); then + date="$(date '+%Y-%m-%d %H:%M:%S')" + touch "${BASH_FRAMEWORK_LOG_FILE}" + printf "%s|%7s|%s\n" "${date}" "${levelMsg}" "${msg}" >>"${BASH_FRAMEWORK_LOG_FILE}" + fi } -# log message to file -# @param {String} $1 message -Log::logFatal() { - Log::logMessage "${2:-FATAL}" "$1" +# @description ensure command realpath is available +# @exitcode 1 if realpath command not available +# @stderr diagnostics information is displayed +Linux::requireRealpathCommand() { + Assert::commandExists realpath } -# log message to file -# @param {String} $1 message -Log::logHelp() { - Log::logMessage "${2:-HELP}" "$1" +# @description check if command specified exists or return 1 +# with error and message if not +# +# @arg $1 commandName:String on which existence must be checked +# @arg $2 helpIfNotExists:String a help command to display if the command does not exist +# +# @exitcode 1 if the command specified does not exist +# @stderr diagnostic information + help if second argument is provided +Assert::commandExists() { + local commandName="$1" + local helpIfNotExists="$2" + + "${BASH_FRAMEWORK_COMMAND:-command}" -v "${commandName}" >/dev/null 2>/dev/null || { + Log::displayError "${commandName} is not installed, please install it" + if [[ -n "${helpIfNotExists}" ]]; then + Log::displayInfo "${helpIfNotExists}" + fi + return 1 + } + return 0 } -# log message to file -# @param {String} $1 message -Log::logInfo() { - Log::logMessage "${2:-INFO}" "$1" +# @description log message to file +# @arg $1 message:String the message to display +Log::logWarning() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING)); then + Log::logMessage "${2:-WARNING}" "$1" + fi } -# log message to file -# @param {String} $1 message -Log::logSkipped() { - Log::logMessage "${2:-SKIPPED}" "$1" +# @description ensure env files are loaded +# @noargs +# @exitcode 1 if getOrderedConfFiles fails +# @exitcode 2 if one of env files fails to load +# @stderr diagnostics information is displayed +Env::requireLoad() { + local configFilesStr + configFilesStr="$(Env::getOrderedConfFiles)" || return 1 + + local -a configFiles + readarray -t configFiles <<<"${configFilesStr}" + + # if empty string, there will be one element + if ((${#configFiles[@]} == 0)) || [[ -z "${configFilesStr}" ]]; then + # should not happen, as there is always default file + Log::displaySkipped "no env file to load" + return 0 + fi + + Env::mergeConfFiles "${configFiles[@]}" || { + Log::displayError "while loading config files: ${configFiles[*]}" + return 2 + } +} + +# @description activate or not Log::display* and Log::log* functions +# based on BASH_FRAMEWORK_DISPLAY_LEVEL and BASH_FRAMEWORK_LOG_LEVEL +# environment variables loaded by Env::requireLoad +# try to create log file and rotate it if necessary +# @noargs +# @set BASH_FRAMEWORK_LOG_LEVEL int to OFF level if BASH_FRAMEWORK_LOG_FILE is empty or not writable +# @env BASH_FRAMEWORK_DISPLAY_LEVEL int +# @env BASH_FRAMEWORK_LOG_LEVEL int +# @env BASH_FRAMEWORK_LOG_FILE String +# @env BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION int do log rotation if > 0 +# @exitcode 0 always successful +# @stderr diagnostics information about log file is displayed +# @require Env::requireLoad +# @require UI::requireTheme +Log::requireLoad() { + if [[ -z "${BASH_FRAMEWORK_LOG_FILE:-}" ]]; then + BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} + export BASH_FRAMEWORK_LOG_LEVEL + fi + + if ((BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF)); then + if [[ ! -f "${BASH_FRAMEWORK_LOG_FILE}" ]]; then + if + ! mkdir -p "$(dirname "${BASH_FRAMEWORK_LOG_FILE}")" 2>/dev/null || + ! touch --no-create "${BASH_FRAMEWORK_LOG_FILE}" 2>/dev/null + then + BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} + echo -e "${__ERROR_COLOR}ERROR - File ${BASH_FRAMEWORK_LOG_FILE} is not writable${__RESET_COLOR}" >&2 + fi + elif [[ ! -w "${BASH_FRAMEWORK_LOG_FILE}" ]]; then + BASH_FRAMEWORK_LOG_LEVEL=${__LEVEL_OFF} + echo -e "${__ERROR_COLOR}ERROR - File ${BASH_FRAMEWORK_LOG_FILE} is not writable${__RESET_COLOR}" >&2 + fi + + fi + + if ((BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF)); then + # will always be created even if not in info level + Log::logMessage "INFO" "Logging to file ${BASH_FRAMEWORK_LOG_FILE} - Log level ${BASH_FRAMEWORK_LOG_LEVEL}" + if ((BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION > 0)); then + Log::rotate "${BASH_FRAMEWORK_LOG_FILE}" "${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION}" + fi + fi } -# log message to file -# @param {String} $1 message -Log::logStatus() { - Log::logMessage "${2:-STATUS}" "$1" +# @description get list of env files to load +# in order to make them available for Env::requireLoad +# @env BASH_FRAMEWORK_ENV_FILES String[] list of env files that should be loaded +# @exitcode 1 if one of the env file cannot be generated +# @exitcode 2 if one of the env file is not a file or readable +# @stdout the env files asked to be loaded +# @stderr diagnostic information on failure +# @see https://github.com/fchastanet/bash-tools-framework/blob/master/FrameworkDoc.md#config_file_order +Env::getOrderedConfFiles() { + local -a configFiles=() + + if [[ -n "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then + # BASH_FRAMEWORK_ENV_FILES is an array + configFiles+=("${BASH_FRAMEWORK_ENV_FILES[@]}") + fi + + local defaultEnvFile + defaultEnvFile="$(Env::createDefaultEnvFile)" || return 1 + configFiles+=("${defaultEnvFile}") + + local file + for file in "${configFiles[@]}"; do + if [[ ! -f "${file}" || ! -r "${file}" ]]; then + Log::displayError "One of the config file is not available '${file}'" + return 2 + fi + echo "${file}" + done } -# log message to file -# @param {String} $1 message -Log::logSuccess() { - Log::logMessage "${2:-SUCCESS}" "$1" +# @description merge and load conf files specified as argument +# - files are cleaned from ay comment +# - missing quotes after property = sign are added automatically +# - automatic remove of all whitespace before and after declarations +# - bash arrays are not supported +# - if a variable is declared in first file and overridden later on +# in the same file or in subsequent files, those overloads will be +# ignored +# @warning if an error occurs while loading one of the config file, exit code 3 but environment could be partially loaded +# @arg $@ args:String[] list of configuration files to load in order +# @set envVars String will set in environment all the variables that have been declared in the config files +# @env envVars String the env variables of the current script could be used to interpret variables during config files parsing +# @exitcode 0 if no config files provided or load completed successfully +# @exitcode 1 if error occurred during parsing the config files (file not found, grep, awk or sed error) +# @exitcode 2 if temporary file cannot be created +# @exitcode 3 if an error occurred during config file sourcing +# @stderr diagnostics information is displayed +# @see largely inspired but modified from https://opensource.com/article/21/5/processing-configuration-files-shell +Env::mergeConfFiles() { + local -a configFileList=("$@") + + if ((${#configFileList[@]} == 0)); then + return 0 + fi + + local combinedConfigFile + combinedConfigFile="$(Framework::createTempFile "mergeConfFiles")" || return 2 + + ( + # removes any trailing whitespace from each file, if any + # this is absolutely required when importing into ConfigMaps + # put quotes around values + sed -E -e $'s/\s*$// ; /^$/d ; /^#.*$/d ; s/=([^"\'].*)$/="\\1"/' "${configFileList[@]}" | + # remove all comment lines + Filters::commentLines | + # iterates over each file and prints (default awk behavior) + # each unique line; only takes first value and ignores duplicates + awk -F= '!line[$1]++' + ) >"${combinedConfigFile}" || return 1 + + # have to export everything, and source it twice: + # 1) first source is to realize variables + # 2) second time is to realize references + set -o allexport + # shellcheck source=.framework-config + source "${combinedConfigFile}" || return 3 + # shellcheck source=.framework-config + source "${combinedConfigFile}" || return 3 + set +o allexport } -# log message to file -# @param {String} $1 message -Log::logWarning() { - Log::logMessage "${2:-WARNING}" "$1" +# @description Display message using skip color (yellow) +# @arg $1 message:String the message to display +Log::displaySkipped() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO)); then + echo -e "${__SKIPPED_COLOR}SKIPPED - ${1}${__RESET_COLOR}" >&2 + fi + Log::logSkipped "$1" } -# To be called before logging in the log file -# @param {string} file $1 log file name -# @param {int} maxLogFilesCount $2 maximum number of log files +# @description To be called before logging in the log file +# @arg $1 file:string log file name +# @arg $2 maxLogFilesCount:int maximum number of log files Log::rotate() { local file="$1" local maxLogFilesCount="${2:-5}" @@ -808,96 +713,180 @@ Log::rotate() { fi } -Args::showHelp() { - local helpArg="$1" - echo -e "${helpArg}" +# @description load color theme +# @noargs +# @env BASH_FRAMEWORK_THEME String theme to use +# @exitcode 0 always successful +UI::requireTheme() { + UI::theme "${BASH_FRAMEWORK_THEME-default}" } -# Internal: common log message -# -# **Arguments**: -# * $1 - message's level description -# * $2 - message -# **Output**: -# [date]|[levelMsg]|message -# -# **Examples**: -#
-# 2020-01-19 19:20:21|ERROR  |log error
-# 2020-01-19 19:20:21|SKIPPED|log skipped
-# 
-Log::logMessage() { - local levelMsg="$1" - local msg="$2" - local date +# @description default env file with all default values +# @stdout the default env filepath +Env::createDefaultEnvFile() { + local envFile + envFile="$(Framework::createTempFile "createDefaultEnvFileEnvFile")" || return 2 - if [[ -z "${BASH_FRAMEWORK_LOG_FILE}" ]]; then - return 0 - fi - if ((BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF)) || [[ "${levelMsg}" = "FATAL" ]]; then - mkdir -p "$(dirname "${BASH_FRAMEWORK_LOG_FILE}")" || true - if Assert::fileWritable "${BASH_FRAMEWORK_LOG_FILE}"; then - date="$(date '+%Y-%m-%d %H:%M:%S')" - touch "${BASH_FRAMEWORK_LOG_FILE}" - printf "%s|%7s|%s\n" "${date}" "${levelMsg}" "${msg}" >>"${BASH_FRAMEWORK_LOG_FILE}" - else - echo -e "${__ERROR_COLOR}ERROR - File ${BASH_FRAMEWORK_LOG_FILE} is not writable${__RESET_COLOR}" >&2 - fi - fi + ( + echo "BASH_FRAMEWORK_THEME=${BASH_FRAMEWORK_THEME:-default}" + echo "BASH_FRAMEWORK_LOG_LEVEL=${BASH_FRAMEWORK_LOG_LEVEL:-0}" + echo "BASH_FRAMEWORK_DISPLAY_LEVEL=${BASH_FRAMEWORK_DISPLAY_LEVEL:-${__LEVEL_WARNING}}" + # shellcheck disable=SC2016 + echo 'BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-"${FRAMEWORK_ROOT_DIR}/logs/${SCRIPT_NAME}.log"}"' + echo "BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" + ) >"${envFile}" + echo "${envFile}" } -# Checks if file can be created in folder -# The file does not need to exist -Assert::fileWritable() { - local file="$1" - local dir +# @description remove comment lines from input or files provided as arguments +# @arg $@ files:String[] (optional) the files to filter +# @env commentLinePrefix String the comment line prefix (default value: #) +# @exitcode 0 if lines filtered or not +# @exitcode 2 if grep fails for any other reasons than not found +# @stdin the file as stdin to filter (alternative to files argument) +# @stdout the filtered lines +# shellcheck disable=SC2120 +Filters::commentLines() { + grep -vxE "[[:blank:]]*(${commentLinePrefix:-#}.*)?" "$@" || test $? = 1 +} - dir="$(dirname "${file}")" +# @description create a temp file using default TMPDIR variable +# initialized in _includes/_commonHeader.sh +# @env TMPDIR String (default value /tmp) +# @arg $1 templateName:String template name to use(optional) +Framework::createTempFile() { + mktemp -p "${TMPDIR:-/tmp}" -t "${1:-}.XXXXXXXXXXXX" +} - Assert::validPath "${file}" && [[ -w "${dir}" ]] +# @description log message to file +# @arg $1 message:String the message to display +Log::logSkipped() { + if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO)); then + Log::logMessage "${2:-SKIPPED}" "$1" + fi } -# Public: check if argument is a valid linux path +# @description load colors theme constants +# @warning if tty not opened, noColor theme will be chosen +# @arg $1 theme:String the theme to use (default, noColor) +# @arg $@ args:String[] +# @set __ERROR_COLOR String indicate error status +# @set __INFO_COLOR String indicate info status +# @set __SUCCESS_COLOR String indicate success status +# @set __WARNING_COLOR String indicate warning status +# @set __SKIPPED_COLOR String indicate skipped status +# @set __DEBUG_COLOR String indicate debug status +# @set __HELP_COLOR String indicate help status +# @set __TEST_COLOR String not used +# @set __TEST_ERROR_COLOR String not used +# @set __HELP_TITLE_COLOR String used to display help title in help strings +# @set __HELP_OPTION_COLOR String used to display highlight options in help strings # -# @param {string} path $1 path that needs to be checked -# @return 1 if path is invalid -# invalid path are those with: -# - invalid characters -# - component beginning by a - (because option) -# - not beginning with a slash -# - relative -Assert::validPath() { - local path="$1" +# @set __RESET_COLOR String reset default color +# +# @set __HELP_EXAMPLE String to remove +# @set __HELP_TITLE String to remove +# @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 +UI::theme() { + local theme="${1-default}" + if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then + theme="noColor" + fi + case "${theme}" in + default | default-force) + theme="default" + ;; + noColor) ;; + *) + Log::fatal "invalid theme provided" + ;; + esac + if [[ "${theme}" = "default" ]]; then + BASH_FRAMEWORK_THEME="default" + # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue + # Internal: reset color + __RESET_COLOR='\e[0m' # Reset Color + # shellcheck disable=SC2155,SC2034 + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + # shellcheck disable=SC2155,SC2034 + __HELP_TITLE="$(echo -e "\e[1;37m")" + # shellcheck disable=SC2155,SC2034 + __HELP_NORMAL="$(echo -e "\033[0m")" + else + BASH_FRAMEWORK_THEME="noColor" + # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' + # Internal: reset color + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' + fi +} - # https://regex101.com/r/afLrmM/2 - [[ "${path}" =~ ^\/$|^(\/[.a-zA-Z_0-9][.a-zA-Z_0-9-]*)+$ ]] && - [[ ! "${path}" =~ (\/\.\.)|(\.\.\/)|^\.$|^\.\.$ ]] # avoid relative +# @description check if tty (interactive mode) is active +# @noargs +# @exitcode 1 if tty not active +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive +# @stderr diagnostic information + help if second argument is provided +Assert::tty() { + if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then + return 1 + fi + if [[ "${INTERACTIVE:-0}" = "1" ]]; then + return 0 + fi + [[ -t 1 || -t 2 ]] } # FUNCTIONS -Env::load -export BASH_FRAMEWORK_DISPLAY_LEVEL="${__LEVEL_WARNING}" -Args::parseVerbose "${__LEVEL_INFO}" "$@" || true -declare -a args=("$@") -Array::remove args -v --verbose -set -- "${args[@]}" - -Log::load - -Env::pathPrepend "${COMMAND_BIN_DIR}" +facade_main_6e8b53ac69594d439449db19aae37378() { +# REQUIRES +Linux::requireRealpathCommand +Env::requireLoad +Log::requireLoad +UI::requireTheme +Compiler::Facade::requireCommandBinDir -# prepare bin directory for eventual bin files generated by Embed::embed -mkdir -p "${TMPDIR:-/tmp}/bin" -Env::pathPrepend "${TMPDIR:-/tmp}/bin" +# @require Compiler::Facade::requireCommandBinDir +# shellcheck disable=SC2154 +############################################################ +# INTERNAL USE ONLY +# USED BY bin/dbScriptAllDatabases sub scripts +# eg: src/DbScriptAllDatabases/extractData.sh +############################################################ Assert::expectNonRootUser declare DSN="$1" # shellcheck disable=SC2034 declare LOG_FORMAT="$2" # shellcheck disable=SC2034 -declare VERBOSE="$3" +declare VERBOSE="${3:-0}" # shellcheck disable=SC2034 declare outputDir="$4" # shellcheck disable=SC2034 @@ -908,63 +897,58 @@ declare -i length=$(($# - 6)) declare -a scriptParameters=("${@:6:${length}}") # shellcheck disable=SC2034,SC2124 declare db="${@:$(($#)):1}" - -[[ "${VERBOSE}" = "1" ]] && Log::displayInfo "process db '${db}'" - # shellcheck disable=SC2034 declare -A dbInstance -Database::newInstance dbInstance "${DSN}" -Database::setQueryOptions dbInstance "${dbInstance[QUERY_OPTIONS]} --connect-timeout=5" -HELP="$( - cat <= 1)); then + Log::displayInfo "process db '${db}'" + fi + + # shellcheck disable=SC2154 + if [[ -z "${scriptParameters[0]}" ]]; then + Log::fatal "query string or file not provided" + fi + + Database::newInstance dbInstance "${DSN}" + Database::setQueryOptions dbInstance "${dbInstance[QUERY_OPTIONS]} --connect-timeout=5" +} -${__HELP_TITLE}Author:${__HELP_NORMAL} -[François Chastanet](https://github.com/fchastanet) +run() { + # extra parameters passed through dbScriptAllDatabases + declare query="${scriptParameters[0]}" + declare queryName="customQuery" + declare queryFile="${query}" + queryFile="$(Conf::getAbsoluteFile "dbQueries" "${queryFile}" "sql" 2>/dev/null || echo "")" + if [[ -n "${queryFile}" ]]; then + queryName="$(basename "${queryFile%.*}")" + query="$(cat "${queryFile}")" + fi -${__HELP_TITLE}Source file:${__HELP_NORMAL} -https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/DbScriptAllDatabases/extractData.sh + # create log file + declare logFile="" + if [[ "${LOG_FORMAT}" = "log" ]]; then + declare logFile="${outputDir}/${db}_${queryName}.log" + exec 6>&1 1>"${logFile}" # redirect stdout to logFile + fi -${__HELP_TITLE}License:${__HELP_NORMAL} -MIT License + Database::skipColumnNames dbInstance 0 + Database::query dbInstance "${query}" "${db}" || true + Database::skipColumnNames dbInstance 1 -Copyright (c) 2022 François Chastanet -EOF -)" -Args::defaultHelp "${HELP}" "$@" + if [[ "${LOG_FORMAT}" = "log" ]]; then + # restore stdout + exec 1>&6 6>&- + fi -# shellcheck disable=SC2154 -if [[ -z "${scriptParameters[0]}" ]]; then - Log::fatal "query string or file not provided" -fi - -# extra parameters passed through dbScriptAllDatabases -declare query="${scriptParameters[0]}" -declare queryName="customQuery" -declare queryFile="${query}" -queryFile="$(Conf::getAbsoluteFile "dbQueries" "${queryFile}" "sql" 2>/dev/null || echo "")" -if [[ -n "${queryFile}" ]]; then - queryName="$(basename "${queryFile%.*}")" - query="$(cat "${queryFile}")" -fi - -# create log file -declare logFile="" -if [[ "${LOG_FORMAT}" = "log" ]]; then - # shellcheck disable=SC2154 - declare logFile="${outputDir}/${db}_${queryName}.log" - exec 6>&1 1>"${logFile}" # redirect stdout to logFile -fi + if [[ "${LOG_FORMAT}" = "log" ]]; then + Log::displayInfo "result available in '${logFile}'" + fi +} -Database::skipColumnNames dbInstance 0 -Database::query dbInstance "${query}" "${db}" || true -Database::skipColumnNames dbInstance 1 +init +run -if [[ "${LOG_FORMAT}" = "log" ]]; then - # restore stdout - exec 1>&6 6>&- -fi +} -[[ "${LOG_FORMAT}" = "log" ]] && Log::displayInfo "result available in '${logFile}'" +facade_main_6e8b53ac69594d439449db19aae37378 "$@" diff --git a/install b/install index 62df2a42..dba0adf6 100755 --- a/install +++ b/install @@ -47,6 +47,7 @@ declare -a BASH_FRAMEWORK_ARGV=("$@") declare -a ORIGINAL_BASH_FRAMEWORK_ARGV=("$@") # @see https://unix.stackexchange.com/a/386856 +# shellcheck disable=SC2317 interruptManagement() { # restore SIGINT handler trap - INT @@ -76,6 +77,7 @@ TMPDIR="$(mktemp -d -p "${PERSISTENT_TMPDIR:-/tmp}" -t bash-framework-$$-XXXXXX) export TMPDIR # temp dir cleaning +# shellcheck disable=SC2317 cleanOnExit() { if [[ "${KEEP_TEMP_FILES:-0}" = "1" ]]; then Log::displayInfo "KEEP_TEMP_FILES=1 temp files kept here '${TMPDIR}'" @@ -134,7 +136,7 @@ Array::wrap() { local -i argNoAnsiLength=0 while (($# > 0)); do - argNoAnsi="$(echo "${arg}" | Filters::removeAnsiCodes)" + argNoAnsi="$(echo "${arg%%*( )}" | Filters::removeAnsiCodes)" ((argNoAnsiLength = ${#argNoAnsi})) || true if (($# < 1 && argNoAnsiLength == 0)); then break @@ -157,7 +159,7 @@ Array::wrap() { if ((currentLineLength == 0 && firstLine == 0)); then echo -n "${indentStr}" fi - echo -e -n "${arg}" + echo -e -n "${arg}" | sed 's/[\t ]*$//g' needEcho="1" ((currentLineLength += argNoAnsiLength)) ((glueLength = ${#glue})) || true @@ -176,7 +178,7 @@ Array::wrap() { fi local -i length ((length = maxLineLength - currentLineLength)) || true - echo -e "${arg:0:${length}}" + echo -e "${arg:0:${length}}" | sed 's/[\t ]*$//g' ((currentLineLength = 0)) || true ((glueLength = 0)) || true arg="${arg:${length}}" @@ -184,7 +186,7 @@ Array::wrap() { else # arg cannot be stored on a whole line, so we add it on next line as a whole echo - echo -e -n "${indentStr}${arg}" + echo -e -n "${indentStr}${arg}" | sed 's/[\t ]*$//g' ((glueLength = ${#glue})) || true ((currentLineLength = argNoAnsiLength)) arg="" # allows to go to next arg @@ -401,6 +403,7 @@ UI::drawLine() { # @set __HELP_EXAMPLE String to remove # @set __HELP_TITLE String to remove # @set __HELP_NORMAL String to remove +# shellcheck disable=SC2034 UI::theme() { local theme="${1-default}" if [[ ! "${theme}" =~ -force$ ]] && ! Assert::tty; then @@ -416,46 +419,46 @@ UI::theme() { ;; esac if [[ "${theme}" = "default" ]]; then - export BASH_FRAMEWORK_THEME="default" + BASH_FRAMEWORK_THEME="default" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='\e[31m' # Red - export __INFO_COLOR='\e[44m' # white on lightBlue - export __SUCCESS_COLOR='\e[32m' # Green - export __WARNING_COLOR='\e[33m' # Yellow - export __SKIPPED_COLOR='\e[33m' # Yellow - export __DEBUG_COLOR='\e[37m' # Grey - export __HELP_COLOR='\e[7;49;33m' # Black on Gold - export __TEST_COLOR='\e[100m' # Light magenta - export __TEST_ERROR_COLOR='\e[41m' # white on red - export __HELP_TITLE_COLOR="\e[1;37m" # Bold - export __HELP_OPTION_COLOR="\e[1;34m" # Blue + __ERROR_COLOR='\e[31m' # Red + __INFO_COLOR='\e[44m' # white on lightBlue + __SUCCESS_COLOR='\e[32m' # Green + __WARNING_COLOR='\e[33m' # Yellow + __SKIPPED_COLOR='\e[33m' # Yellow + __DEBUG_COLOR='\e[37m' # Grey + __HELP_COLOR='\e[7;49;33m' # Black on Gold + __TEST_COLOR='\e[100m' # Light magenta + __TEST_ERROR_COLOR='\e[41m' # white on red + __HELP_TITLE_COLOR="\e[1;37m" # Bold + __HELP_OPTION_COLOR="\e[1;34m" # Blue # Internal: reset color - export __RESET_COLOR='\e[0m' # Reset Color + __RESET_COLOR='\e[0m' # Reset Color # shellcheck disable=SC2155,SC2034 - export __HELP_EXAMPLE="$(echo -e "\e[2;97m")" + __HELP_EXAMPLE="$(echo -e "\e[2;97m")" # shellcheck disable=SC2155,SC2034 - export __HELP_TITLE="$(echo -e "\e[1;37m")" + __HELP_TITLE="$(echo -e "\e[1;37m")" # shellcheck disable=SC2155,SC2034 - export __HELP_NORMAL="$(echo -e "\033[0m")" + __HELP_NORMAL="$(echo -e "\033[0m")" else - export BASH_FRAMEWORK_THEME="noColor" + BASH_FRAMEWORK_THEME="noColor" # check colors applicable https://misc.flogisoft.com/bash/tip_colors_and_formatting - export __ERROR_COLOR='' - export __INFO_COLOR='' - export __SUCCESS_COLOR='' - export __WARNING_COLOR='' - export __SKIPPED_COLOR='' - export __DEBUG_COLOR='' - export __HELP_COLOR='' - export __TEST_COLOR='' - export __TEST_ERROR_COLOR='' - export __HELP_TITLE_COLOR='' - export __HELP_OPTION_COLOR='' + __ERROR_COLOR='' + __INFO_COLOR='' + __SUCCESS_COLOR='' + __WARNING_COLOR='' + __SKIPPED_COLOR='' + __DEBUG_COLOR='' + __HELP_COLOR='' + __TEST_COLOR='' + __TEST_ERROR_COLOR='' + __HELP_TITLE_COLOR='' + __HELP_OPTION_COLOR='' # Internal: reset color - export __RESET_COLOR='' - export __HELP_EXAMPLE='' - export __HELP_TITLE='' - export __HELP_NORMAL='' + __RESET_COLOR='' + __HELP_EXAMPLE='' + __HELP_TITLE='' + __HELP_NORMAL='' fi } @@ -481,8 +484,8 @@ Linux::requireExecutedAsUser() { # @description check if tty (interactive mode) is active # @noargs # @exitcode 1 if tty not active -# @environment NON_INTERACTIVE if 1 consider as not interactive even if environement is interactive -# @environment INTERACTIVE if 1 consider as interactive even if environement is not interactive +# @env NON_INTERACTIVE if 1 consider as not interactive even if environment is interactive +# @env INTERACTIVE if 1 consider as interactive even if environment is not interactive # @stderr diagnostic information + help if second argument is provided Assert::tty() { if [[ "${NON_INTERACTIVE:-0}" = "1" ]]; then @@ -848,16 +851,18 @@ Compiler::Facade::requireCommandBinDir Linux::requireExecutedAsUser # @require Compiler::Facade::requireCommandBinDir +# shellcheck disable=SC2034 + +declare copyrightBeginYear="2020" +declare optionBashFrameworkConfig="${BASH_TOOLS_ROOT_DIR}/.framework-config" declare -a BASH_FRAMEWORK_ARGV_FILTERED=() copyrightCallback() { - local years - years="$(date +%Y)" - if [[ -n "${copyrightBeginYear}" && "${copyrightBeginYear}" != "${years}" ]]; then - years="${copyrightBeginYear}-${years}" + if [[ -z "${copyrightBeginYear}" ]]; then + copyrightBeginYear="$(date +%Y)" fi - echo "Copyright (c) ${years} François Chastanet" + echo "Copyright (c) ${copyrightBeginYear}-now François Chastanet" } # shellcheck disable=SC2317 # if function is overridden @@ -1031,6 +1036,29 @@ optionBashFrameworkConfigCallback() { fi } +defaultFrameworkConfig="$( + cat <<'EOF' +# copied from src/_includes/.framework-config.default +# shellcheck disable=SC2034 + +# describe the functions that will be skipped from being imported +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +# describe the files that do not contain function to be imported +NON_FRAMEWORK_FILES_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)" +# describe the files that are allowed to not have an associated bats file +BATS_FILE_NOT_NEEDED_REGEXP="(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)" +# describe the files that are allowed to not have a function matching the filename +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$" +# Source directories +FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" +) + +# export here all the variables that will be used in your templates +export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +EOF +)" + commandOptionParseFinished() { if [[ -z "${BASH_FRAMEWORK_ENV_FILES[0]+1}" ]]; then BASH_FRAMEWORK_ENV_FILES=() @@ -1050,8 +1078,16 @@ commandOptionParseFinished() { # shellcheck disable=SC2034 BASH_FRAMEWORK_CONFIG_FILE="" # shellcheck source=/.framework-config - Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || - Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config file" + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${FRAMEWORK_ROOT_DIR}" || { + # load default template framework config + if [[ ! -f "${PERSISTENT_TMPDIR}/.framework-config" ]]; then + echo "${defaultFrameworkConfig}" > "${PERSISTENT_TMPDIR}/.framework-config" + fi + Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE "${PERSISTENT_TMPDIR}" || { + Log::fatal "Command ${SCRIPT_NAME} - error while loading .framework-config.default file" + } + Log::displayWarning "Command ${SCRIPT_NAME} - Load default .framework-config file - ${PERSISTENT_TMPDIR}/.framework-config" + } fi if [[ "${optionConfig}" = "1" ]]; then @@ -1098,6 +1134,7 @@ installCommand() { ((options_parse_optionParsedCountOptionLogFile = 0)) || true local -i options_parse_optionParsedCountOptionDisplayLevel ((options_parse_optionParsedCountOptionDisplayLevel = 0)) || true + # shellcheck disable=SC2034 local -i options_parse_parsedArgIndex=0 while (($# > 0)); do local options_parse_arg="$1" @@ -1116,12 +1153,14 @@ installCommand() { return 1 fi ((++options_parse_optionParsedCountOptionBashFrameworkConfig)) + # shellcheck disable=SC2034 optionBashFrameworkConfig="$1" optionBashFrameworkConfigCallback "${options_parse_arg}" "${optionBashFrameworkConfig}" ;; # Option 2/14 # Option optionConfig --config variableType Boolean min 0 max 1 authorizedValues '' regexp '' --config) + # shellcheck disable=SC2034 optionConfig="1" if ((options_parse_optionParsedCountOptionConfig >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1132,6 +1171,7 @@ installCommand() { # Option 3/14 # Option optionInfoVerbose --verbose|-v variableType Boolean min 0 max 1 authorizedValues '' regexp '' --verbose | -v) + # shellcheck disable=SC2034 optionInfoVerbose="1" if ((options_parse_optionParsedCountOptionInfoVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1144,6 +1184,7 @@ installCommand() { # Option 4/14 # Option optionDebugVerbose -vv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vv) + # shellcheck disable=SC2034 optionDebugVerbose="1" if ((options_parse_optionParsedCountOptionDebugVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1156,6 +1197,7 @@ installCommand() { # Option 5/14 # Option optionTraceVerbose -vvv variableType Boolean min 0 max 1 authorizedValues '' regexp '' -vvv) + # shellcheck disable=SC2034 optionTraceVerbose="1" if ((options_parse_optionParsedCountOptionTraceVerbose >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1181,6 +1223,7 @@ installCommand() { # Option 7/14 # Option optionNoColor --no-color variableType Boolean min 0 max 1 authorizedValues '' regexp '' --no-color) + # shellcheck disable=SC2034 optionNoColor="1" if ((options_parse_optionParsedCountOptionNoColor >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1207,6 +1250,7 @@ installCommand() { return 1 fi ((++options_parse_optionParsedCountOptionTheme)) + # shellcheck disable=SC2034 optionTheme="$1" optionThemeCallback "${options_parse_arg}" "${optionTheme}" updateArgListThemeCallback "${options_parse_arg}" "${optionTheme}" @@ -1214,6 +1258,7 @@ installCommand() { # Option 9/14 # Option optionHelp --help|-h variableType Boolean min 0 max 1 authorizedValues '' regexp '' --help | -h) + # shellcheck disable=SC2034 optionHelp="1" if ((options_parse_optionParsedCountOptionHelp >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1225,6 +1270,7 @@ installCommand() { # Option 10/14 # Option optionVersion --version variableType Boolean min 0 max 1 authorizedValues '' regexp '' --version) + # shellcheck disable=SC2034 optionVersion="1" if ((options_parse_optionParsedCountOptionVersion >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1236,6 +1282,7 @@ installCommand() { # Option 11/14 # Option optionQuiet --quiet|-q variableType Boolean min 0 max 1 authorizedValues '' regexp '' --quiet | -q) + # shellcheck disable=SC2034 optionQuiet="1" if ((options_parse_optionParsedCountOptionQuiet >= 1)); then Log::displayError "Command ${SCRIPT_NAME} - Option ${options_parse_arg} - Maximum number of option occurrences reached(1)" @@ -1262,6 +1309,7 @@ installCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogLevel)) + # shellcheck disable=SC2034 optionLogLevel="$1" optionLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" updateArgListLogLevelCallback "${options_parse_arg}" "${optionLogLevel}" @@ -1279,6 +1327,7 @@ installCommand() { return 1 fi ((++options_parse_optionParsedCountOptionLogFile)) + # shellcheck disable=SC2034 optionLogFile="$1" optionLogFileCallback "${options_parse_arg}" "${optionLogFile}" updateArgListLogFileCallback "${options_parse_arg}" "${optionLogFile}" @@ -1300,6 +1349,7 @@ installCommand() { return 1 fi ((++options_parse_optionParsedCountOptionDisplayLevel)) + # shellcheck disable=SC2034 optionDisplayLevel="$1" optionDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" updateArgListDisplayLevelCallback "${options_parse_arg}" "${optionDisplayLevel}" @@ -1334,60 +1384,74 @@ installCommand() { "[--bash-framework-config ]" "[--config]" "[--verbose|-v]" "[-vv]" "[-vvv]" "[--env-file ]" "[--no-color]" "[--theme ]" "[--help|-h]" "[--version]" "[--quiet|-q]" "[--log-level ]" "[--log-file ]" "[--display-level ]")" echo echo -e "${__HELP_TITLE_COLOR}GLOBAL OPTIONS:${__RESET_COLOR}" - printf " %b\n" "${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--bash-framework-config ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(use\ alternate\ bash\ framework\ configuration.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--config${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ configuration) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--verbose${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-v${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(info\ level\ verbose\ mode\ \(alias\ of\ --display-level\ INFO\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(debug\ level\ verbose\ mode\ \(alias\ of\ --display-level\ DEBUG\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}-vvv${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(trace\ level\ verbose\ mode\ \(alias\ of\ --display-level\ TRACE\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} (optional)" + echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Load\ the\ specified\ env\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Produce\ monochrome\ output.\ alias\ of\ --theme\ noColor.) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--theme ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(choose\ color\ theme\ \(default\,\ default-force\ or\ noColor\)\ -\ default-force\ means\ colors\ will\ be\ produced\ even\ if\ command\ is\ piped) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--help${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-h${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Display\ this\ command\ help) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--version${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Print\ version\ information\ and\ quit) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--quiet${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}-q${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(quiet\ mode\,\ doesn\'t\ display\ any\ output) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--log-file ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(Set\ log\ file) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" - printf " %b\n" "${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} (optional) (at most 1 times)" + echo -e " ${__HELP_OPTION_COLOR}--display-level ${__HELP_NORMAL} {single}" local -a helpArray + # shellcheck disable=SC2054 helpArray=(set\ display\ level\ \(one\ of\ OFF\,\ ERROR\,\ WARNING\,\ INFO\,\ DEBUG\,\ TRACE\ value\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo @@ -1409,10 +1473,6 @@ installCommand() { return 1 fi } -declare copyrightBeginYear="2020" -declare optionBashFrameworkConfig="${BASH_TOOLS_ROOT_DIR}/.framework-config" - -installCommand parse "${BASH_FRAMEWORK_ARGV[@]}" # @require Linux::requireExecutedAsUser run() { diff --git a/src/Db/queryOneDatabase.sh b/src/Db/queryOneDatabase.sh index ad655051..6905fb6b 100755 --- a/src/Db/queryOneDatabase.sh +++ b/src/Db/queryOneDatabase.sh @@ -1,12 +1,20 @@ #!/usr/bin/env bash +# VAR_MAIN_FUNCTION_VAR_NAME=dbQueryAllDatabasesFacade +# @description used to execute given query when using +# dbScriptAllDatabases +# @arg $1 dsn:String +# @arg $2 db:String +# @env query String +# @env optionSeparator String # @require Linux::requireExecutedAsUser Db::queryOneDatabase() { - # query, dsnFile and optionSeparator are passed via export - local db="$1" + # query and optionSeparator are passed via export + local dsn="$1" + local db="$2" local -A dbInstance - Database::newInstance dbInstance "${optionFromDsn}" + Database::newInstance dbInstance "${dsn}" Database::setQueryOptions dbInstance "${dbInstance[QUERY_OPTIONS]} --connect-timeout=5" # identify columns header diff --git a/src/_binaries/Converters/mysql2puml.bats b/src/_binaries/Converters/mysql2puml.bats index 9ac6fd65..697edab2 100755 --- a/src/_binaries/Converters/mysql2puml.bats +++ b/src/_binaries/Converters/mysql2puml.bats @@ -16,10 +16,7 @@ setup() { } function Converters::mysql2puml::display_help { #@test - # shellcheck disable=SC2154 - run "${binDir}/mysql2puml" --help 2>&1 - assert_success - assert_line --index 0 "DESCRIPTION: convert mysql dump sql schema to plantuml format" + testCommand "${binDir}/mysql2puml" mysql2puml.help.txt } function Converters::mysql2puml::display_version { #@test diff --git a/src/_binaries/Converters/mysql2puml.options.tpl b/src/_binaries/Converters/mysql2puml.options.tpl index df8f5ee3..beaa2f35 100644 --- a/src/_binaries/Converters/mysql2puml.options.tpl +++ b/src/_binaries/Converters/mysql2puml.options.tpl @@ -26,7 +26,8 @@ ${__HELP_TITLE}List of available skins:${__HELP_NORMAL} source <( Options::generateOption \ --variable-type String \ - --help "header configuration of the plant uml file (default: ${optionSkinDefault})" \ + --help "header configuration of the plant uml file" \ + --default-value "${optionSkinDefault}" \ --alt "--skin" \ --callback "optionSkinCallback" \ --variable-name "optionSkin" \ @@ -47,9 +48,6 @@ options+=( ) Options::generateCommand "${options[@]}" % -declare copyrightBeginYear="2020" -declare optionBashFrameworkConfig="${BASH_TOOLS_ROOT_DIR}/.framework-config" -declare optionSkin="<% ${optionSkinDefault} %>" optionHelpCallback() { local skinListHelpFile @@ -79,3 +77,5 @@ inputSqlFileCallback() { return 1 fi } + +<% ${commandFunctionName} %> parse "${BASH_FRAMEWORK_ARGV[@]}" diff --git a/src/_binaries/Converters/mysql2puml.sh b/src/_binaries/Converters/mysql2puml.sh index 2d216386..13723c29 100755 --- a/src/_binaries/Converters/mysql2puml.sh +++ b/src/_binaries/Converters/mysql2puml.sh @@ -2,11 +2,13 @@ # BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/mysql2puml # VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. # FACADE +# shellcheck disable=SC2034 -.INCLUDE "$(dynamicTemplateDir _binaries/Converters/mysql2puml.options.tpl)" - -mysql2pumlCommand parse "${BASH_FRAMEWORK_ARGV[@]}" +declare copyrightBeginYear="2020" +declare optionBashFrameworkConfig="${BASH_TOOLS_ROOT_DIR}/.framework-config" +declare optionSkin="default" +.INCLUDE "$(dynamicTemplateDir _binaries/Converters/mysql2puml.options.tpl)" declare awkScript awkScript="$( cat <<'EOF' @@ -15,7 +17,6 @@ EOF )" run() { - # shellcheck disable=SC2154 absSkinFile="$(Conf::getAbsoluteFile "mysql2pumlSkins" "${optionSkin}" "puml")" || Log::fatal "the skin ${optionSkin} does not exist" diff --git a/src/_binaries/Converters/testsData/mysql2puml.help.txt b/src/_binaries/Converters/testsData/mysql2puml.help.txt new file mode 100644 index 00000000..b8ee7cce --- /dev/null +++ b/src/_binaries/Converters/testsData/mysql2puml.help.txt @@ -0,0 +1,68 @@ +DESCRIPTION: convert mysql dump sql schema to plantuml format + +USAGE: mysql2puml [OPTIONS] [ARGUMENTS] +USAGE: mysql2puml [--skin ] [--bash-framework-config ] + [--config] [--verbose|-v] [-vv] [-vvv] [--env-file ] [--no-color] + [--theme ] [--help|-h] [--version] [--quiet|-q] [--log-level ] + [--log-file ] [--display-level ] + +ARGUMENTS: + [inputSqlFile {single}] + sql filepath to parse (read from stdin if not provided) + +OPTIONS: + --skin  {single} + header configuration of the plant uml file + Default value: default + +GLOBAL OPTIONS: + --bash-framework-config  {single} + use alternate bash framework configuration. + --config {single} + Display configuration + --verbose, -v {single} + info level verbose mode (alias of --display-level INFO) + -vv {single} + debug level verbose mode (alias of --display-level DEBUG) + -vvv {single} + trace level verbose mode (alias of --display-level TRACE) + --env-file  {list} (optional) + Load the specified env file + --no-color {single} + Produce monochrome output. alias of --theme noColor. + --theme  {single} + choose color theme (default, default-force or noColor) - default-force means + colors will be produced even if command is piped + --help, -h {single} + Display this command help + --version {single} + Print version information and quit + --quiet, -q {single} + quiet mode, doesn't display any output + --log-level  {single} + Set log level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + --log-file  {single} + Set log file + --display-level  {single} + set display level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + +Examples +mysql2puml dump.dql + +mysqldump --skip-add-drop-table --skip-add-locks --skip-disable-keys --skip-set-charset --user=root --password=root --no-data skills | mysql2puml + +List of available skins: + - default + +VERSION: 1.0 + +AUTHOR: +[François Chastanet](https://github.com/fchastanet) + +SOURCE FILE: +https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/Converters/mysql2puml.sh + +LICENSE: +MIT License + +Copyright (c) 2020-now François Chastanet diff --git a/src/_binaries/DbImport/dbImport.bats b/src/_binaries/DbImport/dbImport.bats index 1a1f6403..744554d9 100755 --- a/src/_binaries/DbImport/dbImport.bats +++ b/src/_binaries/DbImport/dbImport.bats @@ -33,10 +33,7 @@ teardown() { } function Database::dbImport::display_help { #@test - # shellcheck disable=SC2154 - run "${binDir}/dbImport" --help 2>&1 - assert_success - assert_line --index 0 "DESCRIPTION: Import source db into target db using eventual table filter" + testCommand "${binDir}/dbImport" dbImport.help.txt } function Database::dbImport::remoteDbName_not_provided { #@test diff --git a/src/_binaries/DbImport/dbImport.options.tpl b/src/_binaries/DbImport/dbImport.options.tpl index 50bdce53..ed9fcd87 100644 --- a/src/_binaries/DbImport/dbImport.options.tpl +++ b/src/_binaries/DbImport/dbImport.options.tpl @@ -25,12 +25,12 @@ ${__HELP_EXAMPLE}TODO${__HELP_NORMAL} ${__HELP_TITLE}Example 2: import from S3${__HELP_NORMAL} ${__HELP_EXAMPLE}TODO${__HELP_NORMAL}''' +# shellcheck disable=SC2116 +declare defaultFromDsnHelp=$'dsn to use for source database\n\ + this option is incompatible with -a|--from-aws option' + % -defaultFromDsnHelp="$(echo \ - "dsn to use for source database" $'\n' \ - "this option is incompatible with -a|--from-aws option" \ -)" .INCLUDE "$(dynamicTemplateDir _binaries/options/options.base.tpl)" .INCLUDE "$(dynamicTemplateDir _binaries/options/options.dsn.tpl)" .INCLUDE "$(dynamicTemplateDir _binaries/options/options.profile.tpl)" @@ -94,20 +94,6 @@ options+=( Options::generateCommand "${options[@]}" % -# default values -declare optionFromAws="" -declare optionSkipSchema="0" -declare targetDbName="" -declare fromDbName="" - -# other configuration -declare copyrightBeginYear="2020" -declare TIMEFORMAT='time spent : %3R' -declare DB_IMPORT_DUMP_DIR=${DB_IMPORT_DUMP_DIR%/} -declare PROFILES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/dbImportProfiles" -declare HOME_PROFILES_DIR="${HOME}/.bash-tools/dbImportProfiles" -declare DOWNLOAD_DUMP=0 - optionHelpCallback() { local profilesList="" local dsnList="" @@ -148,3 +134,5 @@ dbImportCommandCallback() { Log::fatal "Command ${SCRIPT_NAME} -impossible to create directory ${DB_IMPORT_DUMP_DIR} specified by DB_IMPORT_DUMP_DIR env variable" fi } + +<% ${commandFunctionName} %> parse "${BASH_FRAMEWORK_ARGV[@]}" diff --git a/src/_binaries/DbImport/dbImport.sh b/src/_binaries/DbImport/dbImport.sh index 2d03dd7b..02443200 100755 --- a/src/_binaries/DbImport/dbImport.sh +++ b/src/_binaries/DbImport/dbImport.sh @@ -2,6 +2,27 @@ # BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/dbImport # VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. # FACADE +# shellcheck disable=SC2034 + +# default values +declare optionFromAws="" +declare optionSkipSchema="0" +declare targetDbName="" +declare fromDbName="" +declare optionProfile="default" +declare optionTables="" +declare profileCommandFile="" +declare optionTargetDsn="" +declare optionCharacterSet="" +declare defaultTargetCharacterSet="" + +# other configuration +declare copyrightBeginYear="2020" +declare TIMEFORMAT='time spent : %3R' +declare DB_IMPORT_DUMP_DIR=${DB_IMPORT_DUMP_DIR%/} +declare PROFILES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/dbImportProfiles" +declare HOME_PROFILES_DIR="${HOME}/.bash-tools/dbImportProfiles" +declare DOWNLOAD_DUMP=0 .INCLUDE "$(dynamicTemplateDir _binaries/DbImport/dbImport.options.tpl)" @@ -26,12 +47,9 @@ DUMP_SIZE_QUERY="$( EOF )" -dbImportCommand parse "${BASH_FRAMEWORK_ARGV[@]}" - # @require Linux::requireExecutedAsUser run() { - # check dependencies Assert::commandExists mysql "sudo apt-get install -y mysql-client" Assert::commandExists mysqlshow "sudo apt-get install -y mysql-client" @@ -46,6 +64,7 @@ run() { # shellcheck disable=SC2154 Database::newInstance dbTargetDatabase "${optionTargetDsn}" + # shellcheck disable=SC2154 Database::setQueryOptions dbTargetDatabase "${dbTargetDatabase[QUERY_OPTIONS]} --connect-timeout=5" Log::displayInfo "Using target dsn ${dbTargetDatabase['DSN_FILE']}" if [[ -z "${optionFromAws}" ]]; then @@ -79,7 +98,7 @@ run() { Log::displayInfo "local dump ${remoteDbDumpTempFile} already exists, avoid download" fi - Log::displayInfo "tables list will calculated using profile ${optionProfile} => ${profileCommand}" + Log::displayInfo "tables list will calculated using profile ${optionProfile} => ${profileCommandFile}" SECONDS=0 if [[ "${downloadDump}" = "1" ]]; then Log::displayInfo "Download dump" @@ -109,7 +128,7 @@ run() { local listTables local listTablesDumpSize local listTablesDump - listTables="$(Database::query dbFromInstance "show tables" "${fromDbName}" | ${profileCommand} | sort)" + listTables="$(Database::query dbFromInstance "show tables" "${fromDbName}" | ${profileCommandFile} | sort)" # shellcheck disable=SC2034 # used by DUMP_SIZE_QUERY listTablesDumpSize="$(echo "${listTables}" | awk -v d="," -v q="'" '{s=(NR==1?s:s d)q $0 q}END{print s }')" listTablesDump=$(echo "${listTables}" | awk -v d=" " -v q="" '{s=(NR==1?s:s d)q $0 q}END{print s }') @@ -155,13 +174,13 @@ run() { # shellcheck disable=SC2154 local targetCollationName="${optionCollationName:-${defaultTargetCollationName}}" # shellcheck disable=SC2154 - local taregtCharacterSet="${optionCharacterSet:-${defaultTargetCharacterSet}}" + local targetCharacterSet="${optionCharacterSet:-${defaultTargetCharacterSet}}" # shellcheck disable=SC2154 Log::displayInfo "create target database ${targetDbName} if needed" #shellcheck disable=SC2016 Database::query dbTargetDatabase \ - "$(printf 'CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET "%s" COLLATE "%s"' "${targetDbName}" "${taregtCharacterSet}" "${targetCollationName}")" + "$(printf 'CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET "%s" COLLATE "%s"' "${targetDbName}" "${targetCharacterSet}" "${targetCollationName}")" if [[ -z "${optionFromAws}" ]]; then Database::setQueryOptions dbTargetDatabase "${dbTargetDatabase['DB_IMPORT_OPTIONS']}" @@ -179,13 +198,13 @@ run() { fi Log::displayInfo "import remote to local from file ${remoteDbDumpTempFile}" local -a dbImportStreamOptions=( - --profile "${optionProfile}" \ - --target-dsn "${optionTargetDsn}" \ - --character-set "${taregtCharacterSet}" \ + --profile "${optionProfile}" + --target-dsn "${optionTargetDsn}" + --character-set "${targetCharacterSet}" ) if [[ -n "${optionTables:-}" ]]; then dbImportStreamOptions+=( - --tables "${optionTables}" \ + --tables "${optionTables}" ) fi time ( diff --git a/src/_binaries/DbImport/dbImportProfile.bats b/src/_binaries/DbImport/dbImportProfile.bats index aa3af708..1ace02ba 100755 --- a/src/_binaries/DbImport/dbImportProfile.bats +++ b/src/_binaries/DbImport/dbImportProfile.bats @@ -30,11 +30,7 @@ teardown() { } function Database::dbImportProfile::display_help { #@test - # shellcheck disable=SC2154 - run "${binDir}/dbImportProfile" --help 2>&1 - assert_line --index 0 "DESCRIPTION: generate optimized profiles to be used by dbImport" - run "${binDir}/dbImportProfile" -h 2>&1 - assert_line --index 0 "DESCRIPTION: generate optimized profiles to be used by dbImport" + testCommand "${binDir}/dbImportProfile" dbImportProfile.help.txt } function Database::dbImportProfile::fromDbName_not_provided { #@test diff --git a/src/_binaries/DbImport/dbImportProfile.options.tpl b/src/_binaries/DbImport/dbImportProfile.options.tpl index 49b6d874..a89a6ca6 100644 --- a/src/_binaries/DbImport/dbImportProfile.options.tpl +++ b/src/_binaries/DbImport/dbImportProfile.options.tpl @@ -79,17 +79,6 @@ options+=( Options::generateCommand "${options[@]}" % -# default values -declare optionProfile="" -declare fromDbName="" # old FROM_DB -declare optionFromDsn="<% ${defaultFromDsn} %>" # old FROM_DSN -declare optionRatio=70 # old RATIO - -# other configuration -declare copyrightBeginYear="2020" -declare PROFILES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/dbImportProfiles" -declare HOME_PROFILES_DIR="${HOME}/.bash-tools/dbImportProfiles" - optionHelpCallback() { local profilesList="" local dsnList="" @@ -117,3 +106,5 @@ dbImportProfileCommandCallback() { Log::fatal "Ratio value should be between 0 and 100" fi } + +<% ${commandFunctionName} %> parse "${BASH_FRAMEWORK_ARGV[@]}" diff --git a/src/_binaries/DbImport/dbImportProfile.sh b/src/_binaries/DbImport/dbImportProfile.sh index 75df65a1..8f116a42 100755 --- a/src/_binaries/DbImport/dbImportProfile.sh +++ b/src/_binaries/DbImport/dbImportProfile.sh @@ -2,10 +2,21 @@ # BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/dbImportProfile # VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. # FACADE +# shellcheck disable=SC2034 + +# default values +declare optionProfile="" +declare fromDbName="" +declare optionFromDsn="default.remote" +declare optionRatio=70 + +# other configuration +declare copyrightBeginYear="2020" +declare PROFILES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/dbImportProfiles" +declare HOME_PROFILES_DIR="${HOME}/.bash-tools/dbImportProfiles" .INCLUDE "$(dynamicTemplateDir _binaries/DbImport/dbImportProfile.options.tpl)" -# shellcheck disable=SC2154 read -r -d '' QUERY < parse "${BASH_FRAMEWORK_ARGV[@]}" diff --git a/src/_binaries/DbImport/dbImportStream.sh b/src/_binaries/DbImport/dbImportStream.sh index 30767136..5e8af990 100755 --- a/src/_binaries/DbImport/dbImportStream.sh +++ b/src/_binaries/DbImport/dbImportStream.sh @@ -2,18 +2,31 @@ # BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/dbImportStream # VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. # FACADE -# shellcheck disable=SC2154 +# shellcheck disable=SC2034 + +# default values +declare optionProfile="" +declare argTargetDbName="" +declare argDumpFile="" +declare optionTargetDsn="" +declare optionCharacterSet="" +declare defaultTargetCharacterSet="" +declare profileCommandFile="" + +# other configuration +declare copyrightBeginYear="2020" +declare PROFILES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/dbImportProfiles" +declare HOME_PROFILES_DIR="${HOME}/.bash-tools/dbImportProfiles" .INCLUDE "$(dynamicTemplateDir _binaries/DbImport/dbImportStream.options.tpl)" +declare awkScript awkScript="$( cat <<'EOF' .INCLUDE "$(dynamicSrcFile "_binaries/DbImport/dbImportStream.awk")" EOF )" -dbImportStreamCommand parse "${BASH_FRAMEWORK_ARGV[@]}" - # @require Linux::requireExecutedAsUser run() { @@ -26,15 +39,13 @@ run() { # create db instances declare -Agx dbTargetInstance - # shellcheck disable=SC2154 Database::newInstance dbTargetInstance "${optionTargetDsn}" - Database::setQueryOptions dbTargetInstance "${dbTargetDatabase[QUERY_OPTIONS]} --connect-timeout=5" + Database::setQueryOptions dbTargetInstance "${dbTargetInstance[QUERY_OPTIONS]} --connect-timeout=5" Log::displayInfo "Using target dsn ${dbTargetInstance['DSN_FILE']}" initializeDefaultTargetMysqlOptions dbTargetInstance "${argTargetDbName}" # TODO character set should be retrieved from dump files if possible - # shellcheck disable=SC2154 declare remoteCharacterSet="${optionCharacterSet:-${defaultRemoteCharacterSet}}" # shellcheck disable=2086 @@ -50,13 +61,13 @@ run() { if [[ "${status}" -eq "141" ]]; then true; else exit "${status}"; fi ) | awk \ - -v PROFILE_COMMAND="${profileCommand}" \ + -v PROFILE_COMMAND="${profileCommandFile}" \ -v CHARACTER_SET="${remoteCharacterSet}" \ --source "${awkScript}" \ - | mysql \ - "--defaults-extra-file=${dbTargetInstance['AUTH_FILE']}" \ - ${dbTargetInstance['DB_IMPORT_OPTIONS']} \ - "${argTargetDbName}" || exit $? + "--defaults-extra-file=${dbTargetInstance['AUTH_FILE']}" \ + ${dbTargetInstance['DB_IMPORT_OPTIONS']} \ + "${argTargetDbName}" || exit $? } if [[ "${BASH_FRAMEWORK_QUIET_MODE:-0}" = "1" ]]; then diff --git a/src/_binaries/DbImport/testsData/auto_default.local_fromDb_20.sh b/src/_binaries/DbImport/testsData/auto_default.local_fromDb_20.sh index 726241d0..ed42667b 100755 --- a/src/_binaries/DbImport/testsData/auto_default.local_fromDb_20.sh +++ b/src/_binaries/DbImport/testsData/auto_default.local_fromDb_20.sh @@ -2,7 +2,7 @@ # cat represents the whole list of tables cat | - grep -v '^table1$' | # table size 29MB - grep -v '^table2$' | # table size 10MB -# grep -v '^table3$' | # table size 4MB -cat + grep -v '^table1$' | # table size 29MB + grep -v '^table2$' | # table size 10MB + # grep -v '^table3$' | # table size 4MB + cat diff --git a/src/_binaries/DbImport/testsData/auto_default.local_fromDb_70.sh b/src/_binaries/DbImport/testsData/auto_default.local_fromDb_70.sh index 1a491a9e..c153018c 100755 --- a/src/_binaries/DbImport/testsData/auto_default.local_fromDb_70.sh +++ b/src/_binaries/DbImport/testsData/auto_default.local_fromDb_70.sh @@ -2,7 +2,7 @@ # cat represents the whole list of tables cat | - grep -v '^table1$' | # table size 29MB -# grep -v '^table2$' | # table size 10MB -# grep -v '^table3$' | # table size 4MB -cat + grep -v '^table1$' | # table size 29MB + # grep -v '^table2$' | # table size 10MB + # grep -v '^table3$' | # table size 4MB + cat diff --git a/src/_binaries/DbImport/testsData/dbImport.help.txt b/src/_binaries/DbImport/testsData/dbImport.help.txt new file mode 100644 index 00000000..3d4cfd29 --- /dev/null +++ b/src/_binaries/DbImport/testsData/dbImport.help.txt @@ -0,0 +1,115 @@ +DESCRIPTION: Import source db into target db using eventual table filter + +USAGE: dbImport [OPTIONS] [ARGUMENTS] +USAGE: dbImport [--profile|-p ] [--tables ] + [--from-dsn|-f ] [--skip-schema|-s] [--from-aws|-a ] + [--target-dsn|-t ] [--character-set|-c ] + [--collation-name|-o ] [--bash-framework-config ] [--config] + [--verbose|-v] [-vv] [-vvv] [--env-file ] [--no-color] + [--theme ] [--help|-h] [--version] [--quiet|-q] [--log-level ] + [--log-file ] [--display-level ] + +ARGUMENTS: + fromDbName {single} (mandatory) + the name of the source/remote database + [targetDbName {single}] + the name of the target database, use fromDbName(without extension) if not pr + ovided + +PROFILE OPTIONS: + --profile, -p  {single} + the name of the profile to use in order to include or exclude tables (if not + specified in default.sh from 'User profiles directory' if exists or 'De + fault profiles directory') + --tables  {single} + import only table specified in the list. If aws mode, ignore profile option + +FROM OPTIONS: + --from-dsn, -f  {single} + dsn to use for source database + this option is incompatible with -a|--from-aws option + --skip-schema, -s {single} + avoid to import the schema + --from-aws, -a  {single} + db dump will be downloaded from s3 instead of using remote db. The value is the name of the file without s3 location (Only .gz or tar.gz f + ile are supported). This option is incompatible with -f|--from-dsn option + +TARGET OPTIONS: + --target-dsn, -t  {single} + dsn to use for target database (Default: default.local) + --character-set, -c  {single} + change the character set used during database creation (default value: utf8) + --collation-name, -o  {single} + change the collation name used during database creation (default value: utf8 + _general_ci) + +GLOBAL OPTIONS: + --bash-framework-config  {single} + use alternate bash framework configuration. + --config {single} + Display configuration + --verbose, -v {single} + info level verbose mode (alias of --display-level INFO) + -vv {single} + debug level verbose mode (alias of --display-level DEBUG) + -vvv {single} + trace level verbose mode (alias of --display-level TRACE) + --env-file  {list} (optional) + Load the specified env file + --no-color {single} + Produce monochrome output. alias of --theme noColor. + --theme  {single} + choose color theme (default, default-force or noColor) - default-force means + colors will be produced even if command is piped + --help, -h {single} + Display this command help + --version {single} + Print version information and quit + --quiet, -q {single} + quiet mode, doesn't display any output + --log-level  {single} + Set log level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + --log-file  {single} + Set log file + --display-level  {single} + set display level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + +Default profiles directory: +/bash/conf/dbImportProfiles + +User profiles directory: +home/.bash-tools/dbImportProfiles +Allows to override profiles defined in Default profiles directory + +List of available profiles: + - all + - default + - none + +List of available dsn: + - default.local + - default.remote + - localhost-root + +Aws s3 location: +s3://s3server/exports + +Example 1: from one database to another one +TODO + +Example 2: import from S3 +TODO + +VERSION: 2.0 + +AUTHOR: +[François Chastanet](https://github.com/fchastanet) + +SOURCE FILE: +https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/DbImport/dbImport.sh + +LICENSE: +MIT License + +Copyright (c) 2020-now François Chastanet diff --git a/src/_binaries/DbImport/testsData/dbImportProfile.help.txt b/src/_binaries/DbImport/testsData/dbImportProfile.help.txt new file mode 100644 index 00000000..fe9fcc5f --- /dev/null +++ b/src/_binaries/DbImport/testsData/dbImportProfile.help.txt @@ -0,0 +1,84 @@ +DESCRIPTION: generate optimized profiles to be used by dbImport + +USAGE: dbImportProfile [OPTIONS] [ARGUMENTS] +USAGE: dbImportProfile [--profile|-p ] [--from-dsn|-f ] + [--ratio|-r ] [--bash-framework-config ] [--config] + [--verbose|-v] [-vv] [-vvv] [--env-file ] [--no-color] + [--theme ] [--help|-h] [--version] [--quiet|-q] [--log-level ] + [--log-file ] [--display-level ] + +ARGUMENTS: + fromDbName {single} (mandatory) + the name of the source/remote database + +OPTIONS: + --profile, -p  {single} + the name of the profile to write in profiles directory. If not provided, th + e file name pattern will be 'auto__.sh' + --from-dsn, -f  {single} + dsn to use for source database (Default: default.remote) if not provided, th + e file name pattern will be 'auto__.sh' + --ratio, -r  {single} + define the ratio to use (0 to 100% - default 70). 0 means profile will filt + er out all the tables. 100 means profile will keep all the tables. Eg: + 70 means that tables with size(table+index) that are greater that 70% o + f the max table size will be excluded. + +GLOBAL OPTIONS: + --bash-framework-config  {single} + use alternate bash framework configuration. + --config {single} + Display configuration + --verbose, -v {single} + info level verbose mode (alias of --display-level INFO) + -vv {single} + debug level verbose mode (alias of --display-level DEBUG) + -vvv {single} + trace level verbose mode (alias of --display-level TRACE) + --env-file  {list} (optional) + Load the specified env file + --no-color {single} + Produce monochrome output. alias of --theme noColor. + --theme  {single} + choose color theme (default, default-force or noColor) - default-force means + colors will be produced even if command is piped + --help, -h {single} + Display this command help + --version {single} + Print version information and quit + --quiet, -q {single} + quiet mode, doesn't display any output + --log-level  {single} + Set log level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + --log-file  {single} + Set log file + --display-level  {single} + set display level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + +Default profiles directory: +/bash/conf/dbImportProfiles + +User profiles directory: +home/.bash-tools/dbImportProfiles +Allows to override profiles defined in Default profiles directory + +List of available profiles: + + +List of available dsn: + - default.local + - default.remote + - localhost-root + +VERSION: 2.0 + +AUTHOR: +[François Chastanet](https://github.com/fchastanet) + +SOURCE FILE: +https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/DbImport/dbImportProfile.sh + +LICENSE: +MIT License + +Copyright (c) 2020-now François Chastanet diff --git a/src/_binaries/DbImport/testsData/dbImportStream.help.txt b/src/_binaries/DbImport/testsData/dbImportStream.help.txt new file mode 100644 index 00000000..87d3059d --- /dev/null +++ b/src/_binaries/DbImport/testsData/dbImportStream.help.txt @@ -0,0 +1,88 @@ +DESCRIPTION: stream tar.gz file or gz file through mysql + +USAGE: dbImportStream [OPTIONS] [ARGUMENTS] +USAGE: dbImportStream [--profile|-p ] + [--tables ] [--target-dsn|-t ] + [--character-set|-c ] [--bash-framework-config ] [--config] + [--verbose|-v] [-vv] [-vvv] [--env-file ] [--no-color] + [--theme ] [--help|-h] [--version] [--quiet|-q] [--log-level ] + [--log-file ] [--display-level ] + +ARGUMENTS: + argDumpFile {single} (mandatory) + the of the file that will be streamed through mysql + argTargetDbName {single} (mandatory) + the name of the mysql target database + +PROFILE OPTIONS: + --profile, -p  {single} + the name of the profile to use in order to include or exclude tables (if not + specified in default.sh from 'User profiles directory' if exists or 'De + fault profiles directory') + --tables  {single} + import only table specified in the list. If aws mode, ignore profile option + +TARGET OPTIONS: + --target-dsn, -t  {single} + dsn to use for target database (Default: default.local) + --character-set, -c  {single} + change the character set used during database creation (default value: utf8) + +GLOBAL OPTIONS: + --bash-framework-config  {single} + use alternate bash framework configuration. + --config {single} + Display configuration + --verbose, -v {single} + info level verbose mode (alias of --display-level INFO) + -vv {single} + debug level verbose mode (alias of --display-level DEBUG) + -vvv {single} + trace level verbose mode (alias of --display-level TRACE) + --env-file  {list} (optional) + Load the specified env file + --no-color {single} + Produce monochrome output. alias of --theme noColor. + --theme  {single} + choose color theme (default, default-force or noColor) - default-force means + colors will be produced even if command is piped + --help, -h {single} + Display this command help + --version {single} + Print version information and quit + --quiet, -q {single} + quiet mode, doesn't display any output + --log-level  {single} + Set log level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + --log-file  {single} + Set log file + --display-level  {single} + set display level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + +Default profiles directory: +/bash/conf/dbImportProfiles + +User profiles directory: +home/.bash-tools/dbImportProfiles +Allows to override profiles defined in Default profiles directory + +List of available profiles: + + +List of available dsn: + - default.local + - default.remote + - localhost-root + +VERSION: 2.0 + +AUTHOR: +[François Chastanet](https://github.com/fchastanet) + +SOURCE FILE: +https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/DbImport/dbImportStream.sh + +LICENSE: +MIT License + +Copyright (c) 2020-now François Chastanet diff --git a/src/_binaries/DbQueryAllDatabases/dbQueryAllDatabases.bats b/src/_binaries/DbQueryAllDatabases/dbQueryAllDatabases.bats index 2a95b7c6..aa124b6d 100755 --- a/src/_binaries/DbQueryAllDatabases/dbQueryAllDatabases.bats +++ b/src/_binaries/DbQueryAllDatabases/dbQueryAllDatabases.bats @@ -35,10 +35,7 @@ teardown() { } function Database::dbQueryAllDatabases::display_help { #@test - cp "${BATS_TEST_DIRNAME}/testsData/parallel" "${HOME}/bin" - # shellcheck disable=SC2154 - run "${binDir}/dbQueryAllDatabases" --help - assert_line --index 0 --partial "DESCRIPTION: Execute a query on multiple databases in order to generate a report" + testCommand "${binDir}/dbQueryAllDatabases" dbQueryAllDatabases.help.txt } function Database::dbQueryAllDatabases::query_file_not_provided { #@test @@ -49,18 +46,21 @@ function Database::dbQueryAllDatabases::query_file_not_provided { #@test } function Database::dbQueryAllDatabases::providingEnvFileChangeDbConnectionParametersAndRetrieveDbSize { #@test - cp "${BATS_TEST_DIRNAME}/testsData/parallelDbQueryAllDatabases" "${HOME}/bin/parallel" # shellcheck disable=SC2016,SC2086 stub mysql \ '\* --batch --raw --default-character-set=utf8 --connect-timeout=5 -s --skip-column-names -e \* : echo "$9" >'" "${HOME}/query1" ; cat "${BATS_TEST_DIRNAME}/testsData/getUserDbList.result"" \ '\* --batch --raw --default-character-set=utf8 --connect-timeout=5 db1 -e \* : echo -n "${8}" >'" "${HOME}/query2" ; cat "${BATS_TEST_DIRNAME}/testsData/databaseSize.result_db1"" \ '\* --batch --raw --default-character-set=utf8 --connect-timeout=5 db2 -e \* : echo -n "${8}" >'" "${HOME}/query3" ; cat "${BATS_TEST_DIRNAME}/testsData/databaseSize.result_db2"" + # shellcheck disable=SC2016 + stub parallel \ + '--eta --progress --linebuffer -j 1 * * : while IFS= read -r db; do "$6" "$7" "${db}"; done' + f() { # shellcheck disable=SC2317 "${binDir}/dbQueryAllDatabases" \ -f "${BATS_TEST_DIRNAME}/testsData/databaseSize.envProvided.sh" \ - "${rootDir}/conf/dbQueries/databaseSize.sql" 2>/dev/null + "${rootDir}/conf/dbQueries/databaseSize.sql" 2>&1 | grep -v 'INFO - Using dsn' } run f @@ -77,7 +77,6 @@ function Database::dbQueryAllDatabases::providingEnvFileChangeDbConnectionParame } function Database::dbQueryAllDatabases::multipleJobs { #@test - cp "${BATS_TEST_DIRNAME}/testsData/parallelDbQueryAllDatabases" "${HOME}/bin/parallel" export BATS_TEST_DIRNAME export HOME # shellcheck disable=SC2016,SC2086 @@ -86,15 +85,16 @@ function Database::dbQueryAllDatabases::multipleJobs { #@test $'* --batch --raw --default-character-set=utf8 --connect-timeout=5 db1 -e * : echo -n "${8}" >"${HOME}/query2" ; cat "${BATS_TEST_DIRNAME}/testsData/databaseSize.result_db1"' \ $'* --batch --raw --default-character-set=utf8 --connect-timeout=5 db2 -e * : echo -n "${8}" >"${HOME}/query3" ; cat "${BATS_TEST_DIRNAME}/testsData/databaseSize.result_db2"' + # shellcheck disable=SC2016 stub parallel \ - '--eta --progress --linebuffer -j 8 * : while IFS= read -r db; do "$6" "${db}"; done' + '--eta --progress --linebuffer -j 8 * * : while IFS= read -r db; do "$6" "$7" "${db}"; done' f() { # shellcheck disable=SC2317 "${binDir}/dbQueryAllDatabases" \ -f "${BATS_TEST_DIRNAME}/testsData/databaseSize.envProvided.sh" \ -j 8 \ - "${rootDir}/conf/dbQueries/databaseSize.sql" 2>/dev/null + "${rootDir}/conf/dbQueries/databaseSize.sql" 2>&1 | grep -v "INFO - Using dsn" } run f assert_output "$(cat "${BATS_TEST_DIRNAME}/testsData/dbQueryAllDatabases.result")" diff --git a/src/_binaries/DbQueryAllDatabases/dbQueryAllDatabases.options.tpl b/src/_binaries/DbQueryAllDatabases/dbQueryAllDatabases.options.tpl index 9e901207..d108b744 100644 --- a/src/_binaries/DbQueryAllDatabases/dbQueryAllDatabases.options.tpl +++ b/src/_binaries/DbQueryAllDatabases/dbQueryAllDatabases.options.tpl @@ -72,16 +72,6 @@ options+=( Options::generateCommand "${options[@]}" % -#default values -# default value for FROM_DSN if from-aws not set -declare queryIsFile="0" -declare optionSeparator="|" - -# other configuration -declare copyrightBeginYear="2020" -declare QUERIES_DIR="$(cd "${CURRENT_DIR}/.." && pwd -P)/conf/dbQueries" -declare HOME_QUERIES_DIR="${HOME}/.bash-tools/dbQueries" - optionHelpCallback() { local dsnList queriesList dsnList="$(Conf::getMergedList "dsn" "env")" @@ -120,3 +110,5 @@ dbQueryAllDatabasesCommandCallback() { optionFromDsn="<% ${defaultFromDsn} %>" fi } + +<% ${commandFunctionName} %> parse "${BASH_FRAMEWORK_ARGV[@]}" diff --git a/src/_binaries/DbQueryAllDatabases/dbQueryAllDatabases.sh b/src/_binaries/DbQueryAllDatabases/dbQueryAllDatabases.sh index 610b54ae..4d54b8cc 100755 --- a/src/_binaries/DbQueryAllDatabases/dbQueryAllDatabases.sh +++ b/src/_binaries/DbQueryAllDatabases/dbQueryAllDatabases.sh @@ -3,10 +3,20 @@ # VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. # FACADE # EMBED Db::queryOneDatabase as dbQueryOneDatabase +# shellcheck disable=SC2034 -.INCLUDE "$(dynamicTemplateDir _binaries/DbQueryAllDatabases/dbQueryAllDatabases.options.tpl)" +#default values +# default value for FROM_DSN if from-aws not set +declare queryIsFile="0" +declare optionSeparator="|" +declare argQuery="" + +# other configuration +declare copyrightBeginYear="2020" +declare QUERIES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/dbQueries" +declare HOME_QUERIES_DIR="${HOME}/.bash-tools/dbQueries" -dbQueryAllDatabasesCommand parse "${BASH_FRAMEWORK_ARGV[@]}" +.INCLUDE "$(dynamicTemplateDir _binaries/DbQueryAllDatabases/dbQueryAllDatabases.options.tpl)" declare awkScript awkScript="$( @@ -27,7 +37,6 @@ run() { Assert::commandExists awk "sudo apt-get install -y gawk" Version::checkMinimal "gawk" "--version" "5.0.1" - # query contains the sql from argQuery or from query string if -q option is provided declare query="${argQuery}" if [[ "${queryIsFile}" = "1" ]]; then @@ -47,9 +56,10 @@ run() { export query export optionSeparator export optionFromDsn + # shellcheck disable=SC2154 echo "${allDbs}" | SHELL=$(type -p bash) parallel --eta --progress "${PARALLEL_OPTIONS[@]}" \ - "${embed_function_DbQueryOneDatabase}" | + "${embed_function_DbQueryOneDatabase}" "${optionFromDsn}" | awk --source "${awkScript}" - } diff --git a/src/_binaries/DbQueryAllDatabases/testsData/dbQueryAllDatabases.help.txt b/src/_binaries/DbQueryAllDatabases/testsData/dbQueryAllDatabases.help.txt new file mode 100644 index 00000000..8c275026 --- /dev/null +++ b/src/_binaries/DbQueryAllDatabases/testsData/dbQueryAllDatabases.help.txt @@ -0,0 +1,93 @@ +DESCRIPTION: Execute a query on multiple databases in order to generate a report + with tsv format, query can be parallelized on multiple databases + +USAGE: dbQueryAllDatabases [OPTIONS] [ARGUMENTS] +USAGE: dbQueryAllDatabases [--jobs|-j ] [--bar|-b] + [--separator|-s ] [--from-dsn|-f ] + [--bash-framework-config ] [--config] [--verbose|-v] [-vv] [-vvv] + [--env-file ] [--no-color] [--theme ] [--help|-h] [--version] + [--quiet|-q] [--log-level ] [--log-file ] + [--display-level ] + +ARGUMENTS: + argQuery {single} (mandatory) + Query to execute + - , try to execute the mysql query provided by the file + - , search for query file in queries directory (see below) + - else the argument is interpreted as query string + +OPTIONS: + --jobs, -j  {single} + specify the number of db to query in parallel + Default value: 1 + --bar, -b {single} + Show progress as a progress bar. In the bar is shown: % of jobs completed, e + stimated seconds left, and number of jobs started. + --separator, -s  {single} + character to use to separate mysql column + Default value: | + +QUERY OPTIONS: + --from-dsn, -f  {single} + target mysql server + +GLOBAL OPTIONS: + --bash-framework-config  {single} + use alternate bash framework configuration. + --config {single} + Display configuration + --verbose, -v {single} + info level verbose mode (alias of --display-level INFO) + -vv {single} + debug level verbose mode (alias of --display-level DEBUG) + -vvv {single} + trace level verbose mode (alias of --display-level TRACE) + --env-file  {list} (optional) + Load the specified env file + --no-color {single} + Produce monochrome output. alias of --theme noColor. + --theme  {single} + choose color theme (default, default-force or noColor) - default-force means + colors will be produced even if command is piped + --help, -h {single} + Display this command help + --version {single} + Print version information and quit + --quiet, -q {single} + quiet mode, doesn't display any output + --log-level  {single} + Set log level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + --log-file  {single} + Set log file + --display-level  {single} + set display level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + +LIST OF AVAILABLE DSN: + - dsn_local + - dsn_valid + +DEFAULT QUERIES DIRECTORY: +/bash/conf/dbQueries + +USER QUERIES DIRECTORY: +home/.bash-tools/dbQueries +Allows to override queries defined in Default queries directory + +LIST OF AVAILABLE QUERIES: + + +EXAMPLES: +dbQueryAllDatabases databaseSize -j 12 --separator "|" --bar 2>/dev/null | column -s "|" -t -n -c 40 + +VERSION: 2.0 + +AUTHOR: +[François Chastanet](https://github.com/fchastanet) + +SOURCE FILE: +https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/DbQueryAllDatabases/dbQueryAllDatabases.sh + +LICENSE: +MIT License + +Copyright (c) 2020-now François Chastanet diff --git a/src/_binaries/DbQueryAllDatabases/testsData/parallelDbQueryAllDatabases b/src/_binaries/DbQueryAllDatabases/testsData/parallelDbQueryAllDatabases deleted file mode 100755 index 3a897f37..00000000 --- a/src/_binaries/DbQueryAllDatabases/testsData/parallelDbQueryAllDatabases +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -shift 5 # skip --eta --progress --tag --jobs 1 -command="$1" -shift - -while IFS= read -r db; do - "${command}" "$@" "${db}" || true -done diff --git a/src/_binaries/DbScriptAllDatabases/dbScriptAllDatabases.bats b/src/_binaries/DbScriptAllDatabases/dbScriptAllDatabases.bats index 4af3c1fb..37ea83f9 100755 --- a/src/_binaries/DbScriptAllDatabases/dbScriptAllDatabases.bats +++ b/src/_binaries/DbScriptAllDatabases/dbScriptAllDatabases.bats @@ -32,11 +32,7 @@ teardown() { } function Database::dbScriptAllDatabases::display_help { #@test - cp "${BATS_TEST_DIRNAME}/testsData/parallel" "${HOME}/bin" - # shellcheck disable=SC2154 - run "${binDir}/dbScriptAllDatabases" --help - assert_success - assert_line --index 2 --partial "Usage: dbScriptAllDatabases [-j|--jobs ] [-o|--output ] [-d|--dsn ] [-v|--verbose] [-l|--log-format ] [--database ] [optional parameters to pass to the script]" + testCommand "${binDir}/dbScriptAllDatabases" dbScriptAllDatabases.help.txt } function Database::dbScriptAllDatabases::script_file_not_provided { #@test @@ -48,12 +44,10 @@ function Database::dbScriptAllDatabases::script_file_not_provided { #@test } run f assert_failure 1 - assert_output --partial "FATAL - You must provide the script file to be executed" + assert_output --partial "ERROR - Command dbScriptAllDatabases - Argument 'scriptToExecute' should be provided at least 1 time(s)" } function Database::dbScriptAllDatabases::extractData { #@test - cp "${BATS_TEST_DIRNAME}/testsData/parallelDbScriptAllDatabases" "${HOME}/bin/parallel" - chmod +x "${HOME}/bin/parallel" export BATS_TEST_DIRNAME export HOME # shellcheck disable=SC2016 @@ -62,8 +56,12 @@ function Database::dbScriptAllDatabases::extractData { #@test '* --batch --raw --default-character-set=utf8 --connect-timeout=5 db1 -e * : echo "$8" > "${HOME}/query1" ; cat "${BATS_TEST_DIRNAME}/testsData/databaseSize.result_db1"' \ '* --batch --raw --default-character-set=utf8 --connect-timeout=5 db2 -e * : echo "$8" > "${HOME}/query2" ; cat "${BATS_TEST_DIRNAME}/testsData/databaseSize.result_db2"' + # shellcheck disable=SC2016 + stub parallel \ + '--eta --progress --tag --jobs=1 * * * * * * * * : while IFS= read -r db; do "${@:5}" "${db}"; done' + run "${binDir}/dbScriptAllDatabases" \ - -d "${BATS_TEST_DIRNAME}/testsData/databaseSize.envProvided.sh" \ + -f "${BATS_TEST_DIRNAME}/testsData/databaseSize.envProvided.sh" \ "${rootDir}/conf/dbScripts/extractData" \ "${rootDir}/conf/dbQueries/databaseSize.sql" diff --git a/src/_binaries/DbScriptAllDatabases/dbScriptAllDatabases.options.tpl b/src/_binaries/DbScriptAllDatabases/dbScriptAllDatabases.options.tpl new file mode 100644 index 00000000..ab637986 --- /dev/null +++ b/src/_binaries/DbScriptAllDatabases/dbScriptAllDatabases.options.tpl @@ -0,0 +1,159 @@ +% +declare versionNumber="2.0" +declare commandFunctionName="dbScriptAllDatabasesCommand" +declare help="Allows to execute a script on each database of specified mysql server" +# shellcheck disable=SC2016 +declare longDescription=''' +${__HELP_TITLE}NOTE:${__HELP_NORMAL} +the use of output, log-format, verbose options highly depends on the script used + +${__HELP_TITLE}LIST OF AVAILABLE DSN:${__HELP_NORMAL} +${dsnList} + +${__HELP_TITLE}DEFAULT QUERIES DIRECTORY:${__HELP_NORMAL} +${QUERIES_DIR-configuration error} + +${__HELP_TITLE}USER QUERIES DIRECTORY:${__HELP_NORMAL} +${HOME_QUERIES_DIR-configuration error} +Allows to override queries defined in "Default queries directory" + +${__HELP_TITLE}LIST OF AVAILABLE SCRIPTS:${__HELP_NORMAL} +${scriptsList} + +${__HELP_TITLE}EXAMPLES:${__HELP_NORMAL} script conf/dbScripts/extractData.sh + executes query databaseSize (see conf/dbQueries/databaseSize.sql) on each db and log the result in log file in default output dir, call it using + ${__HELP_EXAMPLE}$0 -j 10 extractData databaseSize${__HELP_NORMAL} + + executes query databaseSize on each db and display the result on stdout (2>/dev/null hides information messages) + ${__HELP_EXAMPLE}$0 -j 10 --log-format none extractData databaseSize${__HELP_NORMAL} + + use --verbose to get some debug information + ${__HELP_EXAMPLE}$0 -j 10 --log-format none --verbose extractData databaseSize${__HELP_NORMAL} + +${__HELP_TITLE}USE CASES:${__HELP_NORMAL} + you can use this script in order to check that each db model conforms with your ORM schema + simply create a new script in conf/dbQueries that will call your orm schema checker + + update multiple db at once (simple to complex update script) + +''' +declare defaultFromDsn="default.remote" +% + +.INCLUDE "$(dynamicTemplateDir _binaries/options/options.base.tpl)" +.INCLUDE "$(dynamicTemplateDir _binaries/options/options.dsn.tpl)" +.INCLUDE "$(dynamicTemplateDir _binaries/options/options.jobs.tpl)" +.INCLUDE "$(dynamicTemplateDir _binaries/options/options.progressBar.tpl)" + +% +# shellcheck source=/dev/null +source <( + Options::generateGroup \ + --title "SCRIPT OPTIONS:" \ + --function-name groupSourceDbOptionsFunction + + argScriptToExecuteCallback() { :; } + Options::generateArg \ + --help "the script that will be executed on each databases" \ + --name 'scriptToExecute' \ + --min 1 \ + --max 1 \ + --variable-name "argScriptToExecute" \ + --callback argScriptToExecuteCallback \ + --function-name argScriptToExecuteFunction + + Options::generateArg \ + --help "optional parameters to pass to the script" \ + --name 'scriptArguments' \ + --min 0 \ + --max -1 \ + --variable-name "scriptArguments" \ + --function-name scriptArgumentsFunction + + # shellcheck disable=SC2116 + Options::generateOption \ + --help "if provided will check only this db, otherwise script will be executed on all dbs of mysql server" \ + --help-value-name "dbName" \ + --variable-type "StringArray" \ + --group groupSourceDbOptionsFunction \ + --alt "--database" \ + --variable-name "optionDatabases" \ + --function-name optionDatabasesFunction + + # shellcheck disable=SC2116 + Options::generateOption \ + --help "output directory, see log-format option" \ + --help-value-name "outputDirectory" \ + --variable-type "String" \ + --group groupSourceDbOptionsFunction \ + --alt "--output" \ + --alt "-o" \ + --callback outputDirectoryCallback \ + --variable-name "optionOutputDir" \ + --function-name optionOutputDirFunction + + # shellcheck disable=SC2116 + Options::generateOption \ + --help "if output dir provided, will log each db result to log file" \ + --help-value-name "logFormat" \ + --authorized-values "none|log" \ + --default-value "none" \ + --group groupSourceDbOptionsFunction \ + --alt "--log-format" \ + --alt "-l" \ + --variable-type "String" \ + --variable-name "optionLogFormat" \ + --function-name optionLogFormatFunction +) +options+=( + argScriptToExecuteFunction + scriptArgumentsFunction + optionDatabasesFunction + optionOutputDirFunction + optionLogFormatFunction + --callback dbScriptAllDatabasesCommandCallback +) +Options::generateCommand "${options[@]}" +% + +optionHelpCallback() { + local dsnList queriesList scriptsList + dsnList="$(Conf::getMergedList "dsn" "env")" + queriesList="$(Conf::getMergedList "dbQueries" "sql" || true)" + scriptsList="$(Conf::getMergedList "dbScripts" "sh")" + + <% ${commandFunctionName} %> help | envsubst + exit 0 +} + +outputDirectoryCallback() { + if [[ "${optionOutputDir:0:1}" != "/" ]]; then + # relative path + optionOutputDir="${PWD}/${optionOutputDir}" + fi + mkdir -p "${optionOutputDir}" || Log::fatal "unable to create directory ${optionOutputDir}" + if [[ ! -d "${optionOutputDir}" || ! -w "${optionOutputDir}" ]]; then + Log::fatal "output dir is not correct or not writable" + fi +} + +argScriptToExecuteCallback() { + if [[ ! -f "${argScriptToExecute}" ]]; then + declare scriptAbsoluteFile + scriptAbsoluteFile="$(Conf::getAbsoluteFile "dbScripts" "${argScriptToExecute}" "sh")" && { + argScriptToExecute="${scriptAbsoluteFile}" + if (( BASH_FRAMEWORK_ARGS_VERBOSE >= __VERBOSE_LEVEL_DEBUG )); then + Log::displayInfo "Using script file ${scriptAbsoluteFile}" + fi + } + fi +} + +dbScriptAllDatabasesCommandCallback() { + if [[ -z "${optionFromDsn}" ]]; then + # default value for FROM_DSN if from-aws not set + optionFromDsn="<% ${defaultFromDsn} %>" + fi +} + +<% ${commandFunctionName} %> parse "${BASH_FRAMEWORK_ARGV[@]}" diff --git a/src/_binaries/DbScriptAllDatabases/dbScriptAllDatabases.sh b/src/_binaries/DbScriptAllDatabases/dbScriptAllDatabases.sh index 446b39db..2f718e88 100755 --- a/src/_binaries/DbScriptAllDatabases/dbScriptAllDatabases.sh +++ b/src/_binaries/DbScriptAllDatabases/dbScriptAllDatabases.sh @@ -1,169 +1,65 @@ #!/usr/bin/env bash # BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/dbScriptAllDatabases - -.INCLUDE "$(dynamicTemplateDir _includes/_header.tpl)" -.INCLUDE "$(dynamicTemplateDir _includes/_load.tpl)" - -Assert::expectNonRootUser - -#default values -SCRIPT_NAME=${0##*/} -JOBS_NUMBER=1 -MYSQL_OPTIONS="" -OUTPUT_DIR="${HOME}/.bash-tools/output" -DSN="default.local" -LOG_FORMAT="none" -DB_NAME="" - -# Usage info -showHelp() { - local dsnList scriptsList - dsnList="$(Conf::getMergedList "dsn" "env")" - scriptsList="$(Conf::getMergedList "dbScripts" "sh")" - - cat <] [-o|--output ] [-d|--dsn ] [-v|--verbose] [-l|--log-format ] [--database ] [optional parameters to pass to the script] - the script that will be executed on each databases - -d|--dsn target mysql server (Default: ${DSN}) - --database if provided will check only this db, otherwise script will be executed on all dbs of mysql server - -j|--jobs the number of db to query in parallel (default: ${JOBS_NUMBER}) - -o|--output output directory, see log-format option (default : "${OUTPUT_DIR}") - -l|--log-format if log provided, will log each db result to log file, can be one of these values (none, log) (default: none) - -v|--verbose display more information - -${__HELP_TITLE}Note:${__HELP_NORMAL} the use of output, log-format, verbose options highly depends on the script used - -${__HELP_TITLE}Example:${__HELP_NORMAL} script conf/dbScripts/extractData.sh - executes query databaseSize (see conf/dbQueries/databaseSize.sql) on each db and log the result in log file in default output dir, call it using - $0 -j 10 extractData databaseSize - - executes query databaseSize on each db and display the result on stdout (2>/dev/null hides information messages) - $0 -j 10 --log-format none extractData databaseSize - - use --verbose to get some debug information - $0 -j 10 --log-format none --verbose extractData databaseSize - -${__HELP_TITLE}Use cases:${__HELP_NORMAL} - you can use this script in order to check that each db model conforms with your ORM schema - simply create a new script in conf/dbQueries that will call your orm schema checker - - update multiple db at once (simple to complex update script) - -${__HELP_TITLE}List of available dsn:${__HELP_NORMAL} -${dsnList} -${__HELP_TITLE}list of available scripts (${SCRIPTS_FOLDER}):${__HELP_NORMAL} -${scriptsList} - -.INCLUDE "${ORIGINAL_TEMPLATE_DIR}/_includes/author.tpl" -EOF -} - -# read command parameters -# $@ is all command line parameters passed to the script. -# -o is for short options like -h -# -l is for long options with double dash like --help -# the comma separates different long options -options=$(getopt -l help,log-format:,dsn:,jobs:,database:,output:,verbose -o hd:j:l:o:v -- "$@" 2>/dev/null) || { - showHelp - Log::fatal "invalid options specified" +# VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. +# FACADE +# EMBED Db::queryOneDatabase as dbQueryOneDatabase +# shellcheck disable=SC2034 + +# default values +declare outputDirectory="${HOME}/.bash-tools/output" +declare optionFromDsn="" +declare optionOutputDir="" +declare optionLogFormat="" +declare optionJobs="" +declare argScriptToExecute="" +declare -a scriptArguments=() + +# other configuration +declare copyrightBeginYear="2020" +declare QUERIES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/dbQueries" +declare HOME_QUERIES_DIR="${HOME}/.bash-tools/dbQueries" + +.INCLUDE "$(dynamicTemplateDir _binaries/DbScriptAllDatabases/dbScriptAllDatabases.options.tpl)" + +# @require Linux::requireExecutedAsUser +run() { + + # check dependencies + Assert::commandExists mysql "sudo apt-get install -y mysql-client" + Assert::commandExists mysqlshow "sudo apt-get install -y mysql-client" + Assert::commandExists parallel "sudo apt-get install -y parallel" + + # create db instance + declare -Agx dbInstance + Database::newInstance dbInstance "${optionFromDsn}" + Database::setQueryOptions dbInstance "${dbInstance['QUERY_OPTIONS']} --connect-timeout=5" + if ((BASH_FRAMEWORK_ARGS_VERBOSE >= __VERBOSE_LEVEL_DEBUG)); then + Log::displayInfo "Using dsn ${dbInstance['DSN_FILE']}" + fi + + # list of all databases + if ((BASH_FRAMEWORK_ARGS_VERBOSE >= __VERBOSE_LEVEL_DEBUG)); then + Log::displayInfo "get the list of all databases" + fi + + if ((${#optionDatabases[@]} == 0)); then + mapfile -t optionDatabases < <(Database::getUserDbList dbInstance) + fi + + if ((BASH_FRAMEWORK_ARGS_VERBOSE >= __VERBOSE_LEVEL_DEBUG)); then + Log::displayInfo "processing ${#optionDatabases[@]} databases using ${optionJobs} jobs" + fi + + export selectedQueryFile + export MYSQL_OPTIONS + + printf '%s\n' "${optionDatabases[@]}" | parallel --eta --progress --tag --jobs="${optionJobs}" \ + "${argScriptToExecute}" "${optionFromDsn}" "${optionLogFormat}" "${BASH_FRAMEWORK_ARGS_VERBOSE}" \ + "${optionOutputDir}" "${PWD}" "${scriptArguments[@]}" } -eval set -- "${options}" -while true; do - case $1 in - -h | --help) - showHelp - exit 0 - ;; - --jobs | -j) - shift || true - JOBS_NUMBER=$1 - ;; - --output | -o) - shift || true - OUTPUT_DIR="$1" - ;; - --dsn | -d) - shift || true - DSN=${1:-:-default.local} - ;; - --database) - shift || true - DB_NAME="$1" - ;; - --log-format | -l) - shift || true - LOG_FORMAT="$1" - ;; - --) - shift || true - break - ;; - *) - showHelp - Log::fatal "invalid argument $1" - ;; - esac - shift || true -done - -# check dependencies -Assert::commandExists mysql "sudo apt-get install -y mysql-client" -Assert::commandExists mysqlshow "sudo apt-get install -y mysql-client" -Assert::commandExists parallel "sudo apt-get install -y parallel" - -# output-dir argument -if ! Array::contains "${LOG_FORMAT}" "none" "log"; then - Log::fatal "log format '${LOG_FORMAT}' not supported" -fi - -# additional arguments -shift $((OPTIND - 1)) || true -SCRIPT="$1" -shift || true -if [[ -z "${SCRIPT}" ]]; then - Log::fatal "You must provide the script file to be executed" -fi - -if [[ "${OUTPUT_DIR:0:1}" != "/" ]]; then - # relative path - OUTPUT_DIR="${PWD}/${OUTPUT_DIR}" -fi -mkdir -p "${OUTPUT_DIR}" || Log::fatal "unable to create directory ${OUTPUT_DIR}" -[[ -d "${OUTPUT_DIR}" && -w "${OUTPUT_DIR}" ]] || - Log::fatal "output dir is not correct or not writable" - -if ! [[ ${JOBS_NUMBER} =~ ^[0-9]+$ ]]; then - Log::fatal "number of jobs is incorrect" -fi -[[ ${JOBS_NUMBER} -lt 1 ]] && Log::fatal "number of jobs must be greater than 0" - -# try script inside script folder -SCRIPT="$(Conf::getAbsoluteFile "dbScripts" "${SCRIPT}" "sh")" || exit 1 -[[ "${ARGS_VERBOSE}" = "1" ]] && Log::displayInfo "Using script ${SCRIPT}" -# create db instance -declare -Agx dbInstance - -Database::newInstance dbInstance "${DSN}" -Database::setQueryOptions dbInstance "${dbInstance['QUERY_OPTIONS']} --connect-timeout=5" -[[ "${ARGS_VERBOSE}" = "1" ]] && Log::displayInfo "Using dsn ${dbInstance['DSN_FILE']}" - -# list of all databases -[[ "${ARGS_VERBOSE}" = "1" ]] && Log::displayInfo "get the list of all databases" -if [[ -z "${DB_NAME}" ]]; then - allDbs="$(Database::getUserDbList dbInstance)" +if [[ "${BASH_FRAMEWORK_QUIET_MODE:-0}" = "1" ]]; then + run &>/dev/null else - allDbs="${DB_NAME}" + run fi - -[[ "${ARGS_VERBOSE}" = "1" ]] && Log::displayInfo "processing $(echo "${allDbs}" | wc -l) databases using ${JOBS_NUMBER} jobs" - -export selectedQueryFile -export MYSQL_OPTIONS - -echo "${allDbs}" | parallel --eta --progress --tag --jobs="${JOBS_NUMBER}" \ - "${SCRIPT}" "${DSN}" "${LOG_FORMAT}" "${ARGS_VERBOSE}" \ - "${OUTPUT_DIR}" "${PWD}" "$@" diff --git a/src/_binaries/DbScriptAllDatabases/extractData.sh b/src/_binaries/DbScriptAllDatabases/extractData.sh index 18402f34..76609436 100755 --- a/src/_binaries/DbScriptAllDatabases/extractData.sh +++ b/src/_binaries/DbScriptAllDatabases/extractData.sh @@ -1,48 +1,41 @@ #!/usr/bin/env bash # BIN_FILE=${FRAMEWORK_ROOT_DIR}/conf/dbScripts/extractData - -.INCLUDE "$(dynamicSrcFile _includes/dbScriptOneDatabase.sh)" - -HELP="$( - cat </dev/null || echo "")" -if [[ -n "${queryFile}" ]]; then - queryName="$(basename "${queryFile%.*}")" - query="$(cat "${queryFile}")" -fi - -# create log file -declare logFile="" -if [[ "${LOG_FORMAT}" = "log" ]]; then - # shellcheck disable=SC2154 - declare logFile="${outputDir}/${db}_${queryName}.log" - exec 6>&1 1>"${logFile}" # redirect stdout to logFile -fi - -Database::skipColumnNames dbInstance 0 -Database::query dbInstance "${query}" "${db}" || true -Database::skipColumnNames dbInstance 1 - -if [[ "${LOG_FORMAT}" = "log" ]]; then - # restore stdout - exec 1>&6 6>&- -fi +.INCLUDE "$(dynamicSrcFile _includes/dbScriptOneDatabase.sh)" -[[ "${LOG_FORMAT}" = "log" ]] && Log::displayInfo "result available in '${logFile}'" +run() { + # extra parameters passed through dbScriptAllDatabases + declare query="${scriptParameters[0]}" + declare queryName="customQuery" + declare queryFile="${query}" + queryFile="$(Conf::getAbsoluteFile "dbQueries" "${queryFile}" "sql" 2>/dev/null || echo "")" + if [[ -n "${queryFile}" ]]; then + queryName="$(basename "${queryFile%.*}")" + query="$(cat "${queryFile}")" + fi + + # create log file + declare logFile="" + if [[ "${LOG_FORMAT}" = "log" ]]; then + declare logFile="${outputDir}/${db}_${queryName}.log" + exec 6>&1 1>"${logFile}" # redirect stdout to logFile + fi + + Database::skipColumnNames dbInstance 0 + Database::query dbInstance "${query}" "${db}" || true + Database::skipColumnNames dbInstance 1 + + if [[ "${LOG_FORMAT}" = "log" ]]; then + # restore stdout + exec 1>&6 6>&- + fi + + if [[ "${LOG_FORMAT}" = "log" ]]; then + Log::displayInfo "result available in '${logFile}'" + fi +} + +init +run diff --git a/src/_binaries/DbScriptAllDatabases/testsData/dbScriptAllDatabases.help.txt b/src/_binaries/DbScriptAllDatabases/testsData/dbScriptAllDatabases.help.txt new file mode 100644 index 00000000..884015af --- /dev/null +++ b/src/_binaries/DbScriptAllDatabases/testsData/dbScriptAllDatabases.help.txt @@ -0,0 +1,113 @@ +DESCRIPTION: +Allows to execute a script on each database of specified mysql server + +USAGE: dbScriptAllDatabases [OPTIONS] [ARGUMENTS] +USAGE: dbScriptAllDatabases [--jobs|-j ] [--bar|-b] + [--from-dsn|-f ] [--database ] [--output|-o ] + [--log-format|-l ] [--bash-framework-config ] [--config] + [--verbose|-v] [-vv] [-vvv] [--env-file ] [--no-color] + [--theme ] [--help|-h] [--version] [--quiet|-q] [--log-level ] + [--log-file ] [--display-level ] + +ARGUMENTS: + scriptToExecute {single} (mandatory) + the script that will be executed on each databases + [scriptArguments {list} (optional)] + optional parameters to pass to the script + +OPTIONS: + --jobs, -j  {single} + specify the number of db to query in parallel + Default value: 1 + --bar, -b {single} + Show progress as a progress bar. In the bar is shown: % of jobs completed, e + stimated seconds left, and number of jobs started. + +SCRIPT OPTIONS: + --from-dsn, -f  {single} + target mysql server + --database  {list} (optional) + if provided will check only this db, otherwise script will be executed on al + l dbs of mysql server + --output, -o  {single} + output directory, see log-format option + --log-format, -l  {single} + if output dir provided, will log each db result to log file + Default value: none + +GLOBAL OPTIONS: + --bash-framework-config  {single} + use alternate bash framework configuration. + --config {single} + Display configuration + --verbose, -v {single} + info level verbose mode (alias of --display-level INFO) + -vv {single} + debug level verbose mode (alias of --display-level DEBUG) + -vvv {single} + trace level verbose mode (alias of --display-level TRACE) + --env-file  {list} (optional) + Load the specified env file + --no-color {single} + Produce monochrome output. alias of --theme noColor. + --theme  {single} + choose color theme (default, default-force or noColor) - default-force means + colors will be produced even if command is piped + --help, -h {single} + Display this command help + --version {single} + Print version information and quit + --quiet, -q {single} + quiet mode, doesn't display any output + --log-level  {single} + Set log level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + --log-file  {single} + Set log file + --display-level  {single} + set display level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + +NOTE: +the use of output, log-format, verbose options highly depends on the script used + +LIST OF AVAILABLE DSN: + - dsn_local + - dsn_valid + +DEFAULT QUERIES DIRECTORY: +/bash/conf/dbQueries + +USER QUERIES DIRECTORY: +home/.bash-tools/dbQueries +Allows to override queries defined in Default queries directory + +LIST OF AVAILABLE SCRIPTS: + + +EXAMPLES: script conf/dbScripts/extractData.sh + executes query databaseSize (see conf/dbQueries/databaseSize.sql) on each db and log the result in log file in default output dir, call it using + /bash/bin/dbScriptAllDatabases -j 10 extractData databaseSize + + executes query databaseSize on each db and display the result on stdout (2>/dev/null hides information messages) + /bash/bin/dbScriptAllDatabases -j 10 --log-format none extractData databaseSize + + use --verbose to get some debug information + /bash/bin/dbScriptAllDatabases -j 10 --log-format none --verbose extractData databaseSize + +USE CASES: + you can use this script in order to check that each db model conforms with your ORM schema + simply create a new script in conf/dbQueries that will call your orm schema checker + + update multiple db at once (simple to complex update script) + +VERSION: 2.0 + +AUTHOR: +[François Chastanet](https://github.com/fchastanet) + +SOURCE FILE: +https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/DbScriptAllDatabases/dbScriptAllDatabases.sh + +LICENSE: +MIT License + +Copyright (c) 2020-now François Chastanet diff --git a/src/_binaries/Docker/cli.bats b/src/_binaries/Docker/cli.bats index c072d677..099d4483 100755 --- a/src/_binaries/Docker/cli.bats +++ b/src/_binaries/Docker/cli.bats @@ -27,18 +27,15 @@ teardown() { } function Docker::cli::display_help { #@test - # shellcheck disable=SC2154 - run "${binDir}/cli" --help 2>&1 - assert_success - assert_line --index 0 "DESCRIPTION: easy connection to docker container" + testCommand "${binDir}/cli" cli.help.txt } function Docker::cli::without_any_parameter_connects_to_default_container { #@test stub_tput if read -r -t 0; then - stub docker 'exec -i -e COLUMNS=80 -e LINES=23 --user=www-data project-apache2 //bin/bash : echo "connected to container"' + stub docker 'exec -i -e COLUMNS=80 -e LINES=23 --user=www-data project-apache2 /bin/bash : echo "connected to container"' else - stub docker 'exec -it -e COLUMNS=80 -e LINES=23 --user=www-data project-apache2 //bin/bash : echo "connected to container"' + stub docker 'exec -it -e COLUMNS=80 -e LINES=23 --user=www-data project-apache2 /bin/bash : echo "connected to container"' fi run "${binDir}/cli" 2>&1 @@ -99,9 +96,9 @@ function Docker::cli::add_a_custom_profile_and_use_this_profile { #@test function Docker::cli::to_a_container_without_a_matching_profile { #@test stub_tput if read -r -t 0; then - stub docker 'exec -i -e COLUMNS=80 -e LINES=23 --user=www-data my-container //bin/bash : echo "connected to container"' + stub docker 'exec -i -e COLUMNS=80 -e LINES=23 --user=www-data my-container /bin/bash : echo "connected to container"' else - stub docker 'exec -it -e COLUMNS=80 -e LINES=23 --user=www-data my-container //bin/bash : echo "connected to container"' + stub docker 'exec -it -e COLUMNS=80 -e LINES=23 --user=www-data my-container /bin/bash : echo "connected to container"' fi run "${binDir}/cli" my-container 2>&1 diff --git a/src/_binaries/Docker/cli.options.tpl b/src/_binaries/Docker/cli.options.tpl index 355c9505..81277e2e 100644 --- a/src/_binaries/Docker/cli.options.tpl +++ b/src/_binaries/Docker/cli.options.tpl @@ -14,7 +14,7 @@ ${containers} ${__HELP_TITLE}EXAMPLES:${__HELP_EXAMPLE} to connect to mysql container in bash mode with user mysql - ${SCRIPT_NAME} mysql mysql "//bin/bash" + ${SCRIPT_NAME} mysql mysql '/bin/bash' to connect to web container with user root ${SCRIPT_NAME} web root ${__HELP_NORMAL} @@ -26,8 +26,6 @@ arguments ${__HELP_OPTION_COLOR}userArg${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}c The script has to compute the following variables ${__HELP_OPTION_COLOR}finalUserArg${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}finalContainerArg${__HELP_NORMAL}, ${__HELP_OPTION_COLOR}finalCommandArg${__HELP_NORMAL} ''' -declare defaultUserArg="root" -declare -a defaultCommandArg=("//bin/sh") % .INCLUDE "$(dynamicTemplateDir _binaries/options/options.base.tpl)" % @@ -69,16 +67,6 @@ options+=( Options::generateCommand "${options[@]}" % -# default values -declare containerArg="default" -declare finalUserArg="<% ${defaultUserArg} %>" -declare finalCommandArg=("<% ${defaultCommandArg[@]} %>") -declare copyrightBeginYear="2020" - -# constants -PROFILES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/cliProfiles" -HOME_PROFILES_DIR="${HOME}/.bash-tools/cliProfiles" - containerArgHelpCallback() { Conf::load "cliProfiles" "default" echo "container should be the name of a profile from profile list," @@ -125,3 +113,5 @@ optionHelpCallback() { unknownOption() { commandArg+=("$1") } + +<% ${commandFunctionName} %> parse "${BASH_FRAMEWORK_ARGV[@]}" diff --git a/src/_binaries/Docker/cli.sh b/src/_binaries/Docker/cli.sh index 43b29ff7..6e0f8aaf 100755 --- a/src/_binaries/Docker/cli.sh +++ b/src/_binaries/Docker/cli.sh @@ -2,14 +2,25 @@ # BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/cli # VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. # FACADE +# shellcheck disable=SC2034 -.INCLUDE "$(dynamicTemplateDir _binaries/Docker/cli.options.tpl)" +# constants +declare defaultUserArg="root" +declare -a defaultCommandArg=("//bin/sh") +declare PROFILES_DIR="${BASH_TOOLS_ROOT_DIR}/conf/cliProfiles" +declare HOME_PROFILES_DIR="${HOME}/.bash-tools/cliProfiles" -cliCommand parse "${BASH_FRAMEWORK_ARGV[@]}" +# option values +declare containerArg="default" +declare finalUserArg="${defaultUserArg}" +declare finalCommandArg=("${defaultCommandArg[@]}") -run() { +# other values +declare copyrightBeginYear="2020" +.INCLUDE "$(dynamicTemplateDir _binaries/Docker/cli.options.tpl)" +run() { # Internal function that can be used in conf profiles to load the dsn file loadDsn() { local dsn="$1" diff --git a/src/_binaries/Docker/testsData/cli.help.txt b/src/_binaries/Docker/testsData/cli.help.txt new file mode 100644 index 00000000..f9297ec2 --- /dev/null +++ b/src/_binaries/Docker/testsData/cli.help.txt @@ -0,0 +1,99 @@ +DESCRIPTION: easy connection to docker container + +USAGE: cli [OPTIONS] [ARGUMENTS] +USAGE: cli [--bash-framework-config ] [--config] [--verbose|-v] [-vv] + [-vvv] [--env-file ] [--no-color] [--theme ] [--help|-h] + [--version] [--quiet|-q] [--log-level ] [--log-file ] + [--display-level ] + +ARGUMENTS: + [container {single}] + container should be the name of a profile from profile list, + check containers list below. + If not provided, it will load the container specified in default configu + ration. + Default configuration: default + Default container: project-apache2 + [user {single}] + user to connect on this container + Default user: www-data + loaded from profile selected as first arg + or deduced from default configuration. + Default configuration: default if first arg is not a profile + [commandArg {single}] + The command to execute + Default command: /bin/bash + loaded from profile selected as first arg + or deduced from default configuration. + Default configuration: default if first arg is not a profile + +GLOBAL OPTIONS: + --bash-framework-config  {single} + use alternate bash framework configuration. + --config {single} + Display configuration + --verbose, -v {single} + info level verbose mode (alias of --display-level INFO) + -vv {single} + debug level verbose mode (alias of --display-level DEBUG) + -vvv {single} + trace level verbose mode (alias of --display-level TRACE) + --env-file  {list} (optional) + Load the specified env file + --no-color {single} + Produce monochrome output. alias of --theme noColor. + --theme  {single} + choose color theme (default, default-force or noColor) - default-force means + colors will be produced even if command is piped + --help, -h {single} + Display this command help + --version {single} + Print version information and quit + --quiet, -q {single} + quiet mode, doesn't display any output + --log-level  {single} + Set log level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + --log-file  {single} + Set log file + --display-level  {single} + set display level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + +AVAILABLE PROFILES (from /bash/conf/cliProfiles) +This list can be overridden in home/.bash-tools/cliProfiles + + - default + - mysql + - mysql.remote + - node + - redis + - web + +AVAILABLE CONTAINERS: + + +EXAMPLES: + to connect to mysql container in bash mode with user mysql + cli mysql mysql /bin/bash + to connect to web container with user root + cli web root + + +CREATE NEW PROFILE: +You can create new profiles in home/.bash-tools/cliProfiles. +This script will be called with the +arguments userArg, containerArg, commandArg +The script has to compute the following +variables finalUserArg, finalContainerArg, finalCommandArg + +VERSION: 2.0 + +AUTHOR: +[François Chastanet](https://github.com/fchastanet) + +SOURCE FILE: +https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/Docker/cli.sh + +LICENSE: +MIT License + +Copyright (c) 2020-now François Chastanet diff --git a/src/_binaries/Git/gitIsAncestorOf.bats b/src/_binaries/Git/gitIsAncestorOf.bats new file mode 100755 index 00000000..7bf21d62 --- /dev/null +++ b/src/_binaries/Git/gitIsAncestorOf.bats @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# shellcheck source=src/batsHeaders.sh +source "$(cd "${BATS_TEST_DIRNAME}/../.." && pwd)/batsHeaders.sh" + +load "${FRAMEWORK_ROOT_DIR}/src/_standalone/Bats/assert_lines_count.sh" + +setup() { + export TMPDIR="${BATS_TEST_TMPDIR}" + export HOME="${BATS_TEST_TMPDIR}/home" + export BASH_FRAMEWORK_ENV_FILEPATH="${BATS_TEST_DIRNAME}/testsData/.env" +} + +teardown() { + unstub_all +} + +function Git::gitIsAncestorOf::display_help { #@test + testCommand "${binDir}/gitIsAncestorOf" gitIsAncestorOf.help.txt +} diff --git a/src/_binaries/Git/gitIsAncestorOf.options.tpl b/src/_binaries/Git/gitIsAncestorOf.options.tpl index c859a30f..7f332e96 100644 --- a/src/_binaries/Git/gitIsAncestorOf.options.tpl +++ b/src/_binaries/Git/gitIsAncestorOf.options.tpl @@ -14,7 +14,7 @@ ${__HELP_OPTION_COLOR}2${__HELP_NORMAL}: if commit is not included in given bran % # shellcheck source=/dev/null source <( - containerArgHelpCallback() { :; } + containerArgHelpCallback() { :; } Options::generateArg \ --help "the branch in which the commit will be searched" \ --min 1 \ @@ -38,6 +38,5 @@ options+=( ) Options::generateCommand "${options[@]}" % -declare copyrightBeginYear="2020" -declare claimedBranchArg="" -declare commitArg="" + +<% ${commandFunctionName} %> parse "${BASH_FRAMEWORK_ARGV[@]}" diff --git a/src/_binaries/Git/gitIsAncestorOf.sh b/src/_binaries/Git/gitIsAncestorOf.sh index 6c13d3c2..66173eb0 100755 --- a/src/_binaries/Git/gitIsAncestorOf.sh +++ b/src/_binaries/Git/gitIsAncestorOf.sh @@ -2,10 +2,13 @@ # BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/gitIsAncestorOf # VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. # FACADE +# shellcheck disable=SC2034 -.INCLUDE "$(dynamicTemplateDir _binaries/Git/gitIsAncestorOf.options.tpl)" +declare copyrightBeginYear="2020" +declare claimedBranchArg="" +declare commitArg="" -gitIsAncestorOfCommand parse "${BASH_FRAMEWORK_ARGV[@]}" +.INCLUDE "$(dynamicTemplateDir _binaries/Git/gitIsAncestorOf.options.tpl)" # @require Linux::requireExecutedAsUser run() { @@ -15,7 +18,6 @@ run() { exit 1 fi - # shellcheck disable=SC2154 merge_base="$(git merge-base "${commitArg}" "${claimedBranchArg}")" if [[ -z "${merge_base}" || "${merge_base}" != "$(git rev-parse --verify "${commitArg}")" ]]; then Log::displayError "Commit ${commitArg} is not an ancestor of branch ${claimedBranchArg}" diff --git a/src/_binaries/Git/gitIsBranch.options.tpl b/src/_binaries/Git/gitIsBranch.options.tpl index d6873ac9..243b6e0a 100644 --- a/src/_binaries/Git/gitIsBranch.options.tpl +++ b/src/_binaries/Git/gitIsBranch.options.tpl @@ -9,7 +9,7 @@ declare help="show an error if branchName is not a known branch" % # shellcheck source=/dev/null source <( - containerArgHelpCallback() { :; } + containerArgHelpCallback() { :; } Options::generateArg \ --help "the branch name to check" \ --min 1 \ @@ -24,5 +24,3 @@ options+=( ) Options::generateCommand "${options[@]}" % -declare copyrightBeginYear="2020" -declare branchNameArg="" diff --git a/src/_binaries/Git/gitIsBranch.sh b/src/_binaries/Git/gitIsBranch.sh index 224b674a..bb8af53e 100755 --- a/src/_binaries/Git/gitIsBranch.sh +++ b/src/_binaries/Git/gitIsBranch.sh @@ -2,10 +2,12 @@ # BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/gitIsBranch # VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. # FACADE +# shellcheck disable=SC2034 -.INCLUDE "$(dynamicTemplateDir _binaries/Git/gitIsBranch.options.tpl)" +declare copyrightBeginYear="2020" +declare branchNameArg="" -gitIsBranchCommand parse "${BASH_FRAMEWORK_ARGV[@]}" +.INCLUDE "$(dynamicTemplateDir _binaries/Git/gitIsBranch.options.tpl)" # @require Linux::requireExecutedAsUser run() { diff --git a/src/_binaries/Git/gitRenameBranch.bats b/src/_binaries/Git/gitRenameBranch.bats index f5d47df4..31db7b8a 100755 --- a/src/_binaries/Git/gitRenameBranch.bats +++ b/src/_binaries/Git/gitRenameBranch.bats @@ -34,10 +34,7 @@ teardown() { } function Git::gitRenameBranch::display_help { #@test - export INTERACTIVE=0 - run "${binDir}/gitRenameBranch" --help 2>&1 - assert_success - assert_line --index 0 "DESCRIPTION: rename git local branch, push new branch and delete old branch" + testCommand "${binDir}/gitRenameBranch" gitRenameBranch.help.txt } function Git::gitRenameBranch::not_a_git_repository { #@test @@ -100,7 +97,7 @@ function Git::gitRenameBranch::rename_local_and_push_branch { #@test 'branch -m oldName newBranch : exit 0' \ 'push --set-upstream origin newBranch : exit 0' - run "${binDir}/gitRenameBranch" newBranch --push --verbose 2>&1 <<< 'y' + run "${binDir}/gitRenameBranch" newBranch --push --verbose 2>&1 <<<'y' assert_success assert_line -n 0 --partial "INFO - Renaming branch locally from oldName to newBranch" @@ -116,7 +113,7 @@ function Git::gitRenameBranch::rename_local_push_delete_remote_branch { #@test 'push origin :oldName : exit 0' \ 'push --set-upstream origin newBranch : exit 0' - run "${binDir}/gitRenameBranch" newBranch --push --delete --verbose 2>&1 <<< 'yy' + run "${binDir}/gitRenameBranch" newBranch --push --delete --verbose 2>&1 <<<'yy' assert_success assert_line -n 0 --partial "INFO - Renaming branch locally from oldName to newBranch" assert_line -n 1 --partial "INFO - Removing eventual old remote branch oldName" @@ -130,7 +127,7 @@ function Git::gitRenameBranch::rename_local_and_delete_remote_branch { #@test 'branch -m oldName newBranch : exit 0' \ 'push origin :oldName : exit 0' - run "${binDir}/gitRenameBranch" newBranch oldName --delete --verbose 2>&1 <<< 'y' + run "${binDir}/gitRenameBranch" newBranch oldName --delete --verbose 2>&1 <<<'y' assert_success assert_line -n 0 --partial "INFO - Renaming branch locally from oldName to newBranch" @@ -145,7 +142,7 @@ function Git::gitRenameBranch::rename_local_and_delete_remote_branch_without_old 'branch -m oldName newBranch : exit 0' \ 'push origin :oldName : exit 0' - run "${binDir}/gitRenameBranch" newBranch --delete --verbose 2>&1 <<< 'y' + run "${binDir}/gitRenameBranch" newBranch --delete --verbose 2>&1 <<<'y' assert_success assert_line -n 0 --partial "INFO - Renaming branch locally from oldName to newBranch" @@ -176,7 +173,7 @@ function Git::gitRenameBranch::rename_local_push_delete_remote_branch_assume_yes 'push origin :oldName : exit 0' \ 'push --set-upstream origin newBranch : exit 0' - run "${binDir}/gitRenameBranch" newBranch --push --delete --assume-yes --verbose 2>&1 <<< 'yy' + run "${binDir}/gitRenameBranch" newBranch --push --delete --assume-yes --verbose 2>&1 <<<'yy' assert_success assert_line -n 0 --partial "INFO - Renaming branch locally from oldName to newBranch" @@ -191,7 +188,7 @@ function Git::gitRenameBranch::rename_local_and_delete_remote_branch_assume_yes 'branch -m oldName newBranch : echo "git branch -m oldName newBranch"' \ 'push origin :oldName : echo "git push origin :oldName"' - run "${binDir}/gitRenameBranch" newBranch oldName --delete --assume-yes --verbose 2>&1 <<< 'y' + run "${binDir}/gitRenameBranch" newBranch oldName --delete --assume-yes --verbose 2>&1 <<<'y' assert_success assert_lines_count 4 @@ -206,7 +203,7 @@ function Git::gitRenameBranch::rename_local_and_delete_remote_branch_without_old 'branch -m oldName newBranch : echo "git branch -m oldName newBranch"' \ 'push origin :oldName : echo "git push origin :oldName"' - run "${binDir}/gitRenameBranch" newBranch --delete --assume-yes --verbose 2>&1 <<< 'y' + run "${binDir}/gitRenameBranch" newBranch --delete --assume-yes --verbose 2>&1 <<<'y' assert_success assert_lines_count 4 diff --git a/src/_binaries/Git/gitRenameBranch.options.tpl b/src/_binaries/Git/gitRenameBranch.options.tpl index bb87206d..4c720a28 100644 --- a/src/_binaries/Git/gitRenameBranch.options.tpl +++ b/src/_binaries/Git/gitRenameBranch.options.tpl @@ -72,14 +72,6 @@ options+=( ) Options::generateCommand "${options[@]}" % -declare copyrightBeginYear="2020" - -#default values -declare optionPush="0" -declare optionDelete="0" -declare optionAssumeYes="0" -declare newBranchNameArg="" -declare oldBranchNameArg="" assumeYesHelpCallback() { echo "do not ask for confirmation (use with caution)" $'\n' @@ -93,3 +85,5 @@ commandCallback() { exit 6 fi } + +<% ${commandFunctionName} %> parse "${BASH_FRAMEWORK_ARGV[@]}" diff --git a/src/_binaries/Git/gitRenameBranch.sh b/src/_binaries/Git/gitRenameBranch.sh index b08a71fa..a356254f 100755 --- a/src/_binaries/Git/gitRenameBranch.sh +++ b/src/_binaries/Git/gitRenameBranch.sh @@ -2,11 +2,19 @@ # BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/gitRenameBranch # VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. # FACADE -# shellcheck disable=SC2154 +# shellcheck disable=SC2034 -.INCLUDE "$(dynamicTemplateDir _binaries/Git/gitRenameBranch.options.tpl)" +# variables values +declare optionPush="0" +declare optionDelete="0" +declare optionAssumeYes="0" +declare newBranchNameArg="" +declare oldBranchNameArg="" + +# other values +declare copyrightBeginYear="2020" -gitRenameBranchCommand parse "${BASH_FRAMEWORK_ARGV[@]}" +.INCLUDE "$(dynamicTemplateDir _binaries/Git/gitRenameBranch.options.tpl)" # @require Linux::requireExecutedAsUser run() { @@ -78,4 +86,3 @@ if [[ "${BASH_FRAMEWORK_QUIET_MODE:-0}" = "1" ]]; then else run fi - diff --git a/src/_binaries/Git/testsData/gitIsAncestorOf.help.txt b/src/_binaries/Git/testsData/gitIsAncestorOf.help.txt new file mode 100644 index 00000000..e4db7d17 --- /dev/null +++ b/src/_binaries/Git/testsData/gitIsAncestorOf.help.txt @@ -0,0 +1,61 @@ +DESCRIPTION: check if commit is inside a given branch + +USAGE: gitIsAncestorOf [OPTIONS] [ARGUMENTS] +USAGE: gitIsAncestorOf [--bash-framework-config ] [--config] + [--verbose|-v] [-vv] [-vvv] [--env-file ] [--no-color] + [--theme ] [--help|-h] [--version] [--quiet|-q] [--log-level ] + [--log-file ] [--display-level ] + +ARGUMENTS: + claimedBranch {single} (mandatory) + the branch in which the commit will be searched + commit {single} (mandatory) + the commit oid to check + +GLOBAL OPTIONS: + --bash-framework-config  {single} + use alternate bash framework configuration. + --config {single} + Display configuration + --verbose, -v {single} + info level verbose mode (alias of --display-level INFO) + -vv {single} + debug level verbose mode (alias of --display-level DEBUG) + -vvv {single} + trace level verbose mode (alias of --display-level TRACE) + --env-file  {list} (optional) + Load the specified env file + --no-color {single} + Produce monochrome output. alias of --theme noColor. + --theme  {single} + choose color theme (default, default-force or noColor) - default-force means + colors will be produced even if command is piped + --help, -h {single} + Display this command help + --version {single} + Print version information and quit + --quiet, -q {single} + quiet mode, doesn't display any output + --log-level  {single} + Set log level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + --log-file  {single} + Set log file + --display-level  {single} + set display level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + +EXIT CODES: +1: if commit does not exists +2: if commit is not included in given branch + +VERSION: 1.0 + +AUTHOR: +[François Chastanet](https://github.com/fchastanet) + +SOURCE FILE: +https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/Git/gitIsAncestorOf.sh + +LICENSE: +MIT License + +Copyright (c) 2020-now François Chastanet diff --git a/src/_binaries/Git/testsData/gitRenameBranch.help.txt b/src/_binaries/Git/testsData/gitRenameBranch.help.txt new file mode 100644 index 00000000..2b19e695 --- /dev/null +++ b/src/_binaries/Git/testsData/gitRenameBranch.help.txt @@ -0,0 +1,81 @@ +DESCRIPTION: rename git local branch, push new branch and delete old branch + +USAGE: gitRenameBranch [OPTIONS] [ARGUMENTS] +USAGE: gitRenameBranch [--assume-yes|--yes|-y] [--push|-p] [--delete|-d] + [--bash-framework-config ] [--config] [--verbose|-v] [-vv] [-vvv] + [--env-file ] [--no-color] [--theme ] [--help|-h] [--version] + [--quiet|-q] [--log-level ] [--log-file ] + [--display-level ] + +ARGUMENTS: + newBranchName {single} (mandatory) + the branch name to check + [oldBranchName {single}] + the name of the old branch if not current one + +OPTIONS: + --assume-yes, --yes, -y {single} + do not ask for confirmation (use with caution) + Automatic yes to prompts; assume "y" as answer to all prompts + and run non-interactively. + --push, -p {single} + push the new branch + --delete, -d {single} + delete the old remote branch + +GLOBAL OPTIONS: + --bash-framework-config  {single} + use alternate bash framework configuration. + --config {single} + Display configuration + --verbose, -v {single} + info level verbose mode (alias of --display-level INFO) + -vv {single} + debug level verbose mode (alias of --display-level DEBUG) + -vvv {single} + trace level verbose mode (alias of --display-level TRACE) + --env-file  {list} (optional) + Load the specified env file + --no-color {single} + Produce monochrome output. alias of --theme noColor. + --theme  {single} + choose color theme (default, default-force or noColor) - default-force means + colors will be produced even if command is piped + --help, -h {single} + Display this command help + --version {single} + Print version information and quit + --quiet, -q {single} + quiet mode, doesn't display any output + --log-level  {single} + Set log level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + --log-file  {single} + Set log file + --display-level  {single} + set display level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + +EXIT CODES: +1 : if current directory is not a git repository + or if invalid or missing arguments +2 : if impossible to compute current branch name +3 : master/main branch not supported by this command, + please do it manually +5 : New and old branch names are the same +6 : You can use this tool in non interactive mode only + if --assume-yes option is provided +7 : if failed to rename local branch +8 : if remote branch deletion failed +9 : if failed to push the new branch + +VERSION: 1.0 + +AUTHOR: +[François Chastanet](https://github.com/fchastanet) + +SOURCE FILE: +https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/Git/gitRenameBranch.sh + +LICENSE: +MIT License + +Copyright (c) 2020-now François Chastanet diff --git a/src/_binaries/Git/testsData/upgradeGithubRelease.help.txt b/src/_binaries/Git/testsData/upgradeGithubRelease.help.txt new file mode 100644 index 00000000..7cc9e555 --- /dev/null +++ b/src/_binaries/Git/testsData/upgradeGithubRelease.help.txt @@ -0,0 +1,117 @@ +DESCRIPTION: retrieve latest binary release from github and install it + +USAGE: upgradeGithubRelease [OPTIONS] [ARGUMENTS] +USAGE: upgradeGithubRelease [--version-arg ] + [--current-version|-c ] [--exact-version|-e ] + [--minimal-version|-m ] [--bash-framework-config ] + [--config] [--verbose|-v] [-vv] [-vvv] [--env-file ] [--no-color] + [--theme ] [--help|-h] [--version] [--quiet|-q] [--log-level ] + [--log-file ] [--display-level ] + +ARGUMENTS: + targetFile {single} (mandatory) + the binary downloaded will e written to this file path. Ensure the path is w + ritable. + githubUrlPattern {single} (mandatory) + the url pattern to use to download the binary, see examples below. + @version@ is template variable that will be replaced by the latest + version tag found on github. + +VERSION MANAGEMENT: + --version-arg  {single} + The argument that will be provided to the currently installed binary to che + ck the version of the software. This parameter is needed if --minim + al-version argument is used and is different than default value ([ + 2;97m--version). + Default value: --version + --current-version, -c  {single} + Sometimes the command to retrieve the version is complicated. Some comman + d needs you to parse json or other commands provides multiple sub comma + nd versions. In this case you can provide the version you currently hav + e, see examples below. + --exact-version, -e  {single} + if provided and currently installed binary is not this exactVersion, + This exact version of the binary will be installed. + --minimal-version, -m  {single} + if provided and currently installed binary is below this minimalVersion, + a new version of the binary will be installed. If this argument is not pr + ovided, the latest binary is unconditionally downloaded from github. + +GLOBAL OPTIONS: + --bash-framework-config  {single} + use alternate bash framework configuration. + --config {single} + Display configuration + --verbose, -v {single} + info level verbose mode (alias of --display-level INFO) + -vv {single} + debug level verbose mode (alias of --display-level DEBUG) + -vvv {single} + trace level verbose mode (alias of --display-level TRACE) + --env-file  {list} (optional) + Load the specified env file + --no-color {single} + Produce monochrome output. alias of --theme noColor. + --theme  {single} + choose color theme (default, default-force or noColor) - default-force means + colors will be produced even if command is piped + --help, -h {single} + Display this command help + --version {single} + Print version information and quit + --quiet, -q {single} + quiet mode, doesn't display any output + --log-level  {single} + Set log level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + --log-file  {single} + Set log file + --display-level  {single} + set display level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + +OPTIONS EXCEPTIONS: + +--current-version|-c and --version-arg are mutually exclusive, +you cannot use both argument at the same time. + +--exact-version|-e and --minimal-version|-m are mutually exclusive, +you cannot use both argument at the same time. + +GITHUB TEMPLATE URLS EXAMPLES: + +Simple ones(Sometimes @version@ template variable has to be specified twice): +'https://github.com/hadolint/hadolint/releases/download/v@version@/hadolint-Linux-x86_64' +'https://github.com/koalaman/shellcheck/releases/download/v@version@/shellcheck-v@version@.linux.x86_64.tar.xz' +'https://github.com/sharkdp/fd/releases/download/v@version@/fd_@version@_amd64.deb' +'https://github.com/sharkdp/bat/releases/download/v@version@/bat_@version@_amd64.deb' +'https://github.com/kubernetes-sigs/kind/releases/download/v@version@/kind-linux-amd64' +'https://github.com/kubernetes/minikube/releases/download/v@version@/minikube-linux-amd64' +'https://github.com/plantuml/plantuml/releases/download/v@version@/plantuml-@version@.jar' +'https://github.com/Versent/saml2aws/releases/download/v@version@/saml2aws_@version@_linux_amd64.tar.gz' + +If you want to add a condition on architecture(linux, windows, x86, 64/32 bits): +"https://github.com/docker/compose/releases/download/v@version@/docker-compose-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)" +"https://github.com/docker/docker-credential-helpers/releases/download/v@version@/docker-credential-wincred-v@version@.windows-$(dpkg --print-architecture).exe" +"https://github.com/Blacksmoke16/oq/releases/download/v@version@/oq-v@version@-$(uname -s)-$(uname -m)" + +COMMAND EXAMPLES: +Download docker-compose latest version +upgradeGithubRelease /usr/local/bin/docker-compose "https://github.com/docker/compose/releases/download/v@version@/docker-compose-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)" + +Download oq specific version +upgradeGithubRelease /usr/local/bin/oq --exact-version 1.3.4 "https://github.com/Blacksmoke16/oq/releases/download/v@version@/oq-v@version@-$(uname -s)-$(uname -m)" + +Download oq specific version correctly retrieving the oq version and not the jq one +upgradeGithubRelease /usr/local/bin/oq --exact-version 1.3.4 --version-arg '-V | grep oq:' "https://github.com/Blacksmoke16/oq/releases/download/v@version@/oq-v@version@-$(uname -s)-$(uname -m)" + +VERSION: 2.0 + +AUTHOR: +[François Chastanet](https://github.com/fchastanet) + +SOURCE FILE: +https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/Git/upgradeGithubRelease.sh + +LICENSE: +MIT License + +Copyright (c) 2020-now François Chastanet diff --git a/src/_binaries/Git/upgradeGithubRelease.bats b/src/_binaries/Git/upgradeGithubRelease.bats index ede4ac75..2d1caf0e 100755 --- a/src/_binaries/Git/upgradeGithubRelease.bats +++ b/src/_binaries/Git/upgradeGithubRelease.bats @@ -16,9 +16,7 @@ teardown() { } function Git::upgradeGithubRelease::display_help { #@test - run "${binDir}/upgradeGithubRelease" --help 2>&1 - assert_success - assert_line --index 0 --partial "retrieve latest binary release from github and install it" + testCommand "${binDir}/upgradeGithubRelease" upgradeGithubRelease.help.txt } function Git::upgradeGithubRelease::noArg { #@test @@ -45,7 +43,7 @@ function Git::upgradeGithubRelease::filePathInvalid { #@test run "${binDir}/upgradeGithubRelease" François https://github.com/ 2>&1 assert_failure assert_lines_count 1 - assert_line --index 0 --partial "FATAL - File "$(pwd)/François" is not a valid path" + assert_line --index 0 --partial "FATAL - File $(pwd)/François is not a valid path" } function Git::upgradeGithubRelease::filePathNotWritable { #@test @@ -54,10 +52,11 @@ function Git::upgradeGithubRelease::filePathNotWritable { #@test run "${binDir}/upgradeGithubRelease" "${BATS_TEST_TMPDIR}/dir/targetFile" https://github.com/ 2>&1 assert_failure assert_lines_count 1 - assert_line --index 0 --partial "FATAL - File "${BATS_TEST_TMPDIR}/dir/targetFile" is not writable" + assert_line --index 0 --partial "FATAL - File ${BATS_TEST_TMPDIR}/dir/targetFile is not writable" } function Git::upgradeGithubRelease::filePathNotExistsExactVersionShortArg { #@test + # shellcheck disable=SC2016 stub curl \ '-L -o /dev/null --silent --head --fail https://github.com/hadolint/hadolint/releases/download/v1.0.0/hadolint-Linux-x86_64 : exit 0' \ '-L -o * --fail https://github.com/hadolint/hadolint/releases/download/v1.0.0/hadolint-Linux-x86_64 : echo "success" > "$3"' @@ -77,6 +76,7 @@ function Git::upgradeGithubRelease::filePathNotExistsExactVersionShortArg { #@te } function Git::upgradeGithubRelease::filePathNotExistsExactVersionLongArg { #@test + # shellcheck disable=SC2016 stub curl \ '-L -o /dev/null --silent --head --fail https://github.com/hadolint/hadolint/releases/download/v1.0.0/hadolint-Linux-x86_64 : exit 0' \ '-L -o * --fail https://github.com/hadolint/hadolint/releases/download/v1.0.0/hadolint-Linux-x86_64 : echo "success" > "$3"' @@ -96,6 +96,7 @@ function Git::upgradeGithubRelease::filePathNotExistsExactVersionLongArg { #@tes } function Git::upgradeGithubRelease::filePathNotExistsLatestVersionNotFound { #@test + # shellcheck disable=SC2016 stub curl \ '-L -o * --fail --silent https://api.github.com/repos/hadolint/hadolint/releases/latest : echo "{}" > "$3"' @@ -113,6 +114,7 @@ function Git::upgradeGithubRelease::filePathNotExistsLatestVersionNotFound { #@t } function Git::upgradeGithubRelease::filePathNotExistsLatestVersionFound { #@test + # shellcheck disable=SC2016 stub curl \ '-L -o * --fail --silent https://api.github.com/repos/hadolint/hadolint/releases/latest : echo "{\"tag_name\": \"1.0.0\"}" > "$3"' \ '-L -o * --fail https://github.com/hadolint/hadolint/releases/download/v1.0.0/hadolint-Linux-x86_64 : echo "success" > "$3"' @@ -135,6 +137,7 @@ function Git::upgradeGithubRelease::filePathNotExistsLatestVersionFound { #@test function Git::upgradeGithubRelease::filePathExistsMinVersion { #@test cp "${BATS_TEST_DIRNAME}/testsData/upgradeGithubRelease_bin" "${BATS_TEST_TMPDIR}" + # shellcheck disable=SC2016 stub curl \ '-L -o /dev/null --silent --head --fail https://github.com/hadolint/hadolint/releases/download/v1.1.0/hadolint-Linux-x86_64 : exit 0' \ '-L -o * --fail --silent https://api.github.com/repos/hadolint/hadolint/releases/latest : echo "{\"tag_name\": \"1.1.0\"}" > "$3"' \ @@ -161,6 +164,7 @@ function Git::upgradeGithubRelease::filePathExistsMinVersion { #@test function Git::upgradeGithubRelease::filePathExistsCurrentVersionLessThanMinVersion { #@test cp "${BATS_TEST_DIRNAME}/testsData/upgradeGithubRelease_bin" "${BATS_TEST_TMPDIR}" + # shellcheck disable=SC2016 stub curl \ '-L -o /dev/null --silent --head --fail https://github.com/hadolint/hadolint/releases/download/v1.1.0/hadolint-Linux-x86_64 : exit 0' \ '-L -o * --fail --silent https://api.github.com/repos/hadolint/hadolint/releases/latest : echo "{\"tag_name\": \"1.1.0\"}" > "$3"' \ @@ -188,6 +192,7 @@ function Git::upgradeGithubRelease::filePathExistsCurrentVersionLessThanMinVersi function Git::upgradeGithubRelease::filePathExistsCurrentVersionEqualsMinVersion { #@test cp "${BATS_TEST_DIRNAME}/testsData/upgradeGithubRelease_bin" "${BATS_TEST_TMPDIR}" + # shellcheck disable=SC2016 stub curl \ '-L -o /dev/null --silent --head --fail https://github.com/hadolint/hadolint/releases/download/v1.0.0/hadolint-Linux-x86_64 : exit 0' \ '-L -o * --fail --silent https://api.github.com/repos/hadolint/hadolint/releases/latest : echo "{\"tag_name\": \"1.0.0\"}" > "$3"' @@ -211,6 +216,7 @@ function Git::upgradeGithubRelease::filePathExistsCurrentVersionEqualsMinVersion function Git::upgradeGithubRelease::filePathExistsCurrentVersionGreaterThanMinVersion { #@test cp "${BATS_TEST_DIRNAME}/testsData/upgradeGithubRelease_bin" "${BATS_TEST_TMPDIR}" + # shellcheck disable=SC2016 stub curl \ '-L -o /dev/null --silent --head --fail https://github.com/hadolint/hadolint/releases/download/v1.0.0/hadolint-Linux-x86_64 : exit 0' \ '-L -o * --fail --silent https://api.github.com/repos/hadolint/hadolint/releases/latest : echo "{\"tag_name\": \"1.0.0\"}" > "$3"' @@ -232,6 +238,7 @@ function Git::upgradeGithubRelease::filePathExistsCurrentVersionGreaterThanMinVe function Git::upgradeGithubRelease::filePathExistsExactVersionUpgradeNeeded { #@test cp "${BATS_TEST_DIRNAME}/testsData/upgradeGithubRelease_bin" "${BATS_TEST_TMPDIR}" + # shellcheck disable=SC2016 stub curl \ '-L -o /dev/null --silent --head --fail https://github.com/hadolint/hadolint/releases/download/v1.1.0/hadolint-Linux-x86_64 : exit 0' \ '-L -o * --fail https://github.com/hadolint/hadolint/releases/download/v1.1.0/hadolint-Linux-x86_64 : echo "success" > "$3"' diff --git a/src/_binaries/Git/upgradeGithubRelease.options.tpl b/src/_binaries/Git/upgradeGithubRelease.options.tpl index 8406af8c..00474c73 100644 --- a/src/_binaries/Git/upgradeGithubRelease.options.tpl +++ b/src/_binaries/Git/upgradeGithubRelease.options.tpl @@ -41,6 +41,7 @@ ${__HELP_EXAMPLE}upgradeGithubRelease /usr/local/bin/oq --exact-version 1.3.4 ${ Download oq specific version correctly retrieving the oq version and not the jq one ${__HELP_EXAMPLE}upgradeGithubRelease /usr/local/bin/oq --exact-version 1.3.4 --version-arg '-V | grep oq:' ${example3}${__HELP_NORMAL} """ +# TODO find a way to not duplicate this info declare defaultVersionArg="--version" % @@ -150,14 +151,6 @@ options+=( ) Options::generateCommand "${options[@]}" % -declare copyrightBeginYear="2020" -# default values -declare targetFileArg="" -declare githubUrlPatternArg="" -declare optionVersionArg="<% ${defaultVersionArg} %>" -declare optionCurrentVersion="" -declare optionMinimalVersion="" -declare optionExactVersion="" upgradeGithubReleaseCommandCallback() { if [[ -n "${optionExactVersion}" && -n "${optionMinimalVersion}" ]]; then @@ -182,3 +175,5 @@ targetFileArgCallback() { Log::fatal "File ${targetFileArg} is not writable" fi } + +<% ${commandFunctionName} %> parse "${BASH_FRAMEWORK_ARGV[@]}" diff --git a/src/_binaries/Git/upgradeGithubRelease.sh b/src/_binaries/Git/upgradeGithubRelease.sh index a3f5ba74..f7d7ec97 100755 --- a/src/_binaries/Git/upgradeGithubRelease.sh +++ b/src/_binaries/Git/upgradeGithubRelease.sh @@ -2,11 +2,23 @@ # BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/upgradeGithubRelease # VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. # FACADE -# shellcheck disable=SC2154 +# shellcheck disable=SC2034 -.INCLUDE "$(dynamicTemplateDir _binaries/Git/upgradeGithubRelease.options.tpl)" +# default +declare defaultVersionArg="--version" + +# option values +declare targetFileArg="" +declare githubUrlPatternArg="" +declare optionVersionArg="${defaultVersionArg}" +declare optionCurrentVersion="" +declare optionMinimalVersion="" +declare optionExactVersion="" -upgradeGithubReleaseCommand parse "${BASH_FRAMEWORK_ARGV[@]}" +# other values +declare copyrightBeginYear="2020" + +.INCLUDE "$(dynamicTemplateDir _binaries/Git/upgradeGithubRelease.options.tpl)" run() { computeCurrentCommandVersion() { diff --git a/src/_binaries/Utils/testsData/waitForIt.help.txt b/src/_binaries/Utils/testsData/waitForIt.help.txt new file mode 100644 index 00000000..fbe8505e --- /dev/null +++ b/src/_binaries/Utils/testsData/waitForIt.help.txt @@ -0,0 +1,85 @@ +DESCRIPTION: wait for host:port to be available + +USAGE: waitForIt [OPTIONS] [ARGUMENTS] +USAGE: waitForIt --host|-i --port|-p + [--algorithm|--algo ] [--exec-command-on-success-only|--strict|-s] + [--timeout|-t ] [--bash-framework-config ] [--config] + [--verbose|-v] [-vv] [-vvv] [--env-file ] [--no-color] + [--theme ] [--help|-h] [--version] [--quiet|-q] [--log-level ] + [--log-file ] [--display-level ] + +ARGUMENTS: + [commandArgs {list} (optional)] + Execute command with args after the test finishes or exit with status code i + f no command provided. + +OPTIONS: + --host, -i  {single} (mandatory) + Host or IP under test. + --port, -p  {single} (mandatory) + TCP port under test. + --algorithm, --algo  {single} + Algorithm to use Check algorithms list below. (default: automatic selectio + n based on commands availability and timeout option value). + --exec-command-on-success-only, --strict, -s {single} + Only execute sub-command if the test succeeds. + --timeout, -t  {single} + Timeout in seconds, zero for no timeout. + Default value: 15 + +GLOBAL OPTIONS: + --bash-framework-config  {single} + use alternate bash framework configuration. + --config {single} + Display configuration + --verbose, -v {single} + info level verbose mode (alias of --display-level INFO) + -vv {single} + debug level verbose mode (alias of --display-level DEBUG) + -vvv {single} + trace level verbose mode (alias of --display-level TRACE) + --env-file  {list} (optional) + Load the specified env file + --no-color {single} + Produce monochrome output. alias of --theme noColor. + --theme  {single} + choose color theme (default, default-force or noColor) - default-force means + colors will be produced even if command is piped + --help, -h {single} + Display this command help + --version {single} + Print version information and quit + --quiet, -q {single} + quiet mode, doesn't display any output + --log-level  {single} + Set log level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + --log-file  {single} + Set log file + --display-level  {single} + set display level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + +EXIT STATUS CODES: +0: the host/port is available +1: indicates host/port is not available or argument error +2: timeout reached + +AVAILABLE ALGORITHMS: +timeoutV1WithNc: previous version of timeout command with --timeout option, base command nc +timeoutV2WithNc: newer version of timeout command using timeout as argument, base command nc +whileLoopWithNc: timeout command simulated using while loop, base command nc +timeoutV1WithTcp: previous version of timeout command with --timeout option +timeoutV2WithTcp: newer version of timeout command using timeout as argument +whileLoopWithTcp: timeout command simulated using while loop, base command tcp + +VERSION: 2.0 + +AUTHOR: +[François Chastanet](https://github.com/fchastanet) + +SOURCE FILE: +https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/Utils/waitForIt.sh + +LICENSE: +MIT License + +Copyright (c) 2020-now François Chastanet diff --git a/src/_binaries/Utils/testsData/waitForMysql.help.txt b/src/_binaries/Utils/testsData/waitForMysql.help.txt new file mode 100644 index 00000000..4d739c8d --- /dev/null +++ b/src/_binaries/Utils/testsData/waitForMysql.help.txt @@ -0,0 +1,71 @@ +DESCRIPTION: wait for mysql to be ready + +USAGE: waitForMysql [OPTIONS] [ARGUMENTS] +USAGE: waitForMysql [--timeout|-t ] [--bash-framework-config ] + [--config] [--verbose|-v] [-vv] [-vvv] [--env-file ] [--no-color] + [--theme ] [--help|-h] [--version] [--quiet|-q] [--log-level ] + [--log-file ] [--display-level ] + +ARGUMENTS: + mysqlHost {single} (mandatory) + Mysql host name + mysqlPort {single} (mandatory) + Mysql port + mysqlUserArg {single} (mandatory) + Mysql user name + mysqlPasswordArg {single} (mandatory) + Mysql password + +OPTIONS: + --timeout, -t  {single} + Timeout in seconds, zero for no timeout. + Default value: 15 + +GLOBAL OPTIONS: + --bash-framework-config  {single} + use alternate bash framework configuration. + --config {single} + Display configuration + --verbose, -v {single} + info level verbose mode (alias of --display-level INFO) + -vv {single} + debug level verbose mode (alias of --display-level DEBUG) + -vvv {single} + trace level verbose mode (alias of --display-level TRACE) + --env-file  {list} (optional) + Load the specified env file + --no-color {single} + Produce monochrome output. alias of --theme noColor. + --theme  {single} + choose color theme (default, default-force or noColor) - default-force means + colors will be produced even if command is piped + --help, -h {single} + Display this command help + --version {single} + Print version information and quit + --quiet, -q {single} + quiet mode, doesn't display any output + --log-level  {single} + Set log level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + --log-file  {single} + Set log file + --display-level  {single} + set display level (one of OFF, ERROR, WARNING, INFO, DEBUG, TRACE value) + +EXIT STATUS CODES: +0: mysql is available +1: indicates mysql is not available or argument error +2: timeout reached + +VERSION: 2.0 + +AUTHOR: +[François Chastanet](https://github.com/fchastanet) + +SOURCE FILE: +https://github.com/fchastanet/bash-tools/tree/master/src/_binaries/Utils/waitForMysql.sh + +LICENSE: +MIT License + +Copyright (c) 2020-now François Chastanet diff --git a/src/_binaries/Utils/waitForIt.bats b/src/_binaries/Utils/waitForIt.bats index f63bd1d9..34701c6d 100755 --- a/src/_binaries/Utils/waitForIt.bats +++ b/src/_binaries/Utils/waitForIt.bats @@ -18,10 +18,7 @@ teardown() { } function Utils::waitForIt::display_help { #@test - # shellcheck disable=SC2154 - run "${binDir}/waitForIt" --help 2>&1 - assert_success - assert_line --index 0 "DESCRIPTION: wait for host:port to be available" + testCommand "${binDir}/waitForIt" waitForIt.help.txt } function Utils::waitForIt::noArgs { #@test @@ -96,7 +93,7 @@ function Utils::waitForIt::algo::timeoutV1WithNc::NoCommandExecutedIfFailed { #@ ( echo "#!/bin/bash" echo 'exit 1' - ) > "${HOME}/bin/nc" + ) >"${HOME}/bin/nc" chmod +x "${HOME}/bin/nc" stub timeout \ @@ -142,7 +139,7 @@ function Utils::waitForIt::algo::timeoutV2WithNc::NoCommandExecutedIfFailed { #@ ( echo "#!/bin/bash" echo 'exit 1' - ) > "${HOME}/bin/nc" + ) >"${HOME}/bin/nc" chmod +x "${HOME}/bin/nc" stub timeout \ @@ -161,8 +158,9 @@ function Utils::waitForIt::algo::timeoutV2WithNc::NoCommandExecutedIfFailed { #@ function Utils::waitForIt::algo::timeoutV1WithTcp::WithoutCommand { #@test export WAIT_FOR_IT_MOCKED_TCP=mockedTcp + # shellcheck disable=SC2317 function mockedTcp() { - echo "mocked $@" + echo "mocked $*" } export -f mockedTcp stub timeout "-t 1 ${binDir}/waitForIt --host localhost --port 8888 --timeout 1 --algo timeoutV1WithTcp : ${binDir}/waitForIt --host localhost --port 8888 --timeout 1 --algo timeoutV1WithTcp" @@ -171,15 +169,16 @@ function Utils::waitForIt::algo::timeoutV1WithTcp::WithoutCommand { #@test assert_success assert_line --index 0 --partial "INFO - waitForIt - using algorithm timeoutV1WithTcp" assert_line --index 1 --partial "INFO - waitForIt - waiting 1 seconds for localhost:8888" - assert_line --index 2 "mocked /dev/tcp/localhost/8888" + assert_line --index 2 "mocked /dev/tcp/localhost/8888" assert_line --index 3 --partial "INFO - waitForIt - localhost:8888 is available after " assert_lines_count 4 } function Utils::waitForIt::algo::timeoutV1WithTcp::ExecCommand { #@test export WAIT_FOR_IT_MOCKED_TCP=mockedTcp + # shellcheck disable=SC2317 function mockedTcp() { - echo "mocked $@" + echo "mocked $*" } export -f mockedTcp stub timeout "-t 1 ${binDir}/waitForIt --host localhost --port 8888 --timeout 1 --algo timeoutV1WithTcp echo success : \ @@ -189,7 +188,7 @@ function Utils::waitForIt::algo::timeoutV1WithTcp::ExecCommand { #@test assert_success assert_line --index 0 --partial "INFO - waitForIt - using algorithm timeoutV1WithTcp" assert_line --index 1 --partial "INFO - waitForIt - waiting 1 seconds for localhost:8888" - assert_line --index 2 "mocked /dev/tcp/localhost/8888" + assert_line --index 2 "mocked /dev/tcp/localhost/8888" assert_line --index 3 --partial "INFO - waitForIt - localhost:8888 is available after " assert_line --index 4 "success" assert_lines_count 5 @@ -197,6 +196,7 @@ function Utils::waitForIt::algo::timeoutV1WithTcp::ExecCommand { #@test function Utils::waitForIt::algo::timeoutV1WithTcp::NoCommandExecutedIfFailed { #@test export WAIT_FOR_IT_MOCKED_TCP=mockedTcp + # shellcheck disable=SC2317 function mockedTcp() { return 1 } @@ -215,6 +215,7 @@ function Utils::waitForIt::algo::timeoutV1WithTcp::NoCommandExecutedIfFailed { # function Utils::waitForIt::algo::timeoutV2WithTcp::WithoutCommand { #@test export WAIT_FOR_IT_MOCKED_TCP=mockedTcp + # shellcheck disable=SC2317 function mockedTcp() { return 0 } @@ -231,6 +232,7 @@ function Utils::waitForIt::algo::timeoutV2WithTcp::WithoutCommand { #@test function Utils::waitForIt::algo::timeoutV2WithTcp::ExecCommand { #@test export WAIT_FOR_IT_MOCKED_TCP=mockedTcp + # shellcheck disable=SC2317 function mockedTcp() { return 0 } @@ -249,6 +251,7 @@ function Utils::waitForIt::algo::timeoutV2WithTcp::ExecCommand { #@test function Utils::waitForIt::algo::timeoutV2WithTcp::NoCommandExecutedIfFailed { #@test export WAIT_FOR_IT_MOCKED_TCP=mockedTcp + # shellcheck disable=SC2317 function mockedTcp() { return 1 } @@ -269,8 +272,9 @@ function Utils::waitForIt::algo::timeoutV2WithTcp::NoCommandExecutedIfFailed { # function Utils::waitForIt::algo::whileLoopWithTcp::WithoutCommand { #@test export WAIT_FOR_IT_MOCKED_TCP=mockedTcp + # shellcheck disable=SC2317 function mockedTcp() { - echo "mocked $@" + echo "mocked $*" } export -f mockedTcp stub timeout "-t 1 ${binDir}/waitForIt --host localhost --port 8888 --timeout 1 --algo whileLoopWithTcp : ${binDir}/waitForIt --host localhost --port 8888 --timeout 1 --algo whileLoopWithTcp" @@ -279,15 +283,16 @@ function Utils::waitForIt::algo::whileLoopWithTcp::WithoutCommand { #@test assert_success assert_line --index 0 --partial "INFO - waitForIt - using algorithm whileLoopWithTcp" assert_line --index 1 --partial "INFO - waitForIt - waiting 1 seconds for localhost:8888" - assert_line --index 2 "mocked /dev/tcp/localhost/8888" + assert_line --index 2 "mocked /dev/tcp/localhost/8888" assert_line --index 3 --partial "INFO - waitForIt - localhost:8888 is available after " assert_lines_count 4 } function Utils::waitForIt::algo::whileLoopWithTcp::ExecCommand { #@test export WAIT_FOR_IT_MOCKED_TCP=mockedTcp + # shellcheck disable=SC2317 function mockedTcp() { - echo "mocked $@" + echo "mocked $*" } export -f mockedTcp stub timeout "-t 1 ${binDir}/waitForIt --host localhost --port 8888 --timeout 1 --algo whileLoopWithTcp echo success : \ @@ -297,7 +302,7 @@ function Utils::waitForIt::algo::whileLoopWithTcp::ExecCommand { #@test assert_success assert_line --index 0 --partial "INFO - waitForIt - using algorithm whileLoopWithTcp" assert_line --index 1 --partial "INFO - waitForIt - waiting 1 seconds for localhost:8888" - assert_line --index 2 "mocked /dev/tcp/localhost/8888" + assert_line --index 2 "mocked /dev/tcp/localhost/8888" assert_line --index 3 --partial "INFO - waitForIt - localhost:8888 is available after " assert_line --index 4 "success" assert_lines_count 5 @@ -305,6 +310,7 @@ function Utils::waitForIt::algo::whileLoopWithTcp::ExecCommand { #@test function Utils::waitForIt::algo::whileLoopWithTcp::NoCommandExecutedIfFailed { #@test export WAIT_FOR_IT_MOCKED_TCP=mockedTcp + # shellcheck disable=SC2317 function mockedTcp() { return 1 } @@ -323,7 +329,7 @@ function Utils::waitForIt::algo::whileLoopWithNc::WithoutCommand { #@test ( echo "#!/bin/bash" echo 'exit 0' - ) > "${HOME}/bin/nc" + ) >"${HOME}/bin/nc" chmod +x "${HOME}/bin/nc" run "${binDir}/waitForIt" --host localhost --port 8888 --timeout 1 --algo whileLoopWithNc 2>&1 @@ -338,7 +344,7 @@ function Utils::waitForIt::algo::whileLoopWithNc::ExecCommand { #@test ( echo "#!/bin/bash" echo 'exit 0' - ) > "${HOME}/bin/nc" + ) >"${HOME}/bin/nc" chmod +x "${HOME}/bin/nc" run "${binDir}/waitForIt" --host localhost --port 8888 --timeout 1 --algo whileLoopWithNc echo "success" 2>&1 @@ -354,7 +360,7 @@ function Utils::waitForIt::algo::whileLoopWithNc::NoCommandExecutedIfFailed { #@ ( echo "#!/bin/bash" echo 'exit 1' - ) > "${HOME}/bin/nc" + ) >"${HOME}/bin/nc" chmod +x "${HOME}/bin/nc" run "${binDir}/waitForIt" --host localhost --port 8888 --timeout 1 --strict --algo whileLoopWithNc echo "success" 2>&1 diff --git a/src/_binaries/Utils/waitForIt.options.tpl b/src/_binaries/Utils/waitForIt.options.tpl index 58c58bb3..2565a51f 100644 --- a/src/_binaries/Utils/waitForIt.options.tpl +++ b/src/_binaries/Utils/waitForIt.options.tpl @@ -17,23 +17,16 @@ ${__HELP_OPTION_COLOR}timeoutV1WithTcp${__HELP_NORMAL}: previous version of time ${__HELP_OPTION_COLOR}timeoutV2WithTcp${__HELP_NORMAL}: newer version of timeout command using timeout as argument ${__HELP_OPTION_COLOR}whileLoopWithTcp${__HELP_NORMAL}: timeout command simulated using while loop, base command tcp """ -declare -a availableAlgos=( - "timeoutV1WithNc" - "timeoutV2WithNc" - "whileLoopWithNc" - "timeoutV1WithTcp" - "timeoutV2WithTcp" - "whileLoopWithTcp" -) -declare defaultTimeout="15" % .INCLUDE "$(dynamicTemplateDir _binaries/options/options.base.tpl)" +.INCLUDE "$(dynamicTemplateDir _binaries/options/options.timeout.tpl)" % # shellcheck source=/dev/null source <( Options::generateArg \ --help "Execute command with args after the test finishes or exit with status code if no command provided." \ --min 0 \ + --max -1 \ --name "commandArgs" \ --variable-name "commandArgs" \ --function-name commandArgsFunction @@ -85,17 +78,6 @@ source <( --alt "--user-nc" \ --variable-name "optionLegacy" \ --function-name optionLegacyFunction - - Options::generateOption \ - --help-value-name "timeout" \ - --help "Timeout in seconds, zero for no timeout." \ - --default-value "${defaultTimeout}" \ - --alt "--timeout" \ - --alt "-t" \ - --variable-type "String" \ - --variable-name "optionTimeout" \ - --function-name optionTimeoutFunction \ - --callback optionTimeoutCallback ) options+=( --unknown-option-callback unknownOption @@ -106,7 +88,6 @@ options+=( optionPortFunction optionAlgoFunction optionStrictFunction - optionTimeoutFunction ) Options::generateCommand "${options[@]}" % @@ -122,12 +103,6 @@ optionPortCallback() { fi } -optionTimeoutCallback() { - if [[ ! "${optionTimeout}" =~ ^[0-9]+$ ]]; then - Log::fatal "${SCRIPT_NAME} - invalid timeout option - must be greater or equal to 0" - fi -} - optionAlgoCallback() { if ! Array::contains "${optionAlgo}" "${availableAlgos[@]}"; then Log::fatal "${SCRIPT_NAME} - invalid algorithm '${optionAlgo}'" @@ -140,9 +115,4 @@ commandCallback() { fi } -# default values -declare -a commandArgs=() -declare copyrightBeginYear="2020" -declare optionTimeout="<% ${defaultTimeout} %>" -declare optionAlgo="" -declare -a availableAlgos=(<% "${availableAlgos[@]}" %>) +<% ${commandFunctionName} %> parse "${BASH_FRAMEWORK_ARGV[@]}" diff --git a/src/_binaries/Utils/waitForIt.sh b/src/_binaries/Utils/waitForIt.sh index 1cfa428c..d4c0929b 100755 --- a/src/_binaries/Utils/waitForIt.sh +++ b/src/_binaries/Utils/waitForIt.sh @@ -2,16 +2,33 @@ # BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/waitForIt # VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. # FACADE -# shellcheck disable=SC2154 -# shellcheck disable=SC2317 +# shellcheck disable=SC2034 + +# option values +declare -a commandArgs=() +declare optionHostOrIp="" +declare optionPort="" +declare optionStrict="0" +declare optionTimeout="15" +declare optionAlgo="" +declare -a availableAlgos=( + "timeoutV1WithNc" + "timeoutV2WithNc" + "whileLoopWithNc" + "timeoutV1WithTcp" + "timeoutV2WithTcp" + "whileLoopWithTcp" +) +# other values +declare copyrightBeginYear="2020" .INCLUDE "$(dynamicTemplateDir _binaries/Utils/waitForIt.options.tpl)" # Use this script to test if a given TCP host/port are available # https://github.com/vishnubob/wait-for-it -waitForItCommand parse "${BASH_FRAMEWORK_ARGV[@]}" run() { + # shellcheck disable=SC2317 usingTcp() { # couldn't find another way to mock this part if [[ -n "${WAIT_FOR_IT_MOCKED_TCP:-}" ]]; then @@ -21,6 +38,7 @@ run() { fi } + # shellcheck disable=SC2317 usingNc() { nc -z "${optionHostOrIp}" "${optionPort}" -w 1 2>&1 } @@ -38,7 +56,7 @@ run() { Log::displayInfo "${SCRIPT_NAME} - ${optionHostOrIp}:${optionPort} is available after $((SECONDS - start_ts)) seconds" break fi - if (( optionTimeout!=0 && SECONDS - start_ts >= optionTimeout)); then + if ((optionTimeout != 0 && SECONDS - start_ts >= optionTimeout)); then if [[ "${reportTimeout}" = "1" ]]; then Log::displayError "${SCRIPT_NAME} - timeout for ${optionHostOrIp}:${optionPort} occurred after $((SECONDS - start_ts)) seconds" fi @@ -49,6 +67,7 @@ run() { return 0 } + # shellcheck disable=SC2317 timeoutCommand() { local timeoutVersion="$1" local commandToUse="$2" @@ -61,7 +80,7 @@ run() { # compute timeout command local -a timeoutCmd=(timeout) - if [[ "${timeoutVersion}" = "v1" ]]; then + if [[ "${timeoutVersion}" = "v1" ]]; then # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 timeoutCmd+=("-t") fi @@ -85,21 +104,27 @@ run() { # -------------------------------------- # ALGORITHMS + # shellcheck disable=SC2317 timeoutV1WithNc() { timeoutCommand "v1" "usingNc" } + # shellcheck disable=SC2317 timeoutV2WithNc() { timeoutCommand "v2" "usingNc" } + # shellcheck disable=SC2317 whileLoopWithNc() { whileLoop "usingNc" "1" } + # shellcheck disable=SC2317 timeoutV1WithTcp() { timeoutCommand "v1" "usingTcp" } + # shellcheck disable=SC2317 timeoutV2WithTcp() { timeoutCommand "v2" "usingTcp" } + # shellcheck disable=SC2317 whileLoopWithTcp() { whileLoop "usingTcp" "1" } @@ -117,9 +142,9 @@ run() { command="WithNc" fi - if (( optionTimeout > 0 )); then + if ((optionTimeout > 0)); then if Assert::commandExists timeout &>/dev/null; then - if timeout --help 2>&1 | grep -q -E -e '--timeout '; then + if timeout --help 2>&1 | grep -q -E -e '--timeout '; then echo "timeoutV1${command}" else echo "timeoutV2${command}" diff --git a/src/_binaries/Utils/waitForMysql.bats b/src/_binaries/Utils/waitForMysql.bats index 902f9771..58dfff32 100755 --- a/src/_binaries/Utils/waitForMysql.bats +++ b/src/_binaries/Utils/waitForMysql.bats @@ -18,10 +18,7 @@ teardown() { } function Utils::waitForMysql::display_help { #@test - # shellcheck disable=SC2154 - run "${binDir}/waitForMysql" --help 2>&1 - assert_success - assert_line --index 0 "DESCRIPTION: wait for mysql to be ready" + testCommand "${binDir}/waitForMysql" waitForMysql.help.txt } function Utils::waitForMysql::missingHost { #@test diff --git a/src/_binaries/Utils/waitForMysql.options.tpl b/src/_binaries/Utils/waitForMysql.options.tpl index 7ea1ad49..7abd4e9f 100644 --- a/src/_binaries/Utils/waitForMysql.options.tpl +++ b/src/_binaries/Utils/waitForMysql.options.tpl @@ -9,9 +9,9 @@ ${__HELP_OPTION_COLOR}0${__HELP_NORMAL}: mysql is available ${__HELP_OPTION_COLOR}1${__HELP_NORMAL}: indicates mysql is not available or argument error ${__HELP_OPTION_COLOR}2${__HELP_NORMAL}: timeout reached """ -declare defaultTimeout="15" % .INCLUDE "$(dynamicTemplateDir _binaries/options/options.base.tpl)" +.INCLUDE "$(dynamicTemplateDir _binaries/options/options.timeout.tpl)" % # shellcheck source=/dev/null source <( @@ -40,25 +40,12 @@ source <( --name "mysqlPasswordArg" \ --variable-name "mysqlPasswordArg" \ --function-name mysqlPasswordArgFunction - - optionTimeoutCallback() { :; } - Options::generateOption \ - --help-value-name "timeout" \ - --help "Timeout in seconds, zero for no timeout." \ - --default-value "${defaultTimeout}" \ - --alt "--timeout" \ - --alt "-t" \ - --variable-type "String" \ - --variable-name "optionTimeout" \ - --function-name optionTimeoutFunction \ - --callback optionTimeoutCallback ) options+=( mysqlHostArgFunction mysqlPortArgFunction mysqlUserArgFunction mysqlPasswordArgFunction - optionTimeoutFunction ) Options::generateCommand "${options[@]}" % @@ -69,13 +56,4 @@ mysqlPortArgCallback() { fi } -optionTimeoutCallback() { - if [[ ! "${optionTimeout}" =~ ^[0-9]+$ ]]; then - Log::fatal "${SCRIPT_NAME} - invalid timeout option - must be greater or equal to 0" - fi -} - - -# default values -declare copyrightBeginYear="2020" -declare optionTimeout="<% ${defaultTimeout} %>" +<% ${commandFunctionName} %> parse "${BASH_FRAMEWORK_ARGV[@]}" diff --git a/src/_binaries/Utils/waitForMysql.sh b/src/_binaries/Utils/waitForMysql.sh index 6b3860e7..28bd2255 100755 --- a/src/_binaries/Utils/waitForMysql.sh +++ b/src/_binaries/Utils/waitForMysql.sh @@ -2,12 +2,20 @@ # BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/waitForMysql # VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. # FACADE -# shellcheck disable=SC2154 -# shellcheck disable=SC2317 +# shellcheck disable=SC2034 -.INCLUDE "$(dynamicTemplateDir _binaries/Utils/waitForMysql.options.tpl)" +# default values +declare defaultTimeout="15" +# option values +declare optionTimeout="${defaultTimeout}" +declare mysqlHostArg="" +declare mysqlPortArg="" +declare mysqlUserArg="" +declare mysqlPasswordArg="" +# default values +declare copyrightBeginYear="2020" -waitForMysqlCommand parse "${BASH_FRAMEWORK_ARGV[@]}" +.INCLUDE "$(dynamicTemplateDir _binaries/Utils/waitForMysql.options.tpl)" run() { Assert::commandExists "mysql" @@ -20,7 +28,7 @@ run() { -u"${mysqlUserArg}" \ -p"${mysqlPasswordArg}" &>/dev/null); do (printf >&2 ".") - if (( optionTimeout!=0 && SECONDS - start_ts >= optionTimeout)); then + if ((optionTimeout != 0 && SECONDS - start_ts >= optionTimeout)); then (echo >&2 "") Log::displayError "${SCRIPT_NAME} - timeout for ${mysqlHostArg}:${mysqlPortArg} occurred after $((SECONDS - start_ts)) seconds" return 2 diff --git a/src/_binaries/build/doc.options.tpl b/src/_binaries/build/doc.options.tpl index 59191f05..15c16a48 100644 --- a/src/_binaries/build/doc.options.tpl +++ b/src/_binaries/build/doc.options.tpl @@ -10,8 +10,7 @@ declare help="generate markdown documentation" % Options::generateCommand "${options[@]}" % -declare copyrightBeginYear="2020" -declare -a RUN_CONTAINER_ARGV_FILTERED=() + updateOptionSkipDockerBuildCallback() { if [[ "${IN_BASH_DOCKER:-}" != "You're in docker" ]]; then BASH_FRAMEWORK_ARGV_FILTERED+=("$1") diff --git a/src/_binaries/build/doc.sh b/src/_binaries/build/doc.sh index ddf8e3bd..71e5ff28 100755 --- a/src/_binaries/build/doc.sh +++ b/src/_binaries/build/doc.sh @@ -2,13 +2,14 @@ # BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/doc # VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. # FACADE +# shellcheck disable=SC2034 DOC_DIR="${BASH_TOOLS_ROOT_DIR}/pages" +declare copyrightBeginYear="2020" +declare -a RUN_CONTAINER_ARGV_FILTERED=() .INCLUDE "$(dynamicTemplateDir _binaries/build/doc.options.tpl)" -docCommand parse "${BASH_FRAMEWORK_ARGV[@]}" - run() { if [[ "${IN_BASH_DOCKER:-}" != "You're in docker" ]]; then DOCKER_RUN_OPTIONS=$"-e ORIGINAL_DOC_DIR=${DOC_DIR}" \ @@ -44,7 +45,7 @@ run() { "${DOC_DIR}/Commands.md" \ "${FRAMEWORK_BIN_DIR}" \ TOKEN_NOT_FOUND_COUNT \ - '(bash-tpl|plantuml|definitionLint|compile)$' + '(bash-tpl|plantuml|definitionLint|compile|installFacadeExample)$' # inject plantuml diagram source code into command sed -E -i \ diff --git a/src/_binaries/build/install.options.tpl b/src/_binaries/build/install.options.tpl index 824894a2..cc9b5d66 100644 --- a/src/_binaries/build/install.options.tpl +++ b/src/_binaries/build/install.options.tpl @@ -11,5 +11,3 @@ declare help="Install dependent softwares and configuration needed to use bash-t % Options::generateCommand "${options[@]}" % -declare copyrightBeginYear="2020" -declare optionBashFrameworkConfig="${BASH_TOOLS_ROOT_DIR}/.framework-config" diff --git a/src/_binaries/build/install.sh b/src/_binaries/build/install.sh index 9222bc47..6acc4ba9 100755 --- a/src/_binaries/build/install.sh +++ b/src/_binaries/build/install.sh @@ -2,10 +2,12 @@ # BIN_FILE=${FRAMEWORK_ROOT_DIR}/install # VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=. # FACADE +# shellcheck disable=SC2034 -.INCLUDE "$(dynamicTemplateDir _binaries/build/install.options.tpl)" +declare copyrightBeginYear="2020" +declare optionBashFrameworkConfig="${BASH_TOOLS_ROOT_DIR}/.framework-config" -installCommand parse "${BASH_FRAMEWORK_ARGV[@]}" +.INCLUDE "$(dynamicTemplateDir _binaries/build/install.options.tpl)" # @require Linux::requireExecutedAsUser run() { diff --git a/src/_binaries/build/installRequirements.options.tpl b/src/_binaries/build/installRequirements.options.tpl index 47ef4b9c..8697294e 100644 --- a/src/_binaries/build/installRequirements.options.tpl +++ b/src/_binaries/build/installRequirements.options.tpl @@ -27,5 +27,5 @@ ${__HELP_TITLE}INSTALLS REQUIREMENTS:${__HELP_NORMAL} Options::generateCommand "${options[@]}" declare -p externalBinaries % -declare copyrightBeginYear="2020" -declare optionBashFrameworkConfig="${BASH_TOOLS_ROOT_DIR}/.framework-config" + +<% ${commandFunctionName} %> parse "${BASH_FRAMEWORK_ARGV[@]}" diff --git a/src/_binaries/build/installRequirements.sh b/src/_binaries/build/installRequirements.sh index b6bc059b..6ec19cbf 100755 --- a/src/_binaries/build/installRequirements.sh +++ b/src/_binaries/build/installRequirements.sh @@ -2,12 +2,12 @@ # BIN_FILE=${FRAMEWORK_ROOT_DIR}/bin/installRequirements # VAR_RELATIVE_FRAMEWORK_DIR_TO_CURRENT_DIR=.. # FACADE +# shellcheck disable=SC2034,SC2154 -.INCLUDE "$(dynamicTemplateDir _binaries/build/installRequirements.options.tpl)" - -installRequirementsCommand parse "${BASH_FRAMEWORK_ARGV[@]}" +declare copyrightBeginYear="2020" +declare optionBashFrameworkConfig="${BASH_TOOLS_ROOT_DIR}/.framework-config" -.INCLUDE "${ORIGINAL_TEMPLATE_DIR}/_includes/executedAsUser.sh" +.INCLUDE "$(dynamicTemplateDir _binaries/build/installRequirements.options.tpl)" # @require Linux::requireExecutedAsUser run() { @@ -18,7 +18,6 @@ run() { Log::displayInfo "Copying useful binaries from bash-tools-framework" local externalBinary - # shellcheck disable=SC2154 for externalBinary in "${externalBinaries[@]}"; do cp -v "${BASH_TOOLS_ROOT_DIR}/vendor/bash-tools-framework/${externalBinary}" \ "${BASH_TOOLS_ROOT_DIR}/bin" diff --git a/src/_binaries/options/options.dsn.tpl b/src/_binaries/options/options.dsn.tpl index 475e82c0..5bc4e11c 100644 --- a/src/_binaries/options/options.dsn.tpl +++ b/src/_binaries/options/options.dsn.tpl @@ -1,7 +1,7 @@ % # shellcheck source=/dev/null source <( - # shellcheck disable=SC2116 + # shellcheck disable=SC2116 Options::generateOption \ --help "${defaultFromDsnHelp:-target mysql server}" \ --variable-type "String" \ diff --git a/src/_binaries/options/options.mysql.collationName.tpl b/src/_binaries/options/options.mysql.collationName.tpl index affb83b8..d5d47ff5 100644 --- a/src/_binaries/options/options.mysql.collationName.tpl +++ b/src/_binaries/options/options.mysql.collationName.tpl @@ -23,5 +23,5 @@ source <( ) % -declare optionCollationName="" # old COLLATION_NAME +declare optionCollationName="" declare defaultTargetCollationName="<% ${defaultTargetCollationName} %>" diff --git a/src/_binaries/options/options.mysql.target.tpl b/src/_binaries/options/options.mysql.target.tpl index 8689c936..a019c53a 100644 --- a/src/_binaries/options/options.mysql.target.tpl +++ b/src/_binaries/options/options.mysql.target.tpl @@ -38,8 +38,8 @@ options+=( ) % -declare optionTargetDsn="<% ${defaultTargetDsn} %>" # old TARGET_DSN -declare optionCharacterSet="" # old CHARACTER_SET +declare optionTargetDsn="<% ${defaultTargetDsn} %>" +declare optionCharacterSet="" declare defaultTargetCharacterSet="<% ${defaultTargetCharacterSet} %>" diff --git a/src/_binaries/options/options.profile.tpl b/src/_binaries/options/options.profile.tpl index 38e6328d..5e405e06 100644 --- a/src/_binaries/options/options.profile.tpl +++ b/src/_binaries/options/options.profile.tpl @@ -18,7 +18,7 @@ source <( # shellcheck disable=SC2116 Options::generateOption \ - --help-value-name "table1,table2,..." \ + --help-value-name "tablesSeparatedByComma" \ --help "$(echo \ "import only table specified in the list. " \ "If aws mode, ignore profile option" \ @@ -41,11 +41,11 @@ options+=( # default values declare optionProfile="default" declare optionTables="" -declare profileCommand="" +declare profileCommandFile="" profileOptionHelpCallback() { echo "the name of the profile to use in order to include or exclude tables" - echo "(if not specified ${HOME_PROFILES_DIR}/default.sh is used if exists otherwise ${PROFILES_DIR}/default.sh)" + echo "(if not specified in default.sh from 'User profiles directory' if exists or 'Default profiles directory')" } optionTablesCallback() { @@ -55,9 +55,9 @@ optionTablesCallback() { } profileOptionCallback() { - local -a profilesList - readarray -t profilesList < <(Conf::getMergedList "dbImportProfiles" "sh" "" || true) - if ! Array::contains "$2" "${profilesList[@]}"; then + local -a profilesArray + readarray -t profilesArray < <(Conf::getMergedList "dbImportProfiles" "sh" "" || true) + if ! Array::contains "$2" "${profilesArray[@]}"; then Log::displayError "${SCRIPT_NAME} - invalid profile '$2' provided" return 1 fi @@ -71,7 +71,7 @@ initProfileCommandCallback() { local profileMsgInfo # shellcheck disable=SC2154 if [[ "${optionProfile}" = 'default' && -n "${optionTables}" ]]; then - profileCommand=$(Framework::createTempFile "profileCmd.XXXXXXXXXXXX") + profileCommandFile=$(Framework::createTempFile "profileCmd.XXXXXXXXXXXX") profileMsgInfo="only ${optionTables} will be imported" ( echo '#!/usr/bin/env bash' @@ -81,11 +81,11 @@ initProfileCommandCallback() { # tables option not specified, we will import all tables of the profile echo 'cat' fi - ) >"${profileCommand}" + ) >"${profileCommandFile}" else - profileCommand="$(Conf::getAbsoluteFile "dbImportProfiles" "${optionProfile}" "sh")" || exit 1 - profileMsgInfo="Using profile ${profileCommand}" + profileCommandFile="$(Conf::getAbsoluteFile "dbImportProfiles" "${optionProfile}" "sh")" || exit 1 + profileMsgInfo="Using profile ${profileCommandFile}" fi - chmod +x "${profileCommand}" + chmod +x "${profileCommandFile}" Log::displayInfo "${profileMsgInfo}" } diff --git a/src/_binaries/options/options.timeout.tpl b/src/_binaries/options/options.timeout.tpl new file mode 100644 index 00000000..75d0c12f --- /dev/null +++ b/src/_binaries/options/options.timeout.tpl @@ -0,0 +1,26 @@ +% +declare defaultTimeout="15" +# shellcheck source=/dev/null +source <( + optionTimeoutCallback() { :; } + Options::generateOption \ + --help-value-name "timeout" \ + --help "Timeout in seconds, zero for no timeout." \ + --default-value "${defaultTimeout}" \ + --alt "--timeout" \ + --alt "-t" \ + --variable-type "String" \ + --variable-name "optionTimeout" \ + --function-name optionTimeoutFunction \ + --callback optionTimeoutCallback +) +options+=( + optionTimeoutFunction +) +% + +optionTimeoutCallback() { + if [[ ! "${optionTimeout}" =~ ^[0-9]+$ ]]; then + Log::fatal "${SCRIPT_NAME} - invalid timeout option - must be greater or equal to 0" + fi +} diff --git a/src/_includes/dbScriptOneDatabase.sh b/src/_includes/dbScriptOneDatabase.sh index ab299081..c378d6a5 100755 --- a/src/_includes/dbScriptOneDatabase.sh +++ b/src/_includes/dbScriptOneDatabase.sh @@ -5,17 +5,13 @@ # USED BY bin/dbScriptAllDatabases sub scripts # eg: src/DbScriptAllDatabases/extractData.sh ############################################################ - -.INCLUDE "$(dynamicTemplateDir _includes/_header.tpl)" -.INCLUDE "$(dynamicTemplateDir _includes/_load.tpl)" - Assert::expectNonRootUser declare DSN="$1" # shellcheck disable=SC2034 declare LOG_FORMAT="$2" # shellcheck disable=SC2034 -declare VERBOSE="$3" +declare VERBOSE="${3:-0}" # shellcheck disable=SC2034 declare outputDir="$4" # shellcheck disable=SC2034 @@ -26,10 +22,19 @@ declare -i length=$(($# - 6)) declare -a scriptParameters=("${@:6:${length}}") # shellcheck disable=SC2034,SC2124 declare db="${@:$(($#)):1}" - -[[ "${VERBOSE}" = "1" ]] && Log::displayInfo "process db '${db}'" - # shellcheck disable=SC2034 declare -A dbInstance -Database::newInstance dbInstance "${DSN}" -Database::setQueryOptions dbInstance "${dbInstance[QUERY_OPTIONS]} --connect-timeout=5" + +init() { + if ((VERBOSE >= 1)); then + Log::displayInfo "process db '${db}'" + fi + + # shellcheck disable=SC2154 + if [[ -z "${scriptParameters[0]}" ]]; then + Log::fatal "query string or file not provided" + fi + + Database::newInstance dbInstance "${DSN}" + Database::setQueryOptions dbInstance "${dbInstance[QUERY_OPTIONS]} --connect-timeout=5" +} diff --git a/src/batsHeaders.sh b/src/batsHeaders.sh index 3084f0c6..3793b251 100755 --- a/src/batsHeaders.sh +++ b/src/batsHeaders.sh @@ -55,8 +55,38 @@ source "${FRAMEWORK_ROOT_DIR}/src/Log/logWarning.sh" source "${FRAMEWORK_ROOT_DIR}/src/Log/rotate.sh" # shellcheck source=vendor/bash-tools-framework/src/Log/requireLoad.sh source "${FRAMEWORK_ROOT_DIR}/src/Log/requireLoad.sh" +# shellcheck source=vendor/bash-tools-framework/src/UI/theme.sh +source "${FRAMEWORK_ROOT_DIR}/src/UI/theme.sh" +# shellcheck source=vendor/bash-tools-framework/src/Assert/tty.sh +source "${FRAMEWORK_ROOT_DIR}/src/Assert/tty.sh" export BASH_FRAMEWORK_LOG_FILE="${BATS_TEST_TMPDIR}/logFile" export BASH_FRAMEWORK_DISPLAY_LEVEL="${__LEVEL_INFO}" Env::requireLoad Log::requireLoad + +# @description test command help +# @env BATS_FIX_TEST int 1 to fix testsData expected files +# @arg $1 command:String command to test +# @arg $2 expectedOutputFile:String expected output file +testCommand() { + local command="$1" + local expectedOutputFile="$2" + export INTERACTIVE=1 + UI::theme default + run "${command}" --help + assert_success + if [[ "${BATS_FIX_TEST}" = "1" && ! -f "${BATS_TEST_DIRNAME}/testsData/${expectedOutputFile}" ]]; then + mkdir -p "${BATS_TEST_DIRNAME}/testsData" || true + touch "${BATS_TEST_DIRNAME}/testsData/${expectedOutputFile}" + fi + + output=$(echo -e "${output}" | sed -E "s#${HOME}#home#g") + # shellcheck disable=SC2154 + diff <(echo -e "${output}") "${BATS_TEST_DIRNAME}/testsData/${expectedOutputFile}" >&3 || { + if [[ "${BATS_FIX_TEST}" = "1" ]]; then + echo -e "${output}" >"${BATS_TEST_DIRNAME}/testsData/${expectedOutputFile}" + fi + return 1 + } +} diff --git a/waitForIt.log b/waitForIt.log deleted file mode 100644 index 78f995b6..00000000 --- a/waitForIt.log +++ /dev/null @@ -1,980 +0,0 @@ -+ facade_main_d396e2bc1f6e43a7b79e8a25ad41ac25 --host localhost --port 888 --timeout 1 --algo timeoutV2WithTcp -++ cd /home/wsl/fchastanet/bash-tools/bin/.. -++ pwd -P -+ BASH_TOOLS_ROOT_DIR=/home/wsl/fchastanet/bash-tools -+ [[ -d /home/wsl/fchastanet/bash-tools/vendor/bash-tools-framework/ ]] -++ cd /home/wsl/fchastanet/bash-tools/vendor/bash-tools-framework -++ pwd -P -+ FRAMEWORK_ROOT_DIR=/home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework -+ FRAMEWORK_SRC_DIR=/home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/src -+ FRAMEWORK_BIN_DIR=/home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/bin -+ FRAMEWORK_VENDOR_DIR=/home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/vendor -+ FRAMEWORK_VENDOR_BIN_DIR=/home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/vendor/bin -+ [[ -f /home/wsl/.bash-tools/.env ]] -+ BASH_FRAMEWORK_ENV_FILES=("${HOME}/.bash-tools/.env") -+ export BASH_FRAMEWORK_ENV_FILES -+ Env::requireLoad -+ local configFilesStr -++ Env::getOrderedConfFiles -++ configFiles=() -++ local -a configFiles -++ [[ -n 1 ]] -++ configFiles+=("${BASH_FRAMEWORK_ENV_FILES[@]}") -++ local defaultEnvFile -+++ Env::createDefaultEnvFile -+++ local envFile -++++ Framework::createTempFile createDefaultEnvFileEnvFile -++++ mktemp -p /tmp/bash-framework/bash-framework-1781721-rBgQna -t createDefaultEnvFileEnvFile.XXXXXXXXXXXX -+++ envFile=/tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.mjXFmo1Wm5qf -+++ echo BASH_FRAMEWORK_THEME=default -+++ echo BASH_FRAMEWORK_LOG_LEVEL=0 -+++ echo BASH_FRAMEWORK_DISPLAY_LEVEL=2 -+++ echo 'BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-"${FRAMEWORK_ROOT_DIR}/logs/${SCRIPT_NAME}.log"}"' -+++ echo BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+++ echo /tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.mjXFmo1Wm5qf -++ defaultEnvFile=/tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.mjXFmo1Wm5qf -++ configFiles+=("${defaultEnvFile}") -++ local file -++ for file in "${configFiles[@]}" -++ [[ ! -f /home/wsl/.bash-tools/.env ]] -++ [[ ! -r /home/wsl/.bash-tools/.env ]] -++ echo /home/wsl/.bash-tools/.env -++ for file in "${configFiles[@]}" -++ [[ ! -f /tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.mjXFmo1Wm5qf ]] -++ [[ ! -r /tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.mjXFmo1Wm5qf ]] -++ echo /tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.mjXFmo1Wm5qf -+ configFilesStr='/home/wsl/.bash-tools/.env -/tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.mjXFmo1Wm5qf' -+ local -a configFiles -+ readarray -t configFiles -+ (( 2 == 0 )) -+ [[ -z /home/wsl/.bash-tools/.env -/tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.mjXFmo1Wm5qf ]] -+ Env::mergeConfFiles /home/wsl/.bash-tools/.env /tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.mjXFmo1Wm5qf -+ configFileList=("$@") -+ local -a configFileList -+ (( 2 == 0 )) -+ local combinedConfigFile -++ Framework::createTempFile mergeConfFiles -++ mktemp -p /tmp/bash-framework/bash-framework-1781721-rBgQna -t mergeConfFiles.XXXXXXXXXXXX -+ combinedConfigFile=/tmp/bash-framework/bash-framework-1781721-rBgQna/mergeConfFiles.B27smZI9i3Hj -+ sed -E -e 's/\s*$// ; /^$/d ; /^#.*$/d ; s/=([^"'\''].*)$/="\1"/' /home/wsl/.bash-tools/.env /tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.mjXFmo1Wm5qf -+ Filters::commentLines -+ grep -vxE '[[:blank:]]*(#.*)?' -+ awk -F= '!line[$1]++' -+ set -o allexport -+ source /tmp/bash-framework/bash-framework-1781721-rBgQna/mergeConfFiles.B27smZI9i3Hj -++ BASH_FRAMEWORK_LOG_LEVEL=0 -++ BASH_FRAMEWORK_DISPLAY_LEVEL=3 -++ BASH_FRAMEWORK_LOG_FILE=/home/wsl/.bash-tools/logs/bash.log -++ DB_IMPORT_DUMP_DIR=/home/wsl/.bash-tools/dbImportDumps -++ DB_IMPORT_GARBAGE_COLLECT_DAYS=+30 -++ SCRIPTS_FOLDER=/home/wsl/.bash-tools/conf/dbScripts -++ BASH_TOOLS_FOLDER=/home/wsl/projects/bash-tools -++ S3_BASE_URL=s3://ck-dev-frsa-devsql/exports/ -++ TEMP_FOLDER=/tmp -++ BASH_FRAMEWORK_THEME=default -++ BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+ source /tmp/bash-framework/bash-framework-1781721-rBgQna/mergeConfFiles.B27smZI9i3Hj -++ BASH_FRAMEWORK_LOG_LEVEL=0 -++ BASH_FRAMEWORK_DISPLAY_LEVEL=3 -++ BASH_FRAMEWORK_LOG_FILE=/home/wsl/.bash-tools/logs/bash.log -++ DB_IMPORT_DUMP_DIR=/home/wsl/.bash-tools/dbImportDumps -++ DB_IMPORT_GARBAGE_COLLECT_DAYS=+30 -++ SCRIPTS_FOLDER=/home/wsl/.bash-tools/conf/dbScripts -++ BASH_TOOLS_FOLDER=/home/wsl/projects/bash-tools -++ S3_BASE_URL=s3://ck-dev-frsa-devsql/exports/ -++ TEMP_FOLDER=/tmp -++ BASH_FRAMEWORK_THEME=default -++ BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+ set +o allexport -+ UI::requireTheme -+ UI::theme default -+ local theme=default -+ [[ ! default =~ -force$ ]] -+ Assert::tty -+ [[ 0 = \1 ]] -+ [[ 0 = \1 ]] -+ [[ -t 1 ]] -+ [[ -t 2 ]] -+ theme=noColor -+ case "${theme}" in -+ [[ noColor = \d\e\f\a\u\l\t ]] -+ export BASH_FRAMEWORK_THEME=noColor -+ BASH_FRAMEWORK_THEME=noColor -+ export __ERROR_COLOR= -+ __ERROR_COLOR= -+ export __INFO_COLOR= -+ __INFO_COLOR= -+ export __SUCCESS_COLOR= -+ __SUCCESS_COLOR= -+ export __WARNING_COLOR= -+ __WARNING_COLOR= -+ export __SKIPPED_COLOR= -+ __SKIPPED_COLOR= -+ export __DEBUG_COLOR= -+ __DEBUG_COLOR= -+ export __HELP_COLOR= -+ __HELP_COLOR= -+ export __TEST_COLOR= -+ __TEST_COLOR= -+ export __TEST_ERROR_COLOR= -+ __TEST_ERROR_COLOR= -+ export __HELP_TITLE_COLOR= -+ __HELP_TITLE_COLOR= -+ export __HELP_OPTION_COLOR= -+ __HELP_OPTION_COLOR= -+ export __RESET_COLOR= -+ __RESET_COLOR= -+ export __HELP_EXAMPLE= -+ __HELP_EXAMPLE= -+ export __HELP_TITLE= -+ __HELP_TITLE= -+ export __HELP_NORMAL= -+ __HELP_NORMAL= -+ Log::requireLoad -+ [[ -z /home/wsl/.bash-tools/logs/bash.log ]] -+ (( BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF )) -+ (( BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF )) -+ Compiler::Facade::requireCommandBinDir -+ COMMAND_BIN_DIR=/home/wsl/fchastanet/bash-tools/bin -+ Env::pathPrepend /home/wsl/fchastanet/bash-tools/bin -+ local arg -+ for arg in "$@" -+ [[ -d /home/wsl/fchastanet/bash-tools/bin ]] -+ [[ :/home/wsl/.virtualenv/python3.9/bin:/home/wsl/.bin:/home/wsl/.local/bin:/home/wsl/fchastanet/bash-tools/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/lib/wsl/lib:/c/Python311/Scripts/:/c/Python311/:/c/Program Files/Common Files/Oracle/Java/javapath:/c/Program Files (x86)/Common Files/Oracle/Java/javapath:/c/PROGRA~1/AdoptOpenJDK/jdk-13.0.2.8-hotspot/bin:/c/Windows/system32:/c/Windows:/c/Windows/System32/Wbem:/c/Windows/System32/WindowsPowerShell/v1.0/:/c/Windows/System32/OpenSSH/:/c/PROGRA~1/WindowsPowerShell/Scripts:/c/PROGRA~1/IcedTeaWeb/WebStart/bin:/c/PROGRA~1/dotnet/:/c/PROGRA~2/WI3CF2~1/10/WINDOW~1:/c/PROGRA~2/Meld/:/c/WINDOWS/system32:/c/WINDOWS:/c/WINDOWS/System32/Wbem:/c/WINDOWS/System32/WindowsPowerShell/v1.0/:/c/WINDOWS/System32/OpenSSH/:/c/PROGRA~1/Git/cmd:/c/Program Files/dotnet/:/c/ProgramData/chocolatey/bin:/c/Program Files/PowerShell/7/:/c/Users/fchastanet/AppData/Local/Microsoft/WindowsApps:/c/PROGRA~1/JetBrains/PHPSTO~1.2/bin:/c/Users/fchastanet/AppData/Local/Programs/MICROS~1/bin:/c/Users/fchastanet/AppData/Local/JetBrains/Toolbox/scripts:/c/Users/fchastanet/.dotnet/tools:/c/Users/fchastanet/AppData/Local/Microsoft/WindowsApps:/c/Users/fchastanet/AppData/Local/Programs/Microsoft VS Code/bin:/c/WINDOWS/system32:/mnt/c/Windows:/c/Users/fchastanet/AppData/Local/Microsoft/WindowsApps:/mnt/c/WINDOWS/System32/WindowsPowerShell/v1.0/:/c/Users/fchastanet/AppData/Local/Programs/MICROS~1/bin:/usr/local/.composer/vendor/bin:/home/wsl/go/bin:/home/wsl/n/bin:/opt/kubectx:/home/wsl/.fzf/bin: != *\:\/\h\o\m\e\/\w\s\l\/\f\c\h\a\s\t\a\n\e\t\/\b\a\s\h\-\t\o\o\l\s\/\b\i\n\:* ]] -+ Linux::requireExecutedAsUser -++ id -u -+ [[ 1000 = \0 ]] -+ BASH_FRAMEWORK_ARGV_FILTERED=() -+ declare -a BASH_FRAMEWORK_ARGV_FILTERED -+ commandArgs=() -+ declare -a commandArgs -+ declare copyrightBeginYear=2020 -+ declare optionTimeout=15 -+ declare optionAlgo= -+ availableAlgos=(timeoutV1WithNc timeoutV2WithNc whileLoopWithNc timeoutV1WithTcp timeoutV2WithTcp whileLoopWithTcp) -+ declare -a availableAlgos -+ waitForItCommand parse --host localhost --port 888 --timeout 1 --algo timeoutV2WithTcp -+ local options_parse_cmd=parse -+ shift -+ [[ parse = \p\a\r\s\e ]] -+ local -i options_parse_optionParsedCountOptionHostOrIp -+ (( options_parse_optionParsedCountOptionHostOrIp = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionPort -+ (( options_parse_optionParsedCountOptionPort = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionAlgo -+ (( options_parse_optionParsedCountOptionAlgo = 0 )) -+ true -+ optionStrict=0 -+ local -i options_parse_optionParsedCountOptionStrict -+ (( options_parse_optionParsedCountOptionStrict = 0 )) -+ true -+ optionTimeout=15 -+ local -i options_parse_optionParsedCountOptionTimeout -+ (( options_parse_optionParsedCountOptionTimeout = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionBashFrameworkConfig -+ (( options_parse_optionParsedCountOptionBashFrameworkConfig = 0 )) -+ true -+ optionConfig=0 -+ local -i options_parse_optionParsedCountOptionConfig -+ (( options_parse_optionParsedCountOptionConfig = 0 )) -+ true -+ optionInfoVerbose=0 -+ local -i options_parse_optionParsedCountOptionInfoVerbose -+ (( options_parse_optionParsedCountOptionInfoVerbose = 0 )) -+ true -+ optionDebugVerbose=0 -+ local -i options_parse_optionParsedCountOptionDebugVerbose -+ (( options_parse_optionParsedCountOptionDebugVerbose = 0 )) -+ true -+ optionTraceVerbose=0 -+ local -i options_parse_optionParsedCountOptionTraceVerbose -+ (( options_parse_optionParsedCountOptionTraceVerbose = 0 )) -+ true -+ optionNoColor=0 -+ local -i options_parse_optionParsedCountOptionNoColor -+ (( options_parse_optionParsedCountOptionNoColor = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionTheme -+ (( options_parse_optionParsedCountOptionTheme = 0 )) -+ true -+ optionHelp=0 -+ local -i options_parse_optionParsedCountOptionHelp -+ (( options_parse_optionParsedCountOptionHelp = 0 )) -+ true -+ optionVersion=0 -+ local -i options_parse_optionParsedCountOptionVersion -+ (( options_parse_optionParsedCountOptionVersion = 0 )) -+ true -+ optionQuiet=0 -+ local -i options_parse_optionParsedCountOptionQuiet -+ (( options_parse_optionParsedCountOptionQuiet = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionLogLevel -+ (( options_parse_optionParsedCountOptionLogLevel = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionLogFile -+ (( options_parse_optionParsedCountOptionLogFile = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionDisplayLevel -+ (( options_parse_optionParsedCountOptionDisplayLevel = 0 )) -+ true -+ local -i options_parse_argParsedCountCommandArgs -+ (( options_parse_argParsedCountCommandArgs = 0 )) -+ true -+ local -i options_parse_parsedArgIndex=0 -+ (( 8 > 0 )) -+ local options_parse_arg=--host -+ local argOptDefaultBehavior=0 -+ case "${options_parse_arg}" in -+ shift -+ (( 7 == 0 )) -+ (( options_parse_optionParsedCountOptionHostOrIp >= 1 )) -+ (( ++options_parse_optionParsedCountOptionHostOrIp )) -+ optionHostOrIp=localhost -+ shift -+ (( 6 > 0 )) -+ local options_parse_arg=--port -+ local argOptDefaultBehavior=0 -+ case "${options_parse_arg}" in -+ shift -+ (( 5 == 0 )) -+ (( options_parse_optionParsedCountOptionPort >= 1 )) -+ (( ++options_parse_optionParsedCountOptionPort )) -+ optionPort=888 -+ optionPortCallback --port 888 -+ [[ ! 888 =~ ^[0-9]+$ ]] -+ (( optionPort == 0 )) -+ shift -+ (( 4 > 0 )) -+ local options_parse_arg=--timeout -+ local argOptDefaultBehavior=0 -+ case "${options_parse_arg}" in -+ shift -+ (( 3 == 0 )) -+ (( options_parse_optionParsedCountOptionTimeout >= 1 )) -+ (( ++options_parse_optionParsedCountOptionTimeout )) -+ optionTimeout=1 -+ optionTimeoutCallback --timeout 1 -+ [[ ! 1 =~ ^[0-9]+$ ]] -+ shift -+ (( 2 > 0 )) -+ local options_parse_arg=--algo -+ local argOptDefaultBehavior=0 -+ case "${options_parse_arg}" in -+ shift -+ (( 1 == 0 )) -+ (( options_parse_optionParsedCountOptionAlgo >= 1 )) -+ (( ++options_parse_optionParsedCountOptionAlgo )) -+ optionAlgo=timeoutV2WithTcp -+ optionAlgoCallback --algo timeoutV2WithTcp -+ Array::contains timeoutV2WithTcp timeoutV1WithNc timeoutV2WithNc whileLoopWithNc timeoutV1WithTcp timeoutV2WithTcp whileLoopWithTcp -+ local element -+ for element in "${@:2}" -+ [[ timeoutV1WithNc = \t\i\m\e\o\u\t\V\2\W\i\t\h\T\c\p ]] -+ for element in "${@:2}" -+ [[ timeoutV2WithNc = \t\i\m\e\o\u\t\V\2\W\i\t\h\T\c\p ]] -+ for element in "${@:2}" -+ [[ whileLoopWithNc = \t\i\m\e\o\u\t\V\2\W\i\t\h\T\c\p ]] -+ for element in "${@:2}" -+ [[ timeoutV1WithTcp = \t\i\m\e\o\u\t\V\2\W\i\t\h\T\c\p ]] -+ for element in "${@:2}" -+ [[ timeoutV2WithTcp = \t\i\m\e\o\u\t\V\2\W\i\t\h\T\c\p ]] -+ return 0 -+ shift -+ (( 0 > 0 )) -+ (( options_parse_optionParsedCountOptionHostOrIp < 1 )) -+ (( options_parse_optionParsedCountOptionPort < 1 )) -+ commandOptionParseFinished -+ [[ -z 1 ]] -+ BASH_FRAMEWORK_ENV_FILES+=("${optionEnvFiles[@]}") -+ export BASH_FRAMEWORK_ENV_FILES -+ Env::requireLoad -+ local configFilesStr -++ Env::getOrderedConfFiles -++ configFiles=() -++ local -a configFiles -++ [[ -n 1 ]] -++ configFiles+=("${BASH_FRAMEWORK_ENV_FILES[@]}") -++ local defaultEnvFile -+++ Env::createDefaultEnvFile -+++ local envFile -++++ Framework::createTempFile createDefaultEnvFileEnvFile -++++ mktemp -p /tmp/bash-framework/bash-framework-1781721-rBgQna -t createDefaultEnvFileEnvFile.XXXXXXXXXXXX -+++ envFile=/tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.YCaOFUSF4U8X -+++ echo BASH_FRAMEWORK_THEME=noColor -+++ echo BASH_FRAMEWORK_LOG_LEVEL=0 -+++ echo BASH_FRAMEWORK_DISPLAY_LEVEL=3 -+++ echo 'BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-"${FRAMEWORK_ROOT_DIR}/logs/${SCRIPT_NAME}.log"}"' -+++ echo BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+++ echo /tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.YCaOFUSF4U8X -++ defaultEnvFile=/tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.YCaOFUSF4U8X -++ configFiles+=("${defaultEnvFile}") -++ local file -++ for file in "${configFiles[@]}" -++ [[ ! -f /home/wsl/.bash-tools/.env ]] -++ [[ ! -r /home/wsl/.bash-tools/.env ]] -++ echo /home/wsl/.bash-tools/.env -++ for file in "${configFiles[@]}" -++ [[ ! -f /tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.YCaOFUSF4U8X ]] -++ [[ ! -r /tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.YCaOFUSF4U8X ]] -++ echo /tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.YCaOFUSF4U8X -+ configFilesStr='/home/wsl/.bash-tools/.env -/tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.YCaOFUSF4U8X' -+ local -a configFiles -+ readarray -t configFiles -+ (( 2 == 0 )) -+ [[ -z /home/wsl/.bash-tools/.env -/tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.YCaOFUSF4U8X ]] -+ Env::mergeConfFiles /home/wsl/.bash-tools/.env /tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.YCaOFUSF4U8X -+ configFileList=("$@") -+ local -a configFileList -+ (( 2 == 0 )) -+ local combinedConfigFile -++ Framework::createTempFile mergeConfFiles -++ mktemp -p /tmp/bash-framework/bash-framework-1781721-rBgQna -t mergeConfFiles.XXXXXXXXXXXX -+ combinedConfigFile=/tmp/bash-framework/bash-framework-1781721-rBgQna/mergeConfFiles.veDxMH82WHx7 -+ sed -E -e 's/\s*$// ; /^$/d ; /^#.*$/d ; s/=([^"'\''].*)$/="\1"/' /home/wsl/.bash-tools/.env /tmp/bash-framework/bash-framework-1781721-rBgQna/createDefaultEnvFileEnvFile.YCaOFUSF4U8X -+ Filters::commentLines -+ grep -vxE '[[:blank:]]*(#.*)?' -+ awk -F= '!line[$1]++' -+ set -o allexport -+ source /tmp/bash-framework/bash-framework-1781721-rBgQna/mergeConfFiles.veDxMH82WHx7 -++ BASH_FRAMEWORK_LOG_LEVEL=0 -++ BASH_FRAMEWORK_DISPLAY_LEVEL=3 -++ BASH_FRAMEWORK_LOG_FILE=/home/wsl/.bash-tools/logs/bash.log -++ DB_IMPORT_DUMP_DIR=/home/wsl/.bash-tools/dbImportDumps -++ DB_IMPORT_GARBAGE_COLLECT_DAYS=+30 -++ SCRIPTS_FOLDER=/home/wsl/.bash-tools/conf/dbScripts -++ BASH_TOOLS_FOLDER=/home/wsl/projects/bash-tools -++ S3_BASE_URL=s3://ck-dev-frsa-devsql/exports/ -++ TEMP_FOLDER=/tmp -++ BASH_FRAMEWORK_THEME=noColor -++ BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+ source /tmp/bash-framework/bash-framework-1781721-rBgQna/mergeConfFiles.veDxMH82WHx7 -++ BASH_FRAMEWORK_LOG_LEVEL=0 -++ BASH_FRAMEWORK_DISPLAY_LEVEL=3 -++ BASH_FRAMEWORK_LOG_FILE=/home/wsl/.bash-tools/logs/bash.log -++ DB_IMPORT_DUMP_DIR=/home/wsl/.bash-tools/dbImportDumps -++ DB_IMPORT_GARBAGE_COLLECT_DAYS=+30 -++ SCRIPTS_FOLDER=/home/wsl/.bash-tools/conf/dbScripts -++ BASH_TOOLS_FOLDER=/home/wsl/projects/bash-tools -++ S3_BASE_URL=s3://ck-dev-frsa-devsql/exports/ -++ TEMP_FOLDER=/tmp -++ BASH_FRAMEWORK_THEME=noColor -++ BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+ set +o allexport -+ Log::requireLoad -+ [[ -z /home/wsl/.bash-tools/logs/bash.log ]] -+ (( BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF )) -+ (( BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF )) -+ [[ -n '' ]] -+ BASH_FRAMEWORK_CONFIG_FILE= -+ Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework -+ local -n loadConfig_loadedConfigFile=BASH_FRAMEWORK_CONFIG_FILE -+ shift -+ Conf::loadNearestFile .framework-config loadConfig_loadedConfigFile /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework -+ local configFileName=.framework-config -+ local -n loadedFile=loadConfig_loadedConfigFile -+ shift 2 -+ srcDirs=("$@") -+ local -a srcDirs -+ for srcDir in "${srcDirs[@]}" -++ File::upFind /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework .framework-config -++ local fromPath=/home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework -++ shift -++ local fileName=.framework-config -++ shift -++ local untilInclusivePath=/ -++ shift -++ true -++ [[ -f /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework ]] -++ true -++ [[ -f /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/.framework-config ]] -++ echo /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/.framework-config -++ return 0 -+ configFile=/home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/.framework-config -+ [[ -n /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/.framework-config ]] -+ source /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/.framework-config -++ FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|IMPORT::dir::file|Acquire::ForceIPv4)$' -++ NON_FRAMEWORK_FILES_REGEXP='(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)' -++ BATS_FILE_NOT_NEEDED_REGEXP='(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)' -++ FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP='^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$' -++ FRAMEWORK_SRC_DIRS=("${FRAMEWORK_ROOT_DIR}/src") -++ export REPOSITORY_URL=https://github.com/fchastanet/bash-tools-framework -++ REPOSITORY_URL=https://github.com/fchastanet/bash-tools-framework -+ Log::displayDebug 'Config file /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/.framework-config is loaded' -+ (( BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_DEBUG )) -+ Log::logDebug 'Config file /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/.framework-config is loaded' -+ (( BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_DEBUG )) -+ loadedFile=/home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/.framework-config -+ return 0 -+ [[ 0 = \1 ]] -+ commandCallback -+ [[ localhost = '' ]] -+ [[ 888 = '' ]] -+ Log::displayDebug 'Command waitForIt - parse arguments: --host localhost --port 888 --timeout 1 --algo timeoutV2WithTcp' -+ (( BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_DEBUG )) -+ Log::logDebug 'Command waitForIt - parse arguments: --host localhost --port 888 --timeout 1 --algo timeoutV2WithTcp' -+ (( BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_DEBUG )) -+ Log::displayDebug 'Command waitForIt - parse filtered arguments: ' -+ (( BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_DEBUG )) -+ Log::logDebug 'Command waitForIt - parse filtered arguments: ' -+ (( BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_DEBUG )) -+ [[ 0 = \1 ]] -+ run -+ local result=0 -+ [[ -n '' ]] -+ local algo=timeoutV2WithTcp -+ [[ -z timeoutV2WithTcp ]] -+ Log::displayInfo 'waitForIt - using algorithm timeoutV2WithTcp' -+ local type=INFO -+ (( BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO )) -+ echo -e 'INFO - waitForIt - using algorithm timeoutV2WithTcp' -INFO - waitForIt - using algorithm timeoutV2WithTcp -+ Log::logInfo 'waitForIt - using algorithm timeoutV2WithTcp' INFO -+ (( BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO )) -+ (( optionTimeout > 0 )) -+ Log::displayInfo 'waitForIt - waiting 1 seconds for localhost:888' -+ local type=INFO -+ (( BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO )) -+ echo -e 'INFO - waitForIt - waiting 1 seconds for localhost:888' -INFO - waitForIt - waiting 1 seconds for localhost:888 -+ Log::logInfo 'waitForIt - waiting 1 seconds for localhost:888' INFO -+ (( BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO )) -+ timeoutV2WithTcp -+ timeoutCommand v2 usingTcp -+ local timeoutVersion=v2 -+ local commandToUse=usingTcp -+ local result -+ local -i start_ts=0 -+ Array::contains usingTcp usingTcp usingNc -+ local element -+ for element in "${@:2}" -+ [[ usingTcp = \u\s\i\n\g\T\c\p ]] -+ return 0 -+ timeoutCmd=(timeout) -+ local -a timeoutCmd -+ [[ v2 = \v\1 ]] -+ timeoutCmd+=("${optionTimeout}" "$0" "${ORIGINAL_BASH_FRAMEWORK_ARGV[@]}") -+ local pid=1781756 -+ WAIT_FOR_IT_TIMEOUT_CHILD_ALGO=usingTcp -+ trap 'kill -INT -1781756' INT -+ timeout 1 bin/waitForIt --host localhost --port 888 --timeout 1 --algo timeoutV2WithTcp -+ wait 1781756 -+ facade_main_d396e2bc1f6e43a7b79e8a25ad41ac25 --host localhost --port 888 --timeout 1 --algo timeoutV2WithTcp -++ cd /home/wsl/fchastanet/bash-tools/bin/.. -++ pwd -P -+ BASH_TOOLS_ROOT_DIR=/home/wsl/fchastanet/bash-tools -+ [[ -d /home/wsl/fchastanet/bash-tools/vendor/bash-tools-framework/ ]] -++ cd /home/wsl/fchastanet/bash-tools/vendor/bash-tools-framework -++ pwd -P -+ FRAMEWORK_ROOT_DIR=/home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework -+ FRAMEWORK_SRC_DIR=/home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/src -+ FRAMEWORK_BIN_DIR=/home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/bin -+ FRAMEWORK_VENDOR_DIR=/home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/vendor -+ FRAMEWORK_VENDOR_BIN_DIR=/home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/vendor/bin -+ [[ -f /home/wsl/.bash-tools/.env ]] -+ BASH_FRAMEWORK_ENV_FILES=("${HOME}/.bash-tools/.env") -+ export BASH_FRAMEWORK_ENV_FILES -+ Env::requireLoad -+ local configFilesStr -++ Env::getOrderedConfFiles -++ configFiles=() -++ local -a configFiles -++ [[ -n 1 ]] -++ configFiles+=("${BASH_FRAMEWORK_ENV_FILES[@]}") -++ local defaultEnvFile -+++ Env::createDefaultEnvFile -+++ local envFile -++++ Framework::createTempFile createDefaultEnvFileEnvFile -++++ mktemp -p /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE -t createDefaultEnvFileEnvFile.XXXXXXXXXXXX -+++ envFile=/tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.6GYY1uLmH8qq -+++ echo BASH_FRAMEWORK_THEME=noColor -+++ echo BASH_FRAMEWORK_LOG_LEVEL=0 -+++ echo BASH_FRAMEWORK_DISPLAY_LEVEL=3 -+++ echo 'BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-"${FRAMEWORK_ROOT_DIR}/logs/${SCRIPT_NAME}.log"}"' -+++ echo BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+++ echo /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.6GYY1uLmH8qq -++ defaultEnvFile=/tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.6GYY1uLmH8qq -++ configFiles+=("${defaultEnvFile}") -++ local file -++ for file in "${configFiles[@]}" -++ [[ ! -f /home/wsl/.bash-tools/.env ]] -++ [[ ! -r /home/wsl/.bash-tools/.env ]] -++ echo /home/wsl/.bash-tools/.env -++ for file in "${configFiles[@]}" -++ [[ ! -f /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.6GYY1uLmH8qq ]] -++ [[ ! -r /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.6GYY1uLmH8qq ]] -++ echo /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.6GYY1uLmH8qq -+ configFilesStr='/home/wsl/.bash-tools/.env -/tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.6GYY1uLmH8qq' -+ local -a configFiles -+ readarray -t configFiles -+ (( 2 == 0 )) -+ [[ -z /home/wsl/.bash-tools/.env -/tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.6GYY1uLmH8qq ]] -+ Env::mergeConfFiles /home/wsl/.bash-tools/.env /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.6GYY1uLmH8qq -+ configFileList=("$@") -+ local -a configFileList -+ (( 2 == 0 )) -+ local combinedConfigFile -++ Framework::createTempFile mergeConfFiles -++ mktemp -p /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE -t mergeConfFiles.XXXXXXXXXXXX -+ combinedConfigFile=/tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/mergeConfFiles.qmGWA7wQgTpH -+ sed -E -e 's/\s*$// ; /^$/d ; /^#.*$/d ; s/=([^"'\''].*)$/="\1"/' /home/wsl/.bash-tools/.env /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.6GYY1uLmH8qq -+ Filters::commentLines -+ grep -vxE '[[:blank:]]*(#.*)?' -+ awk -F= '!line[$1]++' -+ set -o allexport -+ source /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/mergeConfFiles.qmGWA7wQgTpH -++ BASH_FRAMEWORK_LOG_LEVEL=0 -++ BASH_FRAMEWORK_DISPLAY_LEVEL=3 -++ BASH_FRAMEWORK_LOG_FILE=/home/wsl/.bash-tools/logs/bash.log -++ DB_IMPORT_DUMP_DIR=/home/wsl/.bash-tools/dbImportDumps -++ DB_IMPORT_GARBAGE_COLLECT_DAYS=+30 -++ SCRIPTS_FOLDER=/home/wsl/.bash-tools/conf/dbScripts -++ BASH_TOOLS_FOLDER=/home/wsl/projects/bash-tools -++ S3_BASE_URL=s3://ck-dev-frsa-devsql/exports/ -++ TEMP_FOLDER=/tmp -++ BASH_FRAMEWORK_THEME=noColor -++ BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+ source /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/mergeConfFiles.qmGWA7wQgTpH -++ BASH_FRAMEWORK_LOG_LEVEL=0 -++ BASH_FRAMEWORK_DISPLAY_LEVEL=3 -++ BASH_FRAMEWORK_LOG_FILE=/home/wsl/.bash-tools/logs/bash.log -++ DB_IMPORT_DUMP_DIR=/home/wsl/.bash-tools/dbImportDumps -++ DB_IMPORT_GARBAGE_COLLECT_DAYS=+30 -++ SCRIPTS_FOLDER=/home/wsl/.bash-tools/conf/dbScripts -++ BASH_TOOLS_FOLDER=/home/wsl/projects/bash-tools -++ S3_BASE_URL=s3://ck-dev-frsa-devsql/exports/ -++ TEMP_FOLDER=/tmp -++ BASH_FRAMEWORK_THEME=noColor -++ BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+ set +o allexport -+ UI::requireTheme -+ UI::theme noColor -+ local theme=noColor -+ [[ ! noColor =~ -force$ ]] -+ Assert::tty -+ [[ 0 = \1 ]] -+ [[ 0 = \1 ]] -+ [[ -t 1 ]] -+ [[ -t 2 ]] -+ theme=noColor -+ case "${theme}" in -+ [[ noColor = \d\e\f\a\u\l\t ]] -+ export BASH_FRAMEWORK_THEME=noColor -+ BASH_FRAMEWORK_THEME=noColor -+ export __ERROR_COLOR= -+ __ERROR_COLOR= -+ export __INFO_COLOR= -+ __INFO_COLOR= -+ export __SUCCESS_COLOR= -+ __SUCCESS_COLOR= -+ export __WARNING_COLOR= -+ __WARNING_COLOR= -+ export __SKIPPED_COLOR= -+ __SKIPPED_COLOR= -+ export __DEBUG_COLOR= -+ __DEBUG_COLOR= -+ export __HELP_COLOR= -+ __HELP_COLOR= -+ export __TEST_COLOR= -+ __TEST_COLOR= -+ export __TEST_ERROR_COLOR= -+ __TEST_ERROR_COLOR= -+ export __HELP_TITLE_COLOR= -+ __HELP_TITLE_COLOR= -+ export __HELP_OPTION_COLOR= -+ __HELP_OPTION_COLOR= -+ export __RESET_COLOR= -+ __RESET_COLOR= -+ export __HELP_EXAMPLE= -+ __HELP_EXAMPLE= -+ export __HELP_TITLE= -+ __HELP_TITLE= -+ export __HELP_NORMAL= -+ __HELP_NORMAL= -+ Log::requireLoad -+ [[ -z /home/wsl/.bash-tools/logs/bash.log ]] -+ (( BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF )) -+ (( BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF )) -+ Compiler::Facade::requireCommandBinDir -+ COMMAND_BIN_DIR=/home/wsl/fchastanet/bash-tools/bin -+ Env::pathPrepend /home/wsl/fchastanet/bash-tools/bin -+ local arg -+ for arg in "$@" -+ [[ -d /home/wsl/fchastanet/bash-tools/bin ]] -+ [[ :/home/wsl/.virtualenv/python3.9/bin:/home/wsl/.bin:/home/wsl/.local/bin:/home/wsl/fchastanet/bash-tools/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/lib/wsl/lib:/c/Python311/Scripts/:/c/Python311/:/c/Program Files/Common Files/Oracle/Java/javapath:/c/Program Files (x86)/Common Files/Oracle/Java/javapath:/c/PROGRA~1/AdoptOpenJDK/jdk-13.0.2.8-hotspot/bin:/c/Windows/system32:/c/Windows:/c/Windows/System32/Wbem:/c/Windows/System32/WindowsPowerShell/v1.0/:/c/Windows/System32/OpenSSH/:/c/PROGRA~1/WindowsPowerShell/Scripts:/c/PROGRA~1/IcedTeaWeb/WebStart/bin:/c/PROGRA~1/dotnet/:/c/PROGRA~2/WI3CF2~1/10/WINDOW~1:/c/PROGRA~2/Meld/:/c/WINDOWS/system32:/c/WINDOWS:/c/WINDOWS/System32/Wbem:/c/WINDOWS/System32/WindowsPowerShell/v1.0/:/c/WINDOWS/System32/OpenSSH/:/c/PROGRA~1/Git/cmd:/c/Program Files/dotnet/:/c/ProgramData/chocolatey/bin:/c/Program Files/PowerShell/7/:/c/Users/fchastanet/AppData/Local/Microsoft/WindowsApps:/c/PROGRA~1/JetBrains/PHPSTO~1.2/bin:/c/Users/fchastanet/AppData/Local/Programs/MICROS~1/bin:/c/Users/fchastanet/AppData/Local/JetBrains/Toolbox/scripts:/c/Users/fchastanet/.dotnet/tools:/c/Users/fchastanet/AppData/Local/Microsoft/WindowsApps:/c/Users/fchastanet/AppData/Local/Programs/Microsoft VS Code/bin:/c/WINDOWS/system32:/mnt/c/Windows:/c/Users/fchastanet/AppData/Local/Microsoft/WindowsApps:/mnt/c/WINDOWS/System32/WindowsPowerShell/v1.0/:/c/Users/fchastanet/AppData/Local/Programs/MICROS~1/bin:/usr/local/.composer/vendor/bin:/home/wsl/go/bin:/home/wsl/n/bin:/opt/kubectx:/home/wsl/.fzf/bin: != *\:\/\h\o\m\e\/\w\s\l\/\f\c\h\a\s\t\a\n\e\t\/\b\a\s\h\-\t\o\o\l\s\/\b\i\n\:* ]] -+ Linux::requireExecutedAsUser -++ id -u -+ [[ 1000 = \0 ]] -+ BASH_FRAMEWORK_ARGV_FILTERED=() -+ declare -a BASH_FRAMEWORK_ARGV_FILTERED -+ commandArgs=() -+ declare -a commandArgs -+ declare copyrightBeginYear=2020 -+ declare optionTimeout=15 -+ declare optionAlgo= -+ availableAlgos=(timeoutV1WithNc timeoutV2WithNc whileLoopWithNc timeoutV1WithTcp timeoutV2WithTcp whileLoopWithTcp) -+ declare -a availableAlgos -+ waitForItCommand parse --host localhost --port 888 --timeout 1 --algo timeoutV2WithTcp -+ local options_parse_cmd=parse -+ shift -+ [[ parse = \p\a\r\s\e ]] -+ local -i options_parse_optionParsedCountOptionHostOrIp -+ (( options_parse_optionParsedCountOptionHostOrIp = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionPort -+ (( options_parse_optionParsedCountOptionPort = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionAlgo -+ (( options_parse_optionParsedCountOptionAlgo = 0 )) -+ true -+ optionStrict=0 -+ local -i options_parse_optionParsedCountOptionStrict -+ (( options_parse_optionParsedCountOptionStrict = 0 )) -+ true -+ optionTimeout=15 -+ local -i options_parse_optionParsedCountOptionTimeout -+ (( options_parse_optionParsedCountOptionTimeout = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionBashFrameworkConfig -+ (( options_parse_optionParsedCountOptionBashFrameworkConfig = 0 )) -+ true -+ optionConfig=0 -+ local -i options_parse_optionParsedCountOptionConfig -+ (( options_parse_optionParsedCountOptionConfig = 0 )) -+ true -+ optionInfoVerbose=0 -+ local -i options_parse_optionParsedCountOptionInfoVerbose -+ (( options_parse_optionParsedCountOptionInfoVerbose = 0 )) -+ true -+ optionDebugVerbose=0 -+ local -i options_parse_optionParsedCountOptionDebugVerbose -+ (( options_parse_optionParsedCountOptionDebugVerbose = 0 )) -+ true -+ optionTraceVerbose=0 -+ local -i options_parse_optionParsedCountOptionTraceVerbose -+ (( options_parse_optionParsedCountOptionTraceVerbose = 0 )) -+ true -+ optionNoColor=0 -+ local -i options_parse_optionParsedCountOptionNoColor -+ (( options_parse_optionParsedCountOptionNoColor = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionTheme -+ (( options_parse_optionParsedCountOptionTheme = 0 )) -+ true -+ optionHelp=0 -+ local -i options_parse_optionParsedCountOptionHelp -+ (( options_parse_optionParsedCountOptionHelp = 0 )) -+ true -+ optionVersion=0 -+ local -i options_parse_optionParsedCountOptionVersion -+ (( options_parse_optionParsedCountOptionVersion = 0 )) -+ true -+ optionQuiet=0 -+ local -i options_parse_optionParsedCountOptionQuiet -+ (( options_parse_optionParsedCountOptionQuiet = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionLogLevel -+ (( options_parse_optionParsedCountOptionLogLevel = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionLogFile -+ (( options_parse_optionParsedCountOptionLogFile = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionDisplayLevel -+ (( options_parse_optionParsedCountOptionDisplayLevel = 0 )) -+ true -+ local -i options_parse_argParsedCountCommandArgs -+ (( options_parse_argParsedCountCommandArgs = 0 )) -+ true -+ local -i options_parse_parsedArgIndex=0 -+ (( 8 > 0 )) -+ local options_parse_arg=--host -+ local argOptDefaultBehavior=0 -+ case "${options_parse_arg}" in -+ shift -+ (( 7 == 0 )) -+ (( options_parse_optionParsedCountOptionHostOrIp >= 1 )) -+ (( ++options_parse_optionParsedCountOptionHostOrIp )) -+ optionHostOrIp=localhost -+ shift -+ (( 6 > 0 )) -+ local options_parse_arg=--port -+ local argOptDefaultBehavior=0 -+ case "${options_parse_arg}" in -+ shift -+ (( 5 == 0 )) -+ (( options_parse_optionParsedCountOptionPort >= 1 )) -+ (( ++options_parse_optionParsedCountOptionPort )) -+ optionPort=888 -+ optionPortCallback --port 888 -+ [[ ! 888 =~ ^[0-9]+$ ]] -+ (( optionPort == 0 )) -+ shift -+ (( 4 > 0 )) -+ local options_parse_arg=--timeout -+ local argOptDefaultBehavior=0 -+ case "${options_parse_arg}" in -+ shift -+ (( 3 == 0 )) -+ (( options_parse_optionParsedCountOptionTimeout >= 1 )) -+ (( ++options_parse_optionParsedCountOptionTimeout )) -+ optionTimeout=1 -+ optionTimeoutCallback --timeout 1 -+ [[ ! 1 =~ ^[0-9]+$ ]] -+ shift -+ (( 2 > 0 )) -+ local options_parse_arg=--algo -+ local argOptDefaultBehavior=0 -+ case "${options_parse_arg}" in -+ shift -+ (( 1 == 0 )) -+ (( options_parse_optionParsedCountOptionAlgo >= 1 )) -+ (( ++options_parse_optionParsedCountOptionAlgo )) -+ optionAlgo=timeoutV2WithTcp -+ optionAlgoCallback --algo timeoutV2WithTcp -+ Array::contains timeoutV2WithTcp timeoutV1WithNc timeoutV2WithNc whileLoopWithNc timeoutV1WithTcp timeoutV2WithTcp whileLoopWithTcp -+ local element -+ for element in "${@:2}" -+ [[ timeoutV1WithNc = \t\i\m\e\o\u\t\V\2\W\i\t\h\T\c\p ]] -+ for element in "${@:2}" -+ [[ timeoutV2WithNc = \t\i\m\e\o\u\t\V\2\W\i\t\h\T\c\p ]] -+ for element in "${@:2}" -+ [[ whileLoopWithNc = \t\i\m\e\o\u\t\V\2\W\i\t\h\T\c\p ]] -+ for element in "${@:2}" -+ [[ timeoutV1WithTcp = \t\i\m\e\o\u\t\V\2\W\i\t\h\T\c\p ]] -+ for element in "${@:2}" -+ [[ timeoutV2WithTcp = \t\i\m\e\o\u\t\V\2\W\i\t\h\T\c\p ]] -+ return 0 -+ shift -+ (( 0 > 0 )) -+ (( options_parse_optionParsedCountOptionHostOrIp < 1 )) -+ (( options_parse_optionParsedCountOptionPort < 1 )) -+ commandOptionParseFinished -+ [[ -z 1 ]] -+ BASH_FRAMEWORK_ENV_FILES+=("${optionEnvFiles[@]}") -+ export BASH_FRAMEWORK_ENV_FILES -+ Env::requireLoad -+ local configFilesStr -++ Env::getOrderedConfFiles -++ configFiles=() -++ local -a configFiles -++ [[ -n 1 ]] -++ configFiles+=("${BASH_FRAMEWORK_ENV_FILES[@]}") -++ local defaultEnvFile -+++ Env::createDefaultEnvFile -+++ local envFile -++++ Framework::createTempFile createDefaultEnvFileEnvFile -++++ mktemp -p /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE -t createDefaultEnvFileEnvFile.XXXXXXXXXXXX -+++ envFile=/tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.g0TBpne4LK88 -+++ echo BASH_FRAMEWORK_THEME=noColor -+++ echo BASH_FRAMEWORK_LOG_LEVEL=0 -+++ echo BASH_FRAMEWORK_DISPLAY_LEVEL=3 -+++ echo 'BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-"${FRAMEWORK_ROOT_DIR}/logs/${SCRIPT_NAME}.log"}"' -+++ echo BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+++ echo /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.g0TBpne4LK88 -++ defaultEnvFile=/tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.g0TBpne4LK88 -++ configFiles+=("${defaultEnvFile}") -++ local file -++ for file in "${configFiles[@]}" -++ [[ ! -f /home/wsl/.bash-tools/.env ]] -++ [[ ! -r /home/wsl/.bash-tools/.env ]] -++ echo /home/wsl/.bash-tools/.env -++ for file in "${configFiles[@]}" -++ [[ ! -f /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.g0TBpne4LK88 ]] -++ [[ ! -r /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.g0TBpne4LK88 ]] -++ echo /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.g0TBpne4LK88 -+ configFilesStr='/home/wsl/.bash-tools/.env -/tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.g0TBpne4LK88' -+ local -a configFiles -+ readarray -t configFiles -+ (( 2 == 0 )) -+ [[ -z /home/wsl/.bash-tools/.env -/tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.g0TBpne4LK88 ]] -+ Env::mergeConfFiles /home/wsl/.bash-tools/.env /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.g0TBpne4LK88 -+ configFileList=("$@") -+ local -a configFileList -+ (( 2 == 0 )) -+ local combinedConfigFile -++ Framework::createTempFile mergeConfFiles -++ mktemp -p /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE -t mergeConfFiles.XXXXXXXXXXXX -+ combinedConfigFile=/tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/mergeConfFiles.VYUdfc7MtOyE -+ sed -E -e 's/\s*$// ; /^$/d ; /^#.*$/d ; s/=([^"'\''].*)$/="\1"/' /home/wsl/.bash-tools/.env /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/createDefaultEnvFileEnvFile.g0TBpne4LK88 -+ Filters::commentLines -+ grep -vxE '[[:blank:]]*(#.*)?' -+ awk -F= '!line[$1]++' -+ set -o allexport -+ source /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/mergeConfFiles.VYUdfc7MtOyE -++ BASH_FRAMEWORK_LOG_LEVEL=0 -++ BASH_FRAMEWORK_DISPLAY_LEVEL=3 -++ BASH_FRAMEWORK_LOG_FILE=/home/wsl/.bash-tools/logs/bash.log -++ DB_IMPORT_DUMP_DIR=/home/wsl/.bash-tools/dbImportDumps -++ DB_IMPORT_GARBAGE_COLLECT_DAYS=+30 -++ SCRIPTS_FOLDER=/home/wsl/.bash-tools/conf/dbScripts -++ BASH_TOOLS_FOLDER=/home/wsl/projects/bash-tools -++ S3_BASE_URL=s3://ck-dev-frsa-devsql/exports/ -++ TEMP_FOLDER=/tmp -++ BASH_FRAMEWORK_THEME=noColor -++ BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+ source /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE/mergeConfFiles.VYUdfc7MtOyE -++ BASH_FRAMEWORK_LOG_LEVEL=0 -++ BASH_FRAMEWORK_DISPLAY_LEVEL=3 -++ BASH_FRAMEWORK_LOG_FILE=/home/wsl/.bash-tools/logs/bash.log -++ DB_IMPORT_DUMP_DIR=/home/wsl/.bash-tools/dbImportDumps -++ DB_IMPORT_GARBAGE_COLLECT_DAYS=+30 -++ SCRIPTS_FOLDER=/home/wsl/.bash-tools/conf/dbScripts -++ BASH_TOOLS_FOLDER=/home/wsl/projects/bash-tools -++ S3_BASE_URL=s3://ck-dev-frsa-devsql/exports/ -++ TEMP_FOLDER=/tmp -++ BASH_FRAMEWORK_THEME=noColor -++ BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+ set +o allexport -+ Log::requireLoad -+ [[ -z /home/wsl/.bash-tools/logs/bash.log ]] -+ (( BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF )) -+ (( BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF )) -+ [[ -n '' ]] -+ BASH_FRAMEWORK_CONFIG_FILE= -+ Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework -+ local -n loadConfig_loadedConfigFile=BASH_FRAMEWORK_CONFIG_FILE -+ shift -+ Conf::loadNearestFile .framework-config loadConfig_loadedConfigFile /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework -+ local configFileName=.framework-config -+ local -n loadedFile=loadConfig_loadedConfigFile -+ shift 2 -+ srcDirs=("$@") -+ local -a srcDirs -+ for srcDir in "${srcDirs[@]}" -++ File::upFind /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework .framework-config -++ local fromPath=/home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework -++ shift -++ local fileName=.framework-config -++ shift -++ local untilInclusivePath=/ -++ shift -++ true -++ [[ -f /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework ]] -++ true -++ [[ -f /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/.framework-config ]] -++ echo /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/.framework-config -++ return 0 -+ configFile=/home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/.framework-config -+ [[ -n /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/.framework-config ]] -+ source /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/.framework-config -++ FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|IMPORT::dir::file|Acquire::ForceIPv4)$' -++ NON_FRAMEWORK_FILES_REGEXP='(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/_binaries|^src/_includes|^src/batsHeaders.sh$|^src/_standalone)' -++ BATS_FILE_NOT_NEEDED_REGEXP='(^bin/|.framework-config|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)' -++ FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP='^bin/|^\.framework-config$|^build.sh$|\.tpl$|/testsData/|^manualTests/|\.bats$' -++ FRAMEWORK_SRC_DIRS=("${FRAMEWORK_ROOT_DIR}/src") -++ export REPOSITORY_URL=https://github.com/fchastanet/bash-tools-framework -++ REPOSITORY_URL=https://github.com/fchastanet/bash-tools-framework -+ Log::displayDebug 'Config file /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/.framework-config is loaded' -+ (( BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_DEBUG )) -+ Log::logDebug 'Config file /home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/.framework-config is loaded' -+ (( BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_DEBUG )) -+ loadedFile=/home/wsl/fchastanet/bash-dev-env/vendor/bash-tools-framework/.framework-config -+ return 0 -+ [[ 0 = \1 ]] -+ commandCallback -+ [[ localhost = '' ]] -+ [[ 888 = '' ]] -+ Log::displayDebug 'Command waitForIt - parse arguments: --host localhost --port 888 --timeout 1 --algo timeoutV2WithTcp' -+ (( BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_DEBUG )) -+ Log::logDebug 'Command waitForIt - parse arguments: --host localhost --port 888 --timeout 1 --algo timeoutV2WithTcp' -+ (( BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_DEBUG )) -+ Log::displayDebug 'Command waitForIt - parse filtered arguments: ' -+ (( BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_DEBUG )) -+ Log::logDebug 'Command waitForIt - parse filtered arguments: ' -+ (( BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_DEBUG )) -+ [[ 0 = \1 ]] -+ run -+ local result=0 -+ [[ -n usingTcp ]] -+ whileLoop usingTcp 0 -+ local commandToUse=usingTcp -+ local reportTimeout=0 -+ Array::contains usingTcp usingTcp usingNc -+ local element -+ for element in "${@:2}" -+ [[ usingTcp = \u\s\i\n\g\T\c\p ]] -+ return 0 -+ local -i start_ts=1 -+ true -+ usingTcp -+ [[ -n '' ]] -+ echo -bin/waitForIt: connect: Connection refused -bin/waitForIt: line 1635: /dev/tcp/localhost/888: Connection refused -+ (( optionTimeout!=0 && SECONDS - start_ts > optionTimeout )) -+ sleep 1 -Terminated -++ cleanOnExit -++ [[ 0 = \1 ]] -++ [[ -n xxx ]] -++ Log::displayDebug 'KEEP_TEMP_FILES=0 removing temp files '\''/tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE'\''' -++ (( BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_DEBUG )) -++ Log::logDebug 'KEEP_TEMP_FILES=0 removing temp files '\''/tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE'\''' -++ (( BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_DEBUG )) -++ rm -Rf /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE -+ true -+ usingTcp -+ [[ -n '' ]] -+ echo -bin/waitForIt: connect: Connection refused -bin/waitForIt: line 1635: /dev/tcp/localhost/888: Connection refused -+ (( optionTimeout!=0 && SECONDS - start_ts > optionTimeout )) -+ sleep 1 -+ true -+ usingTcp -+ [[ -n '' ]] -+ echo -bin/waitForIt: connect: Connection refused -bin/waitForIt: line 1635: /dev/tcp/localhost/888: Connection refused -+ (( optionTimeout!=0 && SECONDS - start_ts > optionTimeout )) -+ sleep 1 -+ true -+ usingTcp -+ [[ -n '' ]] -+ echo -bin/waitForIt: connect: Connection refused -bin/waitForIt: line 1635: /dev/tcp/localhost/888: Connection refused -+ (( optionTimeout!=0 && SECONDS - start_ts > optionTimeout )) -+ [[ 0 = \1 ]] -+ return 2 -+ result=2 -+ exit 2 -+ cleanOnExit -+ [[ 0 = \1 ]] -+ [[ -n xxx ]] -+ Log::displayDebug 'KEEP_TEMP_FILES=0 removing temp files '\''/tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE'\''' -+ (( BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_DEBUG )) -+ Log::logDebug 'KEEP_TEMP_FILES=0 removing temp files '\''/tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE'\''' -+ (( BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_DEBUG )) -+ rm -Rf /tmp/bash-framework/bash-framework-1781721-rBgQna/bash-framework-1781757-68jUsE -+ result=124 -+ [[ 124 != \0 ]] -+ Log::displayError 'waitForIt - timeout occurred after 3 seconds for localhost:888' -+ (( BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_ERROR )) -+ echo -e 'ERROR - waitForIt - timeout occurred after 3 seconds for localhost:888' -ERROR - waitForIt - timeout occurred after 3 seconds for localhost:888 -+ Log::logError 'waitForIt - timeout occurred after 3 seconds for localhost:888' -+ (( BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_ERROR )) -+ return 124 -+ result=124 -+ [[ -n '' ]] -+ exit 124 -+ cleanOnExit -+ [[ 0 = \1 ]] -+ [[ -n xxx ]] -+ Log::displayDebug 'KEEP_TEMP_FILES=0 removing temp files '\''/tmp/bash-framework/bash-framework-1781721-rBgQna'\''' -+ (( BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_DEBUG )) -+ Log::logDebug 'KEEP_TEMP_FILES=0 removing temp files '\''/tmp/bash-framework/bash-framework-1781721-rBgQna'\''' -+ (( BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_DEBUG )) -+ rm -Rf /tmp/bash-framework/bash-framework-1781721-rBgQna diff --git a/waitForMysql.log b/waitForMysql.log deleted file mode 100644 index 6061c88f..00000000 --- a/waitForMysql.log +++ /dev/null @@ -1,376 +0,0 @@ -+ facade_main_665f5dabe75f418ea1c10f53fac6da5e localhost 3306 mysql mysql -+ Env::requireLoad -+ local configFilesStr -++ Env::getOrderedConfFiles -++ configFiles=() -++ local -a configFiles -++ [[ -n '' ]] -++ local defaultEnvFile -+++ Env::createDefaultEnvFile -+++ local envFile -++++ Framework::createTempFile createDefaultEnvFileEnvFile -++++ mktemp -p /tmp/bash-framework/bash-framework-1781863-ah8kM7 -t createDefaultEnvFileEnvFile.XXXXXXXXXXXX -+++ envFile=/tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.lyQQz9jGy7RO -+++ echo BASH_FRAMEWORK_THEME=default -+++ echo BASH_FRAMEWORK_LOG_LEVEL=0 -+++ echo BASH_FRAMEWORK_DISPLAY_LEVEL=2 -+++ echo 'BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-"${FRAMEWORK_ROOT_DIR}/logs/${SCRIPT_NAME}.log"}"' -+++ echo BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+++ echo /tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.lyQQz9jGy7RO -++ defaultEnvFile=/tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.lyQQz9jGy7RO -++ configFiles+=("${defaultEnvFile}") -++ local file -++ for file in "${configFiles[@]}" -++ [[ ! -f /tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.lyQQz9jGy7RO ]] -++ [[ ! -r /tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.lyQQz9jGy7RO ]] -++ echo /tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.lyQQz9jGy7RO -+ configFilesStr=/tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.lyQQz9jGy7RO -+ local -a configFiles -+ readarray -t configFiles -+ (( 1 == 0 )) -+ [[ -z /tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.lyQQz9jGy7RO ]] -+ Env::mergeConfFiles /tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.lyQQz9jGy7RO -+ configFileList=("$@") -+ local -a configFileList -+ (( 1 == 0 )) -+ local combinedConfigFile -++ Framework::createTempFile mergeConfFiles -++ mktemp -p /tmp/bash-framework/bash-framework-1781863-ah8kM7 -t mergeConfFiles.XXXXXXXXXXXX -+ combinedConfigFile=/tmp/bash-framework/bash-framework-1781863-ah8kM7/mergeConfFiles.vhNqn9bXvxy0 -+ sed -E -e 's/\s*$// ; /^$/d ; /^#.*$/d ; s/=([^"'\''].*)$/="\1"/' /tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.lyQQz9jGy7RO -+ Filters::commentLines -+ grep -vxE '[[:blank:]]*(#.*)?' -+ awk -F= '!line[$1]++' -+ set -o allexport -+ source /tmp/bash-framework/bash-framework-1781863-ah8kM7/mergeConfFiles.vhNqn9bXvxy0 -++ BASH_FRAMEWORK_THEME=default -++ BASH_FRAMEWORK_LOG_LEVEL=0 -++ BASH_FRAMEWORK_DISPLAY_LEVEL=2 -++ BASH_FRAMEWORK_LOG_FILE=/logs/waitForMysql.log -++ BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+ source /tmp/bash-framework/bash-framework-1781863-ah8kM7/mergeConfFiles.vhNqn9bXvxy0 -++ BASH_FRAMEWORK_THEME=default -++ BASH_FRAMEWORK_LOG_LEVEL=0 -++ BASH_FRAMEWORK_DISPLAY_LEVEL=2 -++ BASH_FRAMEWORK_LOG_FILE=/logs/waitForMysql.log -++ BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+ set +o allexport -+ UI::requireTheme -+ UI::theme default -+ local theme=default -+ [[ ! default =~ -force$ ]] -+ Assert::tty -+ [[ 0 = \1 ]] -+ [[ 0 = \1 ]] -+ [[ -t 1 ]] -+ [[ -t 2 ]] -+ theme=noColor -+ case "${theme}" in -+ [[ noColor = \d\e\f\a\u\l\t ]] -+ export BASH_FRAMEWORK_THEME=noColor -+ BASH_FRAMEWORK_THEME=noColor -+ export __ERROR_COLOR= -+ __ERROR_COLOR= -+ export __INFO_COLOR= -+ __INFO_COLOR= -+ export __SUCCESS_COLOR= -+ __SUCCESS_COLOR= -+ export __WARNING_COLOR= -+ __WARNING_COLOR= -+ export __SKIPPED_COLOR= -+ __SKIPPED_COLOR= -+ export __DEBUG_COLOR= -+ __DEBUG_COLOR= -+ export __HELP_COLOR= -+ __HELP_COLOR= -+ export __TEST_COLOR= -+ __TEST_COLOR= -+ export __TEST_ERROR_COLOR= -+ __TEST_ERROR_COLOR= -+ export __HELP_TITLE_COLOR= -+ __HELP_TITLE_COLOR= -+ export __HELP_OPTION_COLOR= -+ __HELP_OPTION_COLOR= -+ export __RESET_COLOR= -+ __RESET_COLOR= -+ export __HELP_EXAMPLE= -+ __HELP_EXAMPLE= -+ export __HELP_TITLE= -+ __HELP_TITLE= -+ export __HELP_NORMAL= -+ __HELP_NORMAL= -+ Log::requireLoad -+ [[ -z /logs/waitForMysql.log ]] -+ (( BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF )) -+ (( BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF )) -+ Compiler::Facade::requireCommandBinDir -+ COMMAND_BIN_DIR=/home/wsl/fchastanet/bash-tools/bin -+ Env::pathPrepend /home/wsl/fchastanet/bash-tools/bin -+ local arg -+ for arg in "$@" -+ [[ -d /home/wsl/fchastanet/bash-tools/bin ]] -+ [[ :/home/wsl/.virtualenv/python3.9/bin:/home/wsl/.bin:/home/wsl/.local/bin:/home/wsl/fchastanet/bash-tools/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/lib/wsl/lib:/c/Python311/Scripts/:/c/Python311/:/c/Program Files/Common Files/Oracle/Java/javapath:/c/Program Files (x86)/Common Files/Oracle/Java/javapath:/c/PROGRA~1/AdoptOpenJDK/jdk-13.0.2.8-hotspot/bin:/c/Windows/system32:/c/Windows:/c/Windows/System32/Wbem:/c/Windows/System32/WindowsPowerShell/v1.0/:/c/Windows/System32/OpenSSH/:/c/PROGRA~1/WindowsPowerShell/Scripts:/c/PROGRA~1/IcedTeaWeb/WebStart/bin:/c/PROGRA~1/dotnet/:/c/PROGRA~2/WI3CF2~1/10/WINDOW~1:/c/PROGRA~2/Meld/:/c/WINDOWS/system32:/c/WINDOWS:/c/WINDOWS/System32/Wbem:/c/WINDOWS/System32/WindowsPowerShell/v1.0/:/c/WINDOWS/System32/OpenSSH/:/c/PROGRA~1/Git/cmd:/c/Program Files/dotnet/:/c/ProgramData/chocolatey/bin:/c/Program Files/PowerShell/7/:/c/Users/fchastanet/AppData/Local/Microsoft/WindowsApps:/c/PROGRA~1/JetBrains/PHPSTO~1.2/bin:/c/Users/fchastanet/AppData/Local/Programs/MICROS~1/bin:/c/Users/fchastanet/AppData/Local/JetBrains/Toolbox/scripts:/c/Users/fchastanet/.dotnet/tools:/c/Users/fchastanet/AppData/Local/Microsoft/WindowsApps:/c/Users/fchastanet/AppData/Local/Programs/Microsoft VS Code/bin:/c/WINDOWS/system32:/mnt/c/Windows:/c/Users/fchastanet/AppData/Local/Microsoft/WindowsApps:/mnt/c/WINDOWS/System32/WindowsPowerShell/v1.0/:/c/Users/fchastanet/AppData/Local/Programs/MICROS~1/bin:/usr/local/.composer/vendor/bin:/home/wsl/go/bin:/home/wsl/n/bin:/opt/kubectx:/home/wsl/.fzf/bin: != *\:\/\h\o\m\e\/\w\s\l\/\f\c\h\a\s\t\a\n\e\t\/\b\a\s\h\-\t\o\o\l\s\/\b\i\n\:* ]] -+ BASH_FRAMEWORK_ARGV_FILTERED=() -+ declare -a BASH_FRAMEWORK_ARGV_FILTERED -+ declare copyrightBeginYear=2020 -+ declare optionTimeout=15 -+ waitForMysqlCommand parse localhost 3306 mysql mysql -+ local options_parse_cmd=parse -+ shift -+ [[ parse = \p\a\r\s\e ]] -+ optionTimeout=15 -+ local -i options_parse_optionParsedCountOptionTimeout -+ (( options_parse_optionParsedCountOptionTimeout = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionBashFrameworkConfig -+ (( options_parse_optionParsedCountOptionBashFrameworkConfig = 0 )) -+ true -+ optionConfig=0 -+ local -i options_parse_optionParsedCountOptionConfig -+ (( options_parse_optionParsedCountOptionConfig = 0 )) -+ true -+ optionInfoVerbose=0 -+ local -i options_parse_optionParsedCountOptionInfoVerbose -+ (( options_parse_optionParsedCountOptionInfoVerbose = 0 )) -+ true -+ optionDebugVerbose=0 -+ local -i options_parse_optionParsedCountOptionDebugVerbose -+ (( options_parse_optionParsedCountOptionDebugVerbose = 0 )) -+ true -+ optionTraceVerbose=0 -+ local -i options_parse_optionParsedCountOptionTraceVerbose -+ (( options_parse_optionParsedCountOptionTraceVerbose = 0 )) -+ true -+ optionNoColor=0 -+ local -i options_parse_optionParsedCountOptionNoColor -+ (( options_parse_optionParsedCountOptionNoColor = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionTheme -+ (( options_parse_optionParsedCountOptionTheme = 0 )) -+ true -+ optionHelp=0 -+ local -i options_parse_optionParsedCountOptionHelp -+ (( options_parse_optionParsedCountOptionHelp = 0 )) -+ true -+ optionVersion=0 -+ local -i options_parse_optionParsedCountOptionVersion -+ (( options_parse_optionParsedCountOptionVersion = 0 )) -+ true -+ optionQuiet=0 -+ local -i options_parse_optionParsedCountOptionQuiet -+ (( options_parse_optionParsedCountOptionQuiet = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionLogLevel -+ (( options_parse_optionParsedCountOptionLogLevel = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionLogFile -+ (( options_parse_optionParsedCountOptionLogFile = 0 )) -+ true -+ local -i options_parse_optionParsedCountOptionDisplayLevel -+ (( options_parse_optionParsedCountOptionDisplayLevel = 0 )) -+ true -+ local -i options_parse_argParsedCountMysqlHostArg -+ (( options_parse_argParsedCountMysqlHostArg = 0 )) -+ true -+ local -i options_parse_argParsedCountMysqlPortArg -+ (( options_parse_argParsedCountMysqlPortArg = 0 )) -+ true -+ local -i options_parse_argParsedCountMysqlUserArg -+ (( options_parse_argParsedCountMysqlUserArg = 0 )) -+ true -+ local -i options_parse_argParsedCountMysqlPasswordArg -+ (( options_parse_argParsedCountMysqlPasswordArg = 0 )) -+ true -+ local -i options_parse_parsedArgIndex=0 -+ (( 4 > 0 )) -+ local options_parse_arg=localhost -+ local argOptDefaultBehavior=0 -+ case "${options_parse_arg}" in -+ (( 0 )) -+ (( options_parse_parsedArgIndex >= 0 && options_parse_parsedArgIndex < 1 )) -+ (( options_parse_argParsedCountMysqlHostArg >= 1 )) -+ (( ++options_parse_argParsedCountMysqlHostArg )) -+ mysqlHostArg=localhost -+ (( ++options_parse_parsedArgIndex )) -+ shift -+ (( 3 > 0 )) -+ local options_parse_arg=3306 -+ local argOptDefaultBehavior=0 -+ case "${options_parse_arg}" in -+ (( 0 )) -+ (( options_parse_parsedArgIndex >= 0 && options_parse_parsedArgIndex < 1 )) -+ (( options_parse_parsedArgIndex >= 1 && options_parse_parsedArgIndex < 2 )) -+ (( options_parse_argParsedCountMysqlPortArg >= 1 )) -+ (( ++options_parse_argParsedCountMysqlPortArg )) -+ mysqlPortArg=3306 -+ mysqlPortArgCallback 3306 -- mysql mysql -+ [[ ! 3306 =~ ^[0-9]+$ ]] -+ (( mysqlPortArg == 0 )) -+ (( ++options_parse_parsedArgIndex )) -+ shift -+ (( 2 > 0 )) -+ local options_parse_arg=mysql -+ local argOptDefaultBehavior=0 -+ case "${options_parse_arg}" in -+ (( 0 )) -+ (( options_parse_parsedArgIndex >= 0 && options_parse_parsedArgIndex < 1 )) -+ (( options_parse_parsedArgIndex >= 1 && options_parse_parsedArgIndex < 2 )) -+ (( options_parse_parsedArgIndex >= 2 && options_parse_parsedArgIndex < 3 )) -+ (( options_parse_argParsedCountMysqlUserArg >= 1 )) -+ (( ++options_parse_argParsedCountMysqlUserArg )) -+ mysqlUserArg=mysql -+ (( ++options_parse_parsedArgIndex )) -+ shift -+ (( 1 > 0 )) -+ local options_parse_arg=mysql -+ local argOptDefaultBehavior=0 -+ case "${options_parse_arg}" in -+ (( 0 )) -+ (( options_parse_parsedArgIndex >= 0 && options_parse_parsedArgIndex < 1 )) -+ (( options_parse_parsedArgIndex >= 1 && options_parse_parsedArgIndex < 2 )) -+ (( options_parse_parsedArgIndex >= 2 && options_parse_parsedArgIndex < 3 )) -+ (( options_parse_parsedArgIndex >= 3 && options_parse_parsedArgIndex < 4 )) -+ (( options_parse_argParsedCountMysqlPasswordArg >= 1 )) -+ (( ++options_parse_argParsedCountMysqlPasswordArg )) -+ mysqlPasswordArg=mysql -+ (( ++options_parse_parsedArgIndex )) -+ shift -+ (( 0 > 0 )) -+ (( options_parse_argParsedCountMysqlHostArg < 1 )) -+ (( options_parse_argParsedCountMysqlPortArg < 1 )) -+ (( options_parse_argParsedCountMysqlUserArg < 1 )) -+ (( options_parse_argParsedCountMysqlPasswordArg < 1 )) -+ commandOptionParseFinished -+ [[ -z '' ]] -+ BASH_FRAMEWORK_ENV_FILES=() -+ BASH_FRAMEWORK_ENV_FILES+=("${optionEnvFiles[@]}") -+ export BASH_FRAMEWORK_ENV_FILES -+ Env::requireLoad -+ local configFilesStr -++ Env::getOrderedConfFiles -++ configFiles=() -++ local -a configFiles -++ [[ -n '' ]] -++ local defaultEnvFile -+++ Env::createDefaultEnvFile -+++ local envFile -++++ Framework::createTempFile createDefaultEnvFileEnvFile -++++ mktemp -p /tmp/bash-framework/bash-framework-1781863-ah8kM7 -t createDefaultEnvFileEnvFile.XXXXXXXXXXXX -+++ envFile=/tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.KYLUQ53jhaou -+++ echo BASH_FRAMEWORK_THEME=noColor -+++ echo BASH_FRAMEWORK_LOG_LEVEL=0 -+++ echo BASH_FRAMEWORK_DISPLAY_LEVEL=2 -+++ echo 'BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-"${FRAMEWORK_ROOT_DIR}/logs/${SCRIPT_NAME}.log"}"' -+++ echo BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+++ echo /tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.KYLUQ53jhaou -++ defaultEnvFile=/tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.KYLUQ53jhaou -++ configFiles+=("${defaultEnvFile}") -++ local file -++ for file in "${configFiles[@]}" -++ [[ ! -f /tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.KYLUQ53jhaou ]] -++ [[ ! -r /tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.KYLUQ53jhaou ]] -++ echo /tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.KYLUQ53jhaou -+ configFilesStr=/tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.KYLUQ53jhaou -+ local -a configFiles -+ readarray -t configFiles -+ (( 1 == 0 )) -+ [[ -z /tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.KYLUQ53jhaou ]] -+ Env::mergeConfFiles /tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.KYLUQ53jhaou -+ configFileList=("$@") -+ local -a configFileList -+ (( 1 == 0 )) -+ local combinedConfigFile -++ Framework::createTempFile mergeConfFiles -++ mktemp -p /tmp/bash-framework/bash-framework-1781863-ah8kM7 -t mergeConfFiles.XXXXXXXXXXXX -+ combinedConfigFile=/tmp/bash-framework/bash-framework-1781863-ah8kM7/mergeConfFiles.JFHr71G8P6lK -+ sed -E -e 's/\s*$// ; /^$/d ; /^#.*$/d ; s/=([^"'\''].*)$/="\1"/' /tmp/bash-framework/bash-framework-1781863-ah8kM7/createDefaultEnvFileEnvFile.KYLUQ53jhaou -+ Filters::commentLines -+ grep -vxE '[[:blank:]]*(#.*)?' -+ awk -F= '!line[$1]++' -+ set -o allexport -+ source /tmp/bash-framework/bash-framework-1781863-ah8kM7/mergeConfFiles.JFHr71G8P6lK -++ BASH_FRAMEWORK_THEME=noColor -++ BASH_FRAMEWORK_LOG_LEVEL=0 -++ BASH_FRAMEWORK_DISPLAY_LEVEL=2 -++ BASH_FRAMEWORK_LOG_FILE=/logs/waitForMysql.log -++ BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+ source /tmp/bash-framework/bash-framework-1781863-ah8kM7/mergeConfFiles.JFHr71G8P6lK -++ BASH_FRAMEWORK_THEME=noColor -++ BASH_FRAMEWORK_LOG_LEVEL=0 -++ BASH_FRAMEWORK_DISPLAY_LEVEL=2 -++ BASH_FRAMEWORK_LOG_FILE=/logs/waitForMysql.log -++ BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION=5 -+ set +o allexport -+ Log::requireLoad -+ [[ -z /logs/waitForMysql.log ]] -+ (( BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF )) -+ (( BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF )) -+ [[ -n '' ]] -+ BASH_FRAMEWORK_CONFIG_FILE= -+ Framework::loadConfig BASH_FRAMEWORK_CONFIG_FILE '' -+ local -n loadConfig_loadedConfigFile=BASH_FRAMEWORK_CONFIG_FILE -+ shift -+ Conf::loadNearestFile .framework-config loadConfig_loadedConfigFile '' -+ local configFileName=.framework-config -+ local -n loadedFile=loadConfig_loadedConfigFile -+ shift 2 -+ srcDirs=("$@") -+ local -a srcDirs -+ for srcDir in "${srcDirs[@]}" -++ File::upFind '' .framework-config -++ local fromPath= -++ shift -++ local fileName=.framework-config -++ shift -++ local untilInclusivePath=/ -++ shift -++ true -++ [[ -f '' ]] -++ true -++ [[ -f /.framework-config ]] -++ Array::contains '' / / -++ local element -++ for element in "${@:2}" -++ [[ / = '' ]] -++ for element in "${@:2}" -++ [[ / = '' ]] -++ return 1 -+++ readlink -f /.. -++ fromPath=/ -++ true -++ [[ -f //.framework-config ]] -++ Array::contains / / / -++ local element -++ for element in "${@:2}" -++ [[ / = \/ ]] -++ return 0 -++ return 1 -++ true -+ configFile= -+ [[ -n '' ]] -+ Log::displayWarning 'Config file '\''.framework-config'\'' not found in any source directories provided' -+ (( BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_WARNING )) -+ echo -e 'WARN - Config file '\''.framework-config'\'' not found in any source directories provided' -WARN - Config file '.framework-config' not found in any source directories provided -+ Log::logWarning 'Config file '\''.framework-config'\'' not found in any source directories provided' -+ (( BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_WARNING )) -+ return 1 -+ Log::fatal 'Command waitForMysql - error while loading .framework-config file' -+ echo -e 'FATAL - Command waitForMysql - error while loading .framework-config file' -FATAL - Command waitForMysql - error while loading .framework-config file -+ Log::logFatal 'Command waitForMysql - error while loading .framework-config file' -+ Log::logMessage FATAL 'Command waitForMysql - error while loading .framework-config file' -+ local levelMsg=FATAL -+ local 'msg=Command waitForMysql - error while loading .framework-config file' -+ local date -+ [[ -n /logs/waitForMysql.log ]] -+ (( BASH_FRAMEWORK_LOG_LEVEL > __LEVEL_OFF )) -+ exit 1 -+ cleanOnExit -+ [[ 0 = \1 ]] -+ [[ -n xxx ]] -+ Log::displayDebug 'KEEP_TEMP_FILES=0 removing temp files '\''/tmp/bash-framework/bash-framework-1781863-ah8kM7'\''' -+ (( BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_DEBUG )) -+ Log::logDebug 'KEEP_TEMP_FILES=0 removing temp files '\''/tmp/bash-framework/bash-framework-1781863-ah8kM7'\''' -+ (( BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_DEBUG )) -+ rm -Rf /tmp/bash-framework/bash-framework-1781863-ah8kM7