From a82813501baf46460a30bf4b835e867ddefeb8fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chastanet?= Date: Mon, 27 Nov 2023 22:53:25 +0100 Subject: [PATCH] migrated to bash-tools-framework 1.1.8 - get rid of build.sh --- .cspell.json | 1 + .framework-config | 23 +- .github/workflows/lint-test.yml | 11 - .pre-commit-config.yaml | 26 +- Commands.tmpl.md | 9 - bin/cli | 351 ++++------------- bin/dbImport | 327 ++++------------ bin/dbImportProfile | 341 ++++------------- bin/dbImportStream | 327 ++++------------ bin/dbQueryAllDatabases | 355 ++++------------- bin/dbScriptAllDatabases | 355 ++++------------- bin/doc | 341 ++++------------- bin/gitIsAncestorOf | 341 ++++------------- bin/gitIsBranch | 341 ++++------------- bin/gitRenameBranch | 341 ++++------------- bin/installRequirements | 359 ++++-------------- bin/mysql2puml | 331 ++++------------ bin/upgradeGithubRelease | 351 ++++------------- bin/waitForIt | 325 ++++------------ bin/waitForMysql | 341 ++++------------- build.sh | 37 -- conf/dbScripts/extractData | 190 ++------- install | 329 +++------------- .../Converters/testsData/mysql2puml.help.txt | 3 +- src/_binaries/DbImport/dbImport.bats | 1 - .../testsData/auto_default.local_fromDb_20.sh | 2 +- .../DbImport/testsData/dbImport.help.txt | 3 +- .../testsData/dbImportProfile.help.txt | 3 +- .../testsData/dbImportStream.help.txt | 3 +- .../testsData/dbQueryAllDatabases.help.txt | 3 +- .../testsData/dbScriptAllDatabases.help.txt | 3 +- src/_binaries/Docker/testsData/cli.help.txt | 3 +- .../Git/testsData/gitIsAncestorOf.help.txt | 3 +- .../Git/testsData/gitRenameBranch.help.txt | 3 +- .../testsData/upgradeGithubRelease.help.txt | 3 +- .../Utils/testsData/waitForIt.help.txt | 3 +- .../Utils/testsData/waitForMysql.help.txt | 3 +- src/_binaries/build/doc.sh | 2 +- .../build/installRequirements.options.tpl | 1 - 39 files changed, 1186 insertions(+), 4609 deletions(-) delete mode 100755 build.sh diff --git a/.cspell.json b/.cspell.json index d89c1761..e0c72000 100644 --- a/.cspell.json +++ b/.cspell.json @@ -19,6 +19,7 @@ ".jscpd.json", ".mega-linter.yml", ".shellcheckrc", + "**/*.svg", ".env", "**/*.help.txt" ], diff --git a/.framework-config b/.framework-config index 034a27e3..13906e84 100755 --- a/.framework-config +++ b/.framework-config @@ -1,22 +1,35 @@ #!/usr/bin/env bash # shellcheck disable=SC2034 -BASH_TOOLS_ROOT_DIR="${BASH_TOOLS_ROOT_DIR:-$(pwd)}" -REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" -FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")" && pwd -P)}" +BASH_TOOLS_ROOT_DIR="${BASH_TOOLS_ROOT_DIR:-$(cd "$(readlink -e "${BASH_SOURCE[0]%/*}")" && pwd -P)}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-${BASH_TOOLS_ROOT_DIR}/vendor/bash-tools-framework}" FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" +# compile parameters +# srcFile : file that needs to be compiled +# templateDir : directory from which bash-tpl templates will be searched +# binDir : fallback bin directory in case BIN_FILE has not been provided +# rootDir : directory used to compute src file relative path +# srcDirs : additional directories where to find the functions +COMPILE_PARAMETERS=( + --src-dir "${BASH_TOOLS_ROOT_DIR}/src" + --src-dir "${FRAMEWORK_SRC_DIR}" + --bin-dir "${BASH_TOOLS_ROOT_DIR}/bin" + --root-dir "${BASH_TOOLS_ROOT_DIR}" + --template-dir "${BASH_TOOLS_ROOT_DIR}/src" +) + # describe the functions that will be skipped from being imported FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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="${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="${BATS_FILE_NOT_NEEDED_REGEXP:-(^build.sh$|^conf/|^bin/|.framework-config|^install$|.bats$|/testsData/|^manualTests/|/_.sh$|/ZZZ.sh$|/__all.sh$|^src/batsHeaders.sh$|^src/_includes)}" +BATS_FILE_NOT_NEEDED_REGEXP="${BATS_FILE_NOT_NEEDED_REGEXP:-(^conf/|^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="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^conf/|^bin/|^\.framework-config$|^build.sh$|\.tpl$|testsData/binaryFile$}" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^conf/|^bin/|^\.framework-config$|\.tpl$|testsData/binaryFile$}" # Source directories if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then FRAMEWORK_SRC_DIRS=( diff --git a/.github/workflows/lint-test.yml b/.github/workflows/lint-test.yml index 056bff3d..98b5b8ed 100644 --- a/.github/workflows/lint-test.yml +++ b/.github/workflows/lint-test.yml @@ -70,17 +70,6 @@ jobs: name: build bash-tools-${{matrix.vendor}}-${{matrix.bashTarVersion}} status: pending - - name: Pull/Build/Push docker image - env: - BRANCH: ${GITHUB_REF##*/} - run: | - vendor/bash-tools-framework/bin/buildPushDockerImage \ - --vendor "${{ matrix.vendor }}" \ - --bash-version "${{ matrix.bashTarVersion }}" \ - --bash-base-image "${{ matrix.bashImage }}" \ - --branch-name "${GITHUB_REF##*/}" \ - --push -vvv - - name: Check image run: | docker run --rm \ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6328db0a..8a876cd5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,6 +16,10 @@ repos: hooks: - id: mixed-line-ending - id: end-of-file-fixer + exclude: | + (?x)( + .svg$ + ) - id: check-executables-have-shebangs - id: check-shebang-scripts-are-executable - id: check-xml @@ -57,7 +61,7 @@ repos: - id: prettier - repo: https://github.com/fchastanet/bash-tools-framework - rev: 1.1.5 + rev: 1.1.8 hooks: - id: fixShebangExecutionBit - id: fixShebangExecutionBitGithubActions @@ -87,6 +91,12 @@ repos: - id: plantuml - id: buildShFiles - id: buildShFilesGithubAction + - id: runUnitTests + # not manual as github will run UT with several versions of bash and arch + stages: [pre-commit] + - id: megalinterCheckVersion + - id: megalinter + - id: megalinterGithubAction - repo: local hooks: @@ -95,7 +105,7 @@ repos: name: build doc files language: script entry: bin/doc - args: [--verbose, --skip-docker-build] + args: [-vvv] pass_filenames: false require_serial: true always_run: true @@ -104,17 +114,5 @@ repos: - <<: *BUILD_DOC_FILES id: buildDocFilesGithubAction name: build doc files for Github Actions - args: [--verbose] fail_fast: false stages: [manual] - - # manual stage used to select github action with megalinter full config - - repo: https://github.com/fchastanet/bash-tools-framework - rev: 1.1.5 - hooks: - - id: runUnitTests - # not manual as github will run UT with several versions of bash and arch - stages: [pre-commit] - - id: megalinterCheckVersion - - id: megalinter - - id: megalinterGithubAction diff --git a/Commands.tmpl.md b/Commands.tmpl.md index d59164a5..d5165ebc 100644 --- a/Commands.tmpl.md +++ b/Commands.tmpl.md @@ -7,7 +7,6 @@ - [1.4. bin/doc](#14-bindoc) - [1.5. bin/findShebangFiles](#15-binfindshebangfiles) - [1.6. bin/test](#16-bintest) - - [1.7. bin/runBuildContainer](#17-binrunbuildcontainer) - [2. Converter and Generator tools](#2-converter-and-generator-tools) - [2.1. bin/generateShellDoc](#21-bingenerateshelldoc) - [2.2. bin/mysql2puml](#22-binmysql2puml) @@ -78,14 +77,6 @@ imported from bash-tools-framework @@@test_help@@@ ``` -### 1.7. bin/runBuildContainer - -imported from bash-tools-framework - -```text -@@@runBuildContainer_help@@@ -``` - ## 2. Converter and Generator tools ### 2.1. bin/generateShellDoc diff --git a/bin/cli b/bin/cli index d1c8454a..11e0c762 100755 --- a/bin/cli +++ b/bin/cli @@ -426,42 +426,38 @@ Database::checkDsnFile() { } # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") + fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } -} - -# @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 "$@" + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description Log namespace provides 2 kind of functions @@ -732,122 +728,6 @@ Conf::list() { ) } -# @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" -# -# @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 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 -} - -# @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 -} - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -885,15 +765,6 @@ Filters::removeAnsiCodes() { # cspell:enable } -# @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" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -988,81 +859,13 @@ 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 -# -# @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 [[ -f "${fromPath}" ]]; then - fromPath="$(dirname "${fromPath}")" - fi - while true; do - if [[ -f "${fromPath}/${fileName}" ]]; then - echo "${fromPath}/${fileName}" - return 0 - fi - if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then - return 1 - fi - fromPath="$(readlink -f "${fromPath}"/..)" - done - return 1 -} - -# @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 -} - -# @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" -} - -# @description log message to file +# @description Display message using skip color (yellow) # @arg $1 message:String the message to display -Log::logSkipped() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO)); then - Log::logMessage "${2:-SKIPPED}" "$1" +Log::displaySkipped() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO)); then + echo -e "${__SKIPPED_COLOR}SKIPPED - ${1}${__RESET_COLOR}" >&2 fi + Log::logSkipped "$1" } # @description ensure command realpath is available @@ -1072,20 +875,12 @@ Linux::requireRealpathCommand() { Assert::commandExists realpath } -# @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 +# @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 } # FUNCTIONS @@ -1181,6 +976,7 @@ optionVersionCallback() { # shellcheck disable=SC2317 # if function is overridden optionEnvFileCallback() { local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" exit 1 @@ -1315,54 +1111,47 @@ defaultFrameworkConfig="$( # copied from src/_includes/.framework-config.default # shellcheck disable=SC2034 +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")/../.." && pwd -P)}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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)" +NON_FRAMEWORK_FILES_REGEXP="${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)" +BATS_FILE_NOT_NEEDED_REGEXP="${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$" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|\.tpl$|/testsData/|^manualTests/|\.bats$}" # Source directories -FRAMEWORK_SRC_DIRS=( - "${FRAMEWORK_ROOT_DIR}/src" -) +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi # export here all the variables that will be used in your templates -export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/$(basename "$0").log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" 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 default template framework config + # shellcheck disable=SC2034 + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" > "${defaultEnvFile}" - # 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 + Env::requireLoad "${defaultEnvFile}" + Log::requireLoad if [[ "${optionConfig}" = "1" ]]; then displayConfig @@ -1737,7 +1526,7 @@ cliCommand() { echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file) + helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray diff --git a/bin/dbImport b/bin/dbImport index 08d1c973..33acf693 100755 --- a/bin/dbImport +++ b/bin/dbImport @@ -512,28 +512,38 @@ Database::setQueryOptions() { } # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description delete files older than n days in given path @@ -571,20 +581,6 @@ Framework::createTempFile() { mktemp -p "${TMPDIR:-/tmp}" -t "${1:-}.XXXXXXXXXXXX" } -# @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 "$@" -} - # @description Log namespace provides 2 kind of functions # - Log::display* allows to display given message with # given display level @@ -900,41 +896,6 @@ Conf::list() { ) } -# @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" -# -# @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 # @@ -986,87 +947,6 @@ Database::checkDsnFile() { ) } -# @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 -} - -# @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 -} - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -1104,15 +984,6 @@ Filters::removeAnsiCodes() { # cspell:enable } -# @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" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -1249,65 +1120,20 @@ 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 -# -# @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 [[ -f "${fromPath}" ]]; then - fromPath="$(dirname "${fromPath}")" +# @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 - while true; do - if [[ -f "${fromPath}/${fileName}" ]]; then - echo "${fromPath}/${fileName}" - return 0 - fi - if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then - return 1 - fi - fromPath="$(readlink -f "${fromPath}"/..)" - done - return 1 + Log::logSkipped "$1" } -# @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 +# @description ensure command realpath is available +# @exitcode 1 if realpath command not available +# @stderr diagnostics information is displayed +Linux::requireRealpathCommand() { + Assert::commandExists realpath } # @description log message to file @@ -1318,13 +1144,6 @@ Log::logSkipped() { fi } -# @description ensure command realpath is available -# @exitcode 1 if realpath command not available -# @stderr diagnostics information is displayed -Linux::requireRealpathCommand() { - Assert::commandExists realpath -} - # FUNCTIONS facade_main_dbImportsh() { @@ -1425,6 +1244,7 @@ optionVersionCallback() { # shellcheck disable=SC2317 # if function is overridden optionEnvFileCallback() { local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" exit 1 @@ -1559,54 +1379,47 @@ defaultFrameworkConfig="$( # copied from src/_includes/.framework-config.default # shellcheck disable=SC2034 +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")/../.." && pwd -P)}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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)" +NON_FRAMEWORK_FILES_REGEXP="${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)" +BATS_FILE_NOT_NEEDED_REGEXP="${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$" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|\.tpl$|/testsData/|^manualTests/|\.bats$}" # Source directories -FRAMEWORK_SRC_DIRS=( - "${FRAMEWORK_ROOT_DIR}/src" -) +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi # export here all the variables that will be used in your templates -export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/$(basename "$0").log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" 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 default template framework config + # shellcheck disable=SC2034 + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" > "${defaultEnvFile}" - # 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 + Env::requireLoad "${defaultEnvFile}" + Log::requireLoad if [[ "${optionConfig}" = "1" ]]; then displayConfig @@ -2243,7 +2056,7 @@ dbImportCommand() { echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file) + helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray diff --git a/bin/dbImportProfile b/bin/dbImportProfile index 66f5282d..0d7b4c40 100755 --- a/bin/dbImportProfile +++ b/bin/dbImportProfile @@ -394,42 +394,38 @@ Database::setQueryOptions() { } # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") + fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } -} - -# @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 "$@" + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description Log namespace provides 2 kind of functions @@ -770,41 +766,6 @@ Conf::list() { ) } -# @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" -# -# @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 # @@ -856,87 +817,6 @@ Database::checkDsnFile() { ) } -# @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 -} - -# @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 -} - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -962,15 +842,6 @@ Filters::removeAnsiCodes() { # cspell:enable } -# @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" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -1065,23 +936,6 @@ 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 concatenate 2 paths and ensure the path is correct using realpath -m # @arg $1 basePath:String # @arg $2 subPath:String @@ -1094,56 +948,13 @@ File::concatenatePath() { realpath -m "${fullPath}" 2>/dev/null } -# @description search a file in parent directories -# -# @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 [[ -f "${fromPath}" ]]; then - fromPath="$(dirname "${fromPath}")" +# @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 - while true; do - if [[ -f "${fromPath}/${fileName}" ]]; then - echo "${fromPath}/${fileName}" - return 0 - fi - if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then - return 1 - fi - fromPath="$(readlink -f "${fromPath}"/..)" - done - return 1 -} - -# @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 -} - -# @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" + Log::logSkipped "$1" } # @description log message to file @@ -1154,22 +965,6 @@ Log::logSkipped() { fi } -# @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 -} - # @description ensure command realpath is available # @exitcode 1 if realpath command not available # @stderr diagnostics information is displayed @@ -1268,6 +1063,7 @@ optionVersionCallback() { # shellcheck disable=SC2317 # if function is overridden optionEnvFileCallback() { local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" exit 1 @@ -1402,54 +1198,47 @@ defaultFrameworkConfig="$( # copied from src/_includes/.framework-config.default # shellcheck disable=SC2034 +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")/../.." && pwd -P)}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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)" +NON_FRAMEWORK_FILES_REGEXP="${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)" +BATS_FILE_NOT_NEEDED_REGEXP="${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$" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|\.tpl$|/testsData/|^manualTests/|\.bats$}" # Source directories -FRAMEWORK_SRC_DIRS=( - "${FRAMEWORK_ROOT_DIR}/src" -) +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi # export here all the variables that will be used in your templates -export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/$(basename "$0").log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" 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 default template framework config + # shellcheck disable=SC2034 + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" > "${defaultEnvFile}" - # 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 + Env::requireLoad "${defaultEnvFile}" + Log::requireLoad if [[ "${optionConfig}" = "1" ]]; then displayConfig @@ -1872,7 +1661,7 @@ dbImportProfileCommand() { echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file) + helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray diff --git a/bin/dbImportStream b/bin/dbImportStream index 045b6309..6d8fb926 100755 --- a/bin/dbImportStream +++ b/bin/dbImportStream @@ -449,28 +449,38 @@ Database::setQueryOptions() { } # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description create a temp file using default TMPDIR variable @@ -481,20 +491,6 @@ Framework::createTempFile() { mktemp -p "${TMPDIR:-/tmp}" -t "${1:-}.XXXXXXXXXXXX" } -# @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 "$@" -} - # @description Log namespace provides 2 kind of functions # - Log::display* allows to display given message with # given display level @@ -810,41 +806,6 @@ Conf::list() { ) } -# @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" -# -# @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 # @@ -896,87 +857,6 @@ Database::checkDsnFile() { ) } -# @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 -} - -# @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 -} - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -1014,15 +894,6 @@ Filters::removeAnsiCodes() { # cspell:enable } -# @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" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -1159,65 +1030,20 @@ 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 -# -# @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 [[ -f "${fromPath}" ]]; then - fromPath="$(dirname "${fromPath}")" +# @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 - while true; do - if [[ -f "${fromPath}/${fileName}" ]]; then - echo "${fromPath}/${fileName}" - return 0 - fi - if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then - return 1 - fi - fromPath="$(readlink -f "${fromPath}"/..)" - done - return 1 + Log::logSkipped "$1" } -# @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 +# @description ensure command realpath is available +# @exitcode 1 if realpath command not available +# @stderr diagnostics information is displayed +Linux::requireRealpathCommand() { + Assert::commandExists realpath } # @description log message to file @@ -1228,13 +1054,6 @@ Log::logSkipped() { fi } -# @description ensure command realpath is available -# @exitcode 1 if realpath command not available -# @stderr diagnostics information is displayed -Linux::requireRealpathCommand() { - Assert::commandExists realpath -} - # FUNCTIONS facade_main_dbImportStreamsh() { @@ -1329,6 +1148,7 @@ optionVersionCallback() { # shellcheck disable=SC2317 # if function is overridden optionEnvFileCallback() { local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" exit 1 @@ -1463,54 +1283,47 @@ defaultFrameworkConfig="$( # copied from src/_includes/.framework-config.default # shellcheck disable=SC2034 +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")/../.." && pwd -P)}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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)" +NON_FRAMEWORK_FILES_REGEXP="${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)" +BATS_FILE_NOT_NEEDED_REGEXP="${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$" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|\.tpl$|/testsData/|^manualTests/|\.bats$}" # Source directories -FRAMEWORK_SRC_DIRS=( - "${FRAMEWORK_ROOT_DIR}/src" -) +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi # export here all the variables that will be used in your templates -export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/$(basename "$0").log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" 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 default template framework config + # shellcheck disable=SC2034 + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" > "${defaultEnvFile}" - # 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 + Env::requireLoad "${defaultEnvFile}" + Log::requireLoad if [[ "${optionConfig}" = "1" ]]; then displayConfig @@ -2055,7 +1868,7 @@ dbImportStreamCommand() { echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file) + helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray diff --git a/bin/dbQueryAllDatabases b/bin/dbQueryAllDatabases index 2b198934..06ffbbe0 100755 --- a/bin/dbQueryAllDatabases +++ b/bin/dbQueryAllDatabases @@ -440,42 +440,38 @@ Database::setQueryOptions() { } # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") + fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } -} - -# @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 "$@" + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description Log namespace provides 2 kind of functions @@ -807,41 +803,6 @@ Conf::list() { ) } -# @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" -# -# @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 # @@ -932,87 +893,6 @@ Database::query() { fi } -# @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 -} - -# @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 -} - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -1050,15 +930,6 @@ Filters::removeAnsiCodes() { # cspell:enable } -# @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" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -1195,81 +1066,13 @@ 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 -# -# @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 [[ -f "${fromPath}" ]]; then - fromPath="$(dirname "${fromPath}")" - fi - while true; do - if [[ -f "${fromPath}/${fileName}" ]]; then - echo "${fromPath}/${fileName}" - return 0 - fi - if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then - return 1 - fi - fromPath="$(readlink -f "${fromPath}"/..)" - done - return 1 -} - -# @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 -} - -# @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" -} - -# @description log message to file +# @description Display message using skip color (yellow) # @arg $1 message:String the message to display -Log::logSkipped() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO)); then - Log::logMessage "${2:-SKIPPED}" "$1" +Log::displaySkipped() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO)); then + echo -e "${__SKIPPED_COLOR}SKIPPED - ${1}${__RESET_COLOR}" >&2 fi + Log::logSkipped "$1" } # @description ensure command realpath is available @@ -1279,28 +1082,20 @@ Linux::requireRealpathCommand() { Assert::commandExists realpath } -# @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 +# @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 } # FUNCTIONS # @require Compiler::Embed::requireEmbedBinDir -declare -gx embed_function_DbQueryOneDatabase="${PERSISTENT_TMPDIR:-/tmp}/bin/db083cb53bb15c706d27273bb2620268/dbQueryOneDatabase" -declare -gx encoded_binary_file_DbQueryOneDatabase="IyEvdXNyL2Jpbi9lbnYgYmFzaAojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgR0VORVJBVEVEIEZBQ0FERSBGUk9NIGh0dHBzOi8vZ2l0aHViLmNvbS9mY2hhc3RhbmV0L2Jhc2gtdG9vbHMvdHJlZS9tYXN0ZXIvLi4vYmFzaC1kZXYtZW52L3ZlbmRvci9iYXNoLXRvb2xzLWZyYW1ld29yay9zcmMvQ29tcGlsZXIvRW1iZWQvZW1iZWRGcmFtZXdvcmtGdW5jdGlvbi5iaW5GaWxlLnRwbAojIERPIE5PVCBFRElUIElUCiMgQGdlbmVyYXRlZAojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjI4OCxTQzIwMzQKIyBCSU5fRklMRT0ke0JJTl9GSUxFfQojIEZBQ0FERQoKIyBlbnN1cmUgdGhhdCBubyB1c2VyIGFsaWFzZXMgY291bGQgaW50ZXJmZXJlIHdpdGgKIyBjb21tYW5kcyB1c2VkIGluIHRoaXMgc2NyaXB0CnVuYWxpYXMgLWEgfHwgdHJ1ZQpzaG9wdCAtdSBleHBhbmRfYWxpYXNlcwoKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0CigoZmFpbHVyZXMgPSAwKSkgfHwgdHJ1ZQoKIyBCYXNoIHdpbGwgcmVtZW1iZXIgJiByZXR1cm4gdGhlIGhpZ2hlc3QgZXhpdCBjb2RlIGluIGEgY2hhaW4gb2YgcGlwZXMuCiMgVGhpcyB3YXkgeW91IGNhbiBjYXRjaCB0aGUgZXJyb3IgaW5zaWRlIHBpcGVzLCBlLmcuIG15c3FsZHVtcCB8IGd6aXAKc2V0IC1vIHBpcGVmYWlsCnNldCAtbyBlcnJleGl0CgojIENvbW1hbmQgU3Vic3RpdHV0aW9uIGNhbiBpbmhlcml0IGVycmV4aXQgb3B0aW9uIHNpbmNlIGJhc2ggdjQuNApzaG9wdCAtcyBpbmhlcml0X2VycmV4aXQgfHwgdHJ1ZQoKIyBhIGxvZyBpcyBnZW5lcmF0ZWQgd2hlbiBhIGNvbW1hbmQgZmFpbHMKc2V0IC1vIGVycnRyYWNlCgojIHVzZSBudWxsZ2xvYiBzbyB0aGF0IChmaWxlKi5waHApIHdpbGwgcmV0dXJuIGFuIGVtcHR5IGFycmF5IGlmIG5vIGZpbGUgbWF0Y2hlcyB0aGUgd2lsZGNhcmQKc2hvcHQgLXMgbnVsbGdsb2IKCiMgZW5zdXJlIHJlZ2V4cCBhcmUgaW50ZXJwcmV0ZWQgd2l0aG91dCBhY2NlbnR1YXRlZCBjaGFyYWN0ZXJzCmV4cG9ydCBMQ19BTEw9UE9TSVgKCmV4cG9ydCBURVJNPXh0ZXJtLTI1NmNvbG9yCgojIGF2b2lkIGludGVyYWN0aXZlIGluc3RhbGwKZXhwb3J0IERFQklBTl9GUk9OVEVORD1ub25pbnRlcmFjdGl2ZQpleHBvcnQgREVCQ09ORl9OT05JTlRFUkFDVElWRV9TRUVOPXRydWUKCiMgc3RvcmUgY29tbWFuZCBhcmd1bWVudHMgZm9yIGxhdGVyIHVzYWdlCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNApkZWNsYXJlIC1hIEJBU0hfRlJBTUVXT1JLX0FSR1Y9KCIkQCIpCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNApkZWNsYXJlIC1hIE9SSUdJTkFMX0JBU0hfRlJBTUVXT1JLX0FSR1Y9KCIkQCIpCgojIEBzZWUgaHR0cHM6Ly91bml4LnN0YWNrZXhjaGFuZ2UuY29tL2EvMzg2ODU2CiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjMxNwppbnRlcnJ1cHRNYW5hZ2VtZW50KCkgewogICMgcmVzdG9yZSBTSUdJTlQgaGFuZGxlcgogIHRyYXAgLSBJTlQKICAjIGVuc3VyZSB0aGF0IEN0cmwtQyBpcyB0cmFwcGVkIGJ5IHRoaXMgc2NyaXB0IGFuZCBub3QgYnkgc3ViIHByb2Nlc3MKICAjIHJlcG9ydCB0byB0aGUgcGFyZW50IHRoYXQgd2UgaGF2ZSBpbmRlZWQgYmVlbiBpbnRlcnJ1cHRlZAogIGtpbGwgLXMgSU5UICIkJCIKfQp0cmFwIGludGVycnVwdE1hbmFnZW1lbnQgSU5UClNDUklQVF9OQU1FPSR7MCMjKi99ClJFQUxfU0NSSVBUX0ZJTEU9IiQocmVhZGxpbmsgLWUgIiQocmVhbHBhdGggIiR7QkFTSF9TT1VSQ0VbMF19IikiKSIKaWYgW1sgLW4gIiR7RU1CRURfQ1VSUkVOVF9ESVJ9IiBdXTsgdGhlbgogIENVUlJFTlRfRElSPSIke0VNQkVEX0NVUlJFTlRfRElSfSIKZWxzZQogIENVUlJFTlRfRElSPSIkKGNkICIkKHJlYWRsaW5rIC1lICIke1JFQUxfU0NSSVBUX0ZJTEUlLyp9IikiICYmIHB3ZCAtUCkiCmZpCgojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKIyBUZW1wIGRpciBtYW5hZ2VtZW50CiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKS0VFUF9URU1QX0ZJTEVTPSIke0tFRVBfVEVNUF9GSUxFUzotMH0iCmV4cG9ydCBLRUVQX1RFTVBfRklMRVMKCiMgUEVSU0lTVEVOVF9UTVBESVIgaXMgbm90IGRlbGV0ZWQgYnkgdHJhcHMKUEVSU0lTVEVOVF9UTVBESVI9IiR7VE1QRElSOi0vdG1wfS9iYXNoLWZyYW1ld29yayIKZXhwb3J0IFBFUlNJU1RFTlRfVE1QRElSCm1rZGlyIC1wICIke1BFUlNJU1RFTlRfVE1QRElSfSIKCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNApUTVBESVI9IiQobWt0ZW1wIC1kIC1wICIke1BFUlNJU1RFTlRfVE1QRElSOi0vdG1wfSIgLXQgYmFzaC1mcmFtZXdvcmstJCQtWFhYWFhYKSIKZXhwb3J0IFRNUERJUgoKIyB0ZW1wIGRpciBjbGVhbmluZwojIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIzMTcKY2xlYW5PbkV4aXQoKSB7CiAgaWYgW1sgIiR7S0VFUF9URU1QX0ZJTEVTOi0wfSIgPSAiMSIgXV07IHRoZW4KICAgIExvZzo6ZGlzcGxheUluZm8gIktFRVBfVEVNUF9GSUxFUz0xIHRlbXAgZmlsZXMga2VwdCBoZXJlICcke1RNUERJUn0nIgogIGVsaWYgW1sgLW4gIiR7VE1QRElSK3h4eH0iIF1dOyB0aGVuCiAgICBMb2c6OmRpc3BsYXlEZWJ1ZyAiS0VFUF9URU1QX0ZJTEVTPTAgcmVtb3ZpbmcgdGVtcCBmaWxlcyAnJHtUTVBESVJ9JyIKICAgIHJtIC1SZiAiJHtUTVBESVI6LS90bXAvZmFrZX0iID4vZGV2L251bGwgMj4mMQogIGZpCn0KdHJhcCBjbGVhbk9uRXhpdCBFWElUIEhVUCBRVUlUIEFCUlQgVEVSTQoKIyBWQVJfTUFJTl9GVU5DVElPTl9WQVJfTkFNRT1kYlF1ZXJ5QWxsRGF0YWJhc2VzRmFjYWRlCgojIEBkZXNjcmlwdGlvbiB1c2VkIHRvIGV4ZWN1dGUgZ2l2ZW4gcXVlcnkgd2hlbiB1c2luZwojIGRiU2NyaXB0QWxsRGF0YWJhc2VzCiMgQGFyZyAkMSBkc246U3RyaW5nCiMgQGFyZyAkMiBkYjpTdHJpbmcKIyBAZW52IHF1ZXJ5IFN0cmluZwojIEBlbnYgb3B0aW9uU2VwYXJhdG9yIFN0cmluZwojIEByZXF1aXJlIExpbnV4OjpyZXF1aXJlRXhlY3V0ZWRBc1VzZXIKRGI6OnF1ZXJ5T25lRGF0YWJhc2UoKSB7CiAgIyBxdWVyeSBhbmQgb3B0aW9uU2VwYXJhdG9yIGFyZSBwYXNzZWQgdmlhIGV4cG9ydAogIGxvY2FsIGRzbj0iJDEiCiAgbG9jYWwgZGI9IiQyIgoKICBsb2NhbCAtQSBkYkluc3RhbmNlCiAgRGF0YWJhc2U6Om5ld0luc3RhbmNlIGRiSW5zdGFuY2UgIiR7ZHNufSIKICBEYXRhYmFzZTo6c2V0UXVlcnlPcHRpb25zIGRiSW5zdGFuY2UgIiR7ZGJJbnN0YW5jZVtRVUVSWV9PUFRJT05TXX0gLS1jb25uZWN0LXRpbWVvdXQ9NSIKCiAgIyBpZGVudGlmeSBjb2x1bW5zIGhlYWRlcgogIGVjaG8gLW4gIkBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAIgogIERhdGFiYXNlOjpza2lwQ29sdW1uTmFtZXMgZGJJbnN0YW5jZSAwCgogICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjE1NAogIGlmICEgRGF0YWJhc2U6OnF1ZXJ5IGRiSW5zdGFuY2UgIiR7cXVlcnl9IiAiJHtkYn0iIHwgc2VkICJzL1x0LyR7b3B0aW9uU2VwYXJhdG9yfS9nIjsgdGhlbgogICAgTG9nOjpmYXRhbCAiZGF0YWJhc2UgJHtkYn0gZXJyb3IiIDE+JjIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBMb2cgbmFtZXNwYWNlIHByb3ZpZGVzIDIga2luZCBvZiBmdW5jdGlvbnMKIyAtIExvZzo6ZGlzcGxheSogYWxsb3dzIHRvIGRpc3BsYXkgZ2l2ZW4gbWVzc2FnZSB3aXRoCiMgICBnaXZlbiBkaXNwbGF5IGxldmVsCiMgLSBMb2c6OmxvZyogYWxsb3dzIHRvIGxvZyBnaXZlbiBtZXNzYWdlIHdpdGgKIyAgIGdpdmVuIGxvZyBsZXZlbAojIExvZzo6ZGlzcGxheSogZnVuY3Rpb25zIGF1dG9tYXRpY2FsbHkgbG9nIHRoZSBtZXNzYWdlIHRvbwojIEBzZWUgRW52OjpyZXF1aXJlTG9hZCB0byBsb2FkIHRoZSBkaXNwbGF5IGFuZCBsb2cgbGV2ZWwgZnJvbSAuZW52IGZpbGUKCiMgQGRlc2NyaXB0aW9uIGxvZyBsZXZlbCBvZmYKZXhwb3J0IF9fTEVWRUxfT0ZGPTAKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIGVycm9yCmV4cG9ydCBfX0xFVkVMX0VSUk9SPTEKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIHdhcm5pbmcKZXhwb3J0IF9fTEVWRUxfV0FSTklORz0yCiMgQGRlc2NyaXB0aW9uIGxvZyBsZXZlbCBpbmZvCmV4cG9ydCBfX0xFVkVMX0lORk89MwojIEBkZXNjcmlwdGlvbiBsb2cgbGV2ZWwgc3VjY2VzcwpleHBvcnQgX19MRVZFTF9TVUNDRVNTPTMKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIGRlYnVnCmV4cG9ydCBfX0xFVkVMX0RFQlVHPTQKCiMgQGRlc2NyaXB0aW9uIHZlcmJvc2UgbGV2ZWwgb2ZmCmV4cG9ydCBfX1ZFUkJPU0VfTEVWRUxfT0ZGPTAKIyBAZGVzY3JpcHRpb24gdmVyYm9zZSBsZXZlbCBpbmZvCmV4cG9ydCBfX1ZFUkJPU0VfTEVWRUxfSU5GTz0xCiMgQGRlc2NyaXB0aW9uIHZlcmJvc2UgbGV2ZWwgaW5mbwpleHBvcnQgX19WRVJCT1NFX0xFVkVMX0RFQlVHPTIKIyBAZGVzY3JpcHRpb24gdmVyYm9zZSBsZXZlbCBpbmZvCmV4cG9ydCBfX1ZFUkJPU0VfTEVWRUxfVFJBQ0U9MwoKIyBAZGVzY3JpcHRpb24gRGlzcGxheSBtZXNzYWdlIHVzaW5nIGRlYnVnIGNvbG9yIChncmV5KQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlEZWJ1ZygpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUwgPj0gX19MRVZFTF9ERUJVRykpOyB0aGVuCiAgICBlY2hvIC1lICIke19fREVCVUdfQ09MT1J9REVCVUcgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBmaQogIExvZzo6bG9nRGVidWcgIiQxIgp9CgojIEBkZXNjcmlwdGlvbiBEaXNwbGF5IG1lc3NhZ2UgdXNpbmcgaW5mbyBjb2xvciAoYmcgbGlnaHQgYmx1ZS9mZyB3aGl0ZSkKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpkaXNwbGF5SW5mbygpIHsKICBsb2NhbCB0eXBlPSIkezI6LUlORk99IgogIGlmICgoQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCA+PSBfX0xFVkVMX0lORk8pKTsgdGhlbgogICAgZWNobyAtZSAiJHtfX0lORk9fQ09MT1J9JHt0eXBlfSAgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBmaQogIExvZzo6bG9nSW5mbyAiJDEiICIke3R5cGV9Igp9CgojIEBkZXNjcmlwdGlvbiBlbnN1cmUgQ09NTUFORF9CSU5fRElSIGVudiB2YXIgaXMgc2V0CiMgYW5kIFBBVEggY29ycmVjdGx5IHByZXBhcmVkCiMgQG5vYXJncwojIEBzZXQgQ09NTUFORF9CSU5fRElSIHN0cmluZyB0aGUgZGlyZWN0b3J5IHdoZXJlIHRvIGZpbmQgdGhpcyBjb21tYW5kCiMgQHNldCBQQVRIIHN0cmluZyBhZGQgZGlyZWN0b3J5IHdoZXJlIHRvIGZpbmQgdGhpcyBjb21tYW5kIGJpbmFyeQpDb21waWxlcjo6RmFjYWRlOjpyZXF1aXJlQ29tbWFuZEJpbkRpcigpIHsKICBDT01NQU5EX0JJTl9ESVI9IiR7Q1VSUkVOVF9ESVJ9IgogIEVudjo6cGF0aFByZXBlbmQgIiR7Q09NTUFORF9CSU5fRElSfSIKfQoKIyBAZGVzY3JpcHRpb24gY3JlYXRlIGEgbmV3IGRiIGluc3RhbmNlCiMgUmV0dXJucyBpbW1lZGlhdGVseSBpZiB0aGUgaW5zdGFuY2UgaXMgYWxyZWFkeSBpbml0aWFsaXplZAojCiMgQGFyZyAkMSBpbnN0YW5jZU5ld0luc3RhbmNlOiZNYXA8U3RyaW5nLFN0cmluZz4gKHBhc3NlZCBieSByZWZlcmVuY2UpIGRhdGFiYXNlIGluc3RhbmNlIHRvIHVzZQojIEBhcmcgJDIgZHNuOlN0cmluZyBkc24gcHJvZmlsZSAtIGxvYWQgdGhlIGRzbi5lbnYgcHJvZmlsZSBkZWR1Y2VkIHVzaW5nIHJ1bGVzIGRlZmluZWQgaW4gQ29uZjo6Z2V0QWJzb2x1dGVGaWxlCiMKIyBAZXhhbXBsZQojICAgZGVjbGFyZSAtQWd4IGRiSW5zdGFuY2UKIyAgIERhdGFiYXNlOjpuZXdJbnN0YW5jZSBkYkluc3RhbmNlICJkZWZhdWx0LmxvY2FsIgojCiMgQGV4aXRjb2RlIDEgaWYgZG5zIGZpbGUgbm90IGFibGUgdG8gbG9hZGVkCkRhdGFiYXNlOjpuZXdJbnN0YW5jZSgpIHsKICBsb2NhbCAtbiBpbnN0YW5jZU5ld0luc3RhbmNlPSQxCiAgbG9jYWwgZHNuPSIkMiIKICBsb2NhbCBEU05fRklMRQoKICBpZiBbWyAtdiBpbnN0YW5jZU5ld0luc3RhbmNlWydJTklUSUFMSVpFRCddICYmICIke2luc3RhbmNlTmV3SW5zdGFuY2VbJ0lOSVRJQUxJWkVEJ106LTB9IiA9PSAiMSIgXV07IHRoZW4KICAgIHJldHVybgogIGZpCgogICMgZmluYWwgYXV0aCBmaWxlIGdlbmVyYXRlZCBmcm9tIGRucyBmaWxlCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnQVVUSF9GSUxFJ109IiIKICBpbnN0YW5jZU5ld0luc3RhbmNlWydEU05fRklMRSddPSIiCgogICMgY2hlY2sgZHNuIGZpbGUKICBEU05fRklMRT0iJChDb25mOjpnZXRBYnNvbHV0ZUZpbGUgImRzbiIgIiR7ZHNufSIgImVudiIpIiB8fCByZXR1cm4gMQogIERhdGFiYXNlOjpjaGVja0RzbkZpbGUgIiR7RFNOX0ZJTEV9IiB8fCByZXR1cm4gMQogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0RTTl9GSUxFJ109IiR7RFNOX0ZJTEV9IgoKICAjIHNoZWxsY2hlY2sgc291cmNlPS9zcmMvRGF0YWJhc2UvdGVzdHNEYXRhL2Rzbl92YWxpZC5lbnYKICBzb3VyY2UgIiR7aW5zdGFuY2VOZXdJbnN0YW5jZVsnRFNOX0ZJTEUnXX0iCgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1VTRVInXT0iJHtVU0VSfSIKICBpbnN0YW5jZU5ld0luc3RhbmNlWydQQVNTV09SRCddPSIke1BBU1NXT1JEfSIKICBpbnN0YW5jZU5ld0luc3RhbmNlWydIT1NUTkFNRSddPSIke0hPU1ROQU1FfSIKICBpbnN0YW5jZU5ld0luc3RhbmNlWydQT1JUJ109IiR7UE9SVH0iCgogICMgZ2VuZXJhdGUgYXV0aEZpbGUgZm9yIGVhc3kgYXV0aGVudGljYXRpb24KICBpbnN0YW5jZU5ld0luc3RhbmNlWydBVVRIX0ZJTEUnXT0kKG1rdGVtcCAtcCAiJHtUTVBESVI6LS90bXB9IiAtdCAibXlzcWwuWFhYWFhYWFhYWFhYIikKICAoCiAgICBlY2hvICJbY2xpZW50XSIKICAgIGVjaG8gInVzZXIgPSAke1VTRVJ9IgogICAgZWNobyAicGFzc3dvcmQgPSAke1BBU1NXT1JEfSIKICAgIGVjaG8gImhvc3QgPSAke0hPU1ROQU1FfSIKICAgIGVjaG8gInBvcnQgPSAke1BPUlR9IgogICkgPiIke2luc3RhbmNlTmV3SW5zdGFuY2VbJ0FVVEhfRklMRSddfSIKCiAgIyBzb21lIG9mIHRob3NlIHZhbHVlcyBjYW4gYmUgb3ZlcnJpZGRlbiB1c2luZyB0aGUgZHNuIGZpbGUKICAjIFNLSVBfQ09MVU1OX05BTUVTIGVuYWJsZWQgYnkgZGVmYXVsdAogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1NLSVBfQ09MVU1OX05BTUVTJ109IiR7U0tJUF9DT0xVTU5fTkFNRVM6LTF9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1NTTF9PUFRJT05TJ109IiR7TVlTUUxfU1NMX09QVElPTlM6LS0tc3NsLW1vZGU9RElTQUJMRUR9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1FVRVJZX09QVElPTlMnXT0iJHtNWVNRTF9RVUVSWV9PUFRJT05TOi0tLWJhdGNoIC0tcmF3IC0tZGVmYXVsdC1jaGFyYWN0ZXItc2V0PXV0Zjh9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0RVTVBfT1BUSU9OUyddPSIke01ZU1FMX0RVTVBfT1BUSU9OUzotLS1kZWZhdWx0LWNoYXJhY3Rlci1zZXQ9dXRmOCAtLWNvbXByZXNzIC0taGV4LWJsb2IgLS1yb3V0aW5lcyAtLXRyaWdnZXJzIC0tc2luZ2xlLXRyYW5zYWN0aW9uIC0tc2V0LWd0aWQtcHVyZ2VkPU9GRiAtLWNvbHVtbi1zdGF0aXN0aWNzPTAgJHtpbnN0YW5jZU5ld0luc3RhbmNlWydTU0xfT1BUSU9OUyddfX0iCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnREJfSU1QT1JUX09QVElPTlMnXT0iJHtEQl9JTVBPUlRfT1BUSU9OUzotLS1jb25uZWN0LXRpbWVvdXQ9NSAtLWJhdGNoIC0tcmF3IC0tZGVmYXVsdC1jaGFyYWN0ZXItc2V0PXV0Zjh9IgoKICBpbnN0YW5jZU5ld0luc3RhbmNlWydJTklUSUFMSVpFRCddPTEKfQoKIyBAZGVzY3JpcHRpb24gbXlzcWwgcXVlcnkgb24gYSBnaXZlbiBkYgojIEB3YXJuaW5nIGNvdWxkIHVzZSBRVUVSWV9PUFRJT05TIHZhcmlhYmxlIGZyb20gZHNuIGlmIGRlZmluZWQKIyBAZXhhbXBsZQojICAgY2F0IGZpbGUuc3FsIHwgRGF0YWJhc2U6OnF1ZXJ5IC4uLgojIEBhcmcgJDEgaW5zdGFuY2VRdWVyeTomTWFwPFN0cmluZyxTdHJpbmc+IChwYXNzZWQgYnkgcmVmZXJlbmNlKSBkYXRhYmFzZSBpbnN0YW5jZSB0byB1c2UKIyBAYXJnICQyIHNxbFF1ZXJ5OlN0cmluZyAob3B0aW9uYWwpIHNxbCBxdWVyeSBvciBzcWwgZmlsZSB0byBleGVjdXRlLiBpZiBub3QgcHJvdmlkZWQgb3IgZW1wdHksIHRoZSBjb21tYW5kIGNhbiBiZSBwaXBlZAojIEBhcmcgJDMgZGJOYW1lOlN0cmluZyAob3B0aW9uYWwpIHRoZSBkYiBuYW1lCiMKIyBAZXhpdGNvZGUgbXlzcWwgY29tbWFuZCBzdGF0dXMgY29kZQpEYXRhYmFzZTo6cXVlcnkoKSB7CiAgbG9jYWwgLW4gaW5zdGFuY2VRdWVyeT0kMQogIGxvY2FsIC1hIG15c3FsQ29tbWFuZD0oKQogIGxvY2FsIC1hIHF1ZXJ5T3B0aW9ucwoKICBteXNxbENvbW1hbmQrPShteXNxbCkKICBteXNxbENvbW1hbmQrPSgiLS1kZWZhdWx0cy1leHRyYS1maWxlPSR7aW5zdGFuY2VRdWVyeVsnQVVUSF9GSUxFJ119IikKICBJRlM9JyAnIHJlYWQgLXIgLWEgcXVlcnlPcHRpb25zIDw8PCIke2luc3RhbmNlUXVlcnlbJ1FVRVJZX09QVElPTlMnXX0iCiAgbXlzcWxDb21tYW5kKz0oIiR7cXVlcnlPcHRpb25zW0BdfSIpCiAgaWYgW1sgIiR7aW5zdGFuY2VRdWVyeVsnU0tJUF9DT0xVTU5fTkFNRVMnXX0iID0gIjEiIF1dOyB0aGVuCiAgICBteXNxbENvbW1hbmQrPSgiLXMiICItLXNraXAtY29sdW1uLW5hbWVzIikKICBmaQogICMgYWRkIG9wdGlvbmFsIGRiIG5hbWUKICBpZiBbWyAtbiAiJHszK3h9IiBdXTsgdGhlbgogICAgbXlzcWxDb21tYW5kKz0oIiQzIikKICBmaQogICMgYWRkIG9wdGlvbmFsIHNxbCBxdWVyeQogIGlmIFtbIC1uICIkezIreH0iICYmIC1uICIkMiIgJiYgISAtZiAiJDIiIF1dOyB0aGVuCiAgICBteXNxbENvbW1hbmQrPSgiLWUiKQogICAgbXlzcWxDb21tYW5kKz0oIiQyIikKICBmaQogIExvZzo6ZGlzcGxheURlYnVnICIkKHByaW50ZiAiZXhlY3V0ZSBjb21tYW5kOiAnJXMnIiAiJHtteXNxbENvbW1hbmRbKl19IikiCgogIGlmIFtbIC1mICIkMiIgXV07IHRoZW4KICAgICIke215c3FsQ29tbWFuZFtAXX0iIDwiJDIiCiAgZWxzZQogICAgIiR7bXlzcWxDb21tYW5kW0BdfSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBzZXQgdGhlIGdlbmVyYWwgb3B0aW9ucyB0byB1c2Ugb24gbXlzcWwgY29tbWFuZCB0byBxdWVyeSB0aGUgZGF0YWJhc2UKIyBEaWZmZXJzIHRoYW4gc2V0T3B0aW9ucyBpbiB0aGUgd2F5IHRoYXQgdGhlc2Ugb3B0aW9ucyBjb3VsZCBjaGFuZ2UgZWFjaCB0aW1lCiMKIyBAYXJnICQxIGluc3RhbmNlU2V0UXVlcnlPcHRpb25zOiZNYXA8U3RyaW5nLFN0cmluZz4gKHBhc3NlZCBieSByZWZlcmVuY2UpIGRhdGFiYXNlIGluc3RhbmNlIHRvIHVzZQojIEBhcmcgJDIgb3B0aW9uc0xpc3Q6U3RyaW5nIHF1ZXJ5IG9wdGlvbnMgbGlzdApEYXRhYmFzZTo6c2V0UXVlcnlPcHRpb25zKCkgewogIGxvY2FsIC1uIGluc3RhbmNlU2V0UXVlcnlPcHRpb25zPSQxCiAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0CiAgaW5zdGFuY2VTZXRRdWVyeU9wdGlvbnNbJ1FVRVJZX09QVElPTlMnXT0iJDIiCn0KCiMgQGRlc2NyaXB0aW9uIGJ5IGRlZmF1bHQgd2Ugc2tpcCB0aGUgY29sdW1uIG5hbWVzCiMgYnV0IHNvbWV0aW1lcyB3ZSBuZWVkIGNvbHVtbiBuYW1lcyB0byBkaXNwbGF5IHNvbWUgcmVzdWx0cwojIGRpc2FibGUgdGhpcyBvcHRpb24gdGVtcG9yYXJpbHkgYW5kIHRoZW4gcmVzdG9yZSBpdCB0byB0cnVlCiMKIyBAYXJnICQxIGluc3RhbmNlU2V0UXVlcnlPcHRpb25zOiZNYXA8U3RyaW5nLFN0cmluZz4gKHBhc3NlZCBieSByZWZlcmVuY2UpIGRhdGFiYXNlIGluc3RhbmNlIHRvIHVzZQojIEBhcmcgJDIgc2tpcENvbHVtbk5hbWVzOkJvb2xlYW4gMCB0byBkaXNhYmxlLCAxIHRvIGVuYWJsZSAoaGlkZSBjb2x1bW4gbmFtZXMpCkRhdGFiYXNlOjpza2lwQ29sdW1uTmFtZXMoKSB7CiAgbG9jYWwgLW4gaW5zdGFuY2VTa2lwQ29sdW1uTmFtZXM9JDEKICAjIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIwMzQKICBpbnN0YW5jZVNraXBDb2x1bW5OYW1lc1snU0tJUF9DT0xVTU5fTkFNRVMnXT0iJDIiCn0KCiMgQGRlc2NyaXB0aW9uIHByZXBlbmQgZGlyZWN0b3JpZXMgdG8gdGhlIFBBVEggZW52aXJvbm1lbnQgdmFyaWFibGUKIyBAYXJnICRAIGFyZ3M6U3RyaW5nW10gbGlzdCBvZiBkaXJlY3RvcmllcyB0byBwcmVwZW5kCiMgQHNldCBQQVRIIHVwZGF0ZSBQQVRIIHdpdGggdGhlIGRpcmVjdG9yaWVzIHByZXBlbmRlZApFbnY6OnBhdGhQcmVwZW5kKCkgewogIGxvY2FsIGFyZwogIGZvciBhcmcgaW4gIiRAIjsgZG8KICAgIGlmIFtbIC1kICIke2FyZ30iICYmICI6JHtQQVRIfToiICE9ICoiOiR7YXJnfToiKiBdXTsgdGhlbgogICAgICBQQVRIPSIkKHJlYWxwYXRoICIke2FyZ30iKToke1BBVEh9IgogICAgZmkKICBkb25lCn0KCiMgQGRlc2NyaXB0aW9uIERpc3BsYXkgbWVzc2FnZSB1c2luZyBlcnJvciBjb2xvciAocmVkKSBhbmQgZXhpdCBpbW1lZGlhdGVseSB3aXRoIGVycm9yIHN0YXR1cyAxCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6ZmF0YWwoKSB7CiAgZWNobyAtZSAiJHtfX0VSUk9SX0NPTE9SfUZBVEFMICAgLSAkezF9JHtfX1JFU0VUX0NPTE9SfSIgPiYyCiAgTG9nOjpsb2dGYXRhbCAiJDEiCiAgZXhpdCAxCn0KCiMgQGRlc2NyaXB0aW9uIGxvZyBtZXNzYWdlIHRvIGZpbGUKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpsb2dEZWJ1ZygpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+PSBfX0xFVkVMX0RFQlVHKSk7IHRoZW4KICAgIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1ERUJVR30iICIkMSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBsb2cgbWVzc2FnZSB0byBmaWxlCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6bG9nSW5mbygpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+PSBfX0xFVkVMX0lORk8pKTsgdGhlbgogICAgTG9nOjpsb2dNZXNzYWdlICIkezI6LUlORk99IiAiJDEiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gZW5zdXJlIHJ1bm5pbmcgdXNlciBpcyBub3Qgcm9vdAojIEBleGl0Y29kZSAxIGlmIGN1cnJlbnQgdXNlciBpcyByb290CiMgQHN0ZGVyciBkaWFnbm9zdGljcyBpbmZvcm1hdGlvbiBpcyBkaXNwbGF5ZWQKTGludXg6OnJlcXVpcmVFeGVjdXRlZEFzVXNlcigpIHsKICBpZiBbWyAiJChpZCAtdSkiID0gIjAiIF1dOyB0aGVuCiAgICBMb2c6OmZhdGFsICJ0aGlzIHNjcmlwdCBzaG91bGQgYmUgZXhlY3V0ZWQgYXMgbm9ybWFsIHVzZXIiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gZ2V0IGFic29sdXRlIGNvbmYgZmlsZSBmcm9tIHNwZWNpZmllZCBjb25mIGZvbGRlciBkZWR1Y2VkIHVzaW5nIHRoZXNlIHJ1bGVzCiMgICAqIGZyb20gYWJzb2x1dGUgZmlsZSAoaWdub3JlcyA8Y29uZkZvbGRlcj4gYW5kIDxleHRlbnNpb24+KQojICAgKiByZWxhdGl2ZSB0byB3aGVyZSBzY3JpcHQgaXMgZXhlY3V0ZWQgKGlnbm9yZXMgPGNvbmZGb2xkZXI+IGFuZCA8ZXh0ZW5zaW9uPikKIyAgICogZnJvbSBob21lLy5iYXNoLXRvb2xzLzxjb25mRm9sZGVyPgojICAgKiBmcm9tIGZyYW1ld29yayBjb25mLzxjb25mRm9sZGVyPgojCiMgQGFyZyAkMSBjb25mRm9sZGVyOlN0cmluZyB0aGUgZGlyZWN0b3J5IG5hbWUgKG5vdCB0aGUgcGF0aCkgdG8gbGlzdAojIEBhcmcgJDIgY29uZjpTdHJpbmcgZmlsZSB0byB1c2Ugd2l0aG91dCBleHRlbnNpb24KIyBAYXJnICQzIGV4dGVuc2lvbjpTdHJpbmcgdGhlIGV4dGVuc2lvbiAoLnNoIGJ5IGRlZmF1bHQpCiMKIyBAc3Rkb3V0IGFic29sdXRlIGNvbmYgZmlsZW5hbWUKIyBAZXhpdGNvZGUgMSBpZiBmaWxlIGlzIG5vdCBmb3VuZCBpbiBhbnkgbG9jYXRpb24KQ29uZjo6Z2V0QWJzb2x1dGVGaWxlKCkgewogIGxvY2FsIGNvbmZGb2xkZXI9IiQxIgogIGxvY2FsIGNvbmY9IiQyIgogIGxvY2FsIGV4dGVuc2lvbj0iJHszLS5zaH0iCiAgaWYgW1sgLW4gIiR7ZXh0ZW5zaW9ufSIgJiYgIiR7ZXh0ZW5zaW9uOjA6MX0iICE9ICIuIiBdXTsgdGhlbgogICAgZXh0ZW5zaW9uPSIuJHtleHRlbnNpb259IgogIGZpCgogIHRlc3RBYnMoKSB7CiAgICBsb2NhbCByZXN1bHQKICAgIHJlc3VsdD0iJChyZWFscGF0aCAtZSAiJDEiIDI+L2Rldi9udWxsKSIKICAgICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjE4MQogICAgaWYgW1sgIiQ/IiA9ICIwIiAmJiAtZiAiJHtyZXN1bHR9IiBdXTsgdGhlbgogICAgICBlY2hvICIke3Jlc3VsdH0iCiAgICAgIHJldHVybiAwCiAgICBmaQogICAgcmV0dXJuIDEKICB9CgogICMgY29uZiBpcyBhYnNvbHV0ZSBmaWxlIChpbmNsdWRpbmcgZXh0ZW5zaW9uKQogIHRlc3RBYnMgIiR7Y29uZkZvbGRlcn0ke2V4dGVuc2lvbn0iICYmIHJldHVybiAwCiAgIyBjb25mIGlzIGFic29sdXRlIGZpbGUKICB0ZXN0QWJzICIke2NvbmZGb2xkZXJ9IiAmJiByZXR1cm4gMAogICMgY29uZiBpcyBhYnNvbHV0ZSBmaWxlIChpbmNsdWRpbmcgZXh0ZW5zaW9uKQogIHRlc3RBYnMgIiR7Y29uZn0ke2V4dGVuc2lvbn0iICYmIHJldHVybiAwCiAgIyBjb25mIGlzIGFic29sdXRlIGZpbGUKICB0ZXN0QWJzICIke2NvbmZ9IiAmJiByZXR1cm4gMAoKICAjIHJlbGF0aXZlIHRvIHdoZXJlIHNjcmlwdCBpcyBleGVjdXRlZCAoaW5jbHVkaW5nIGV4dGVuc2lvbikKICBpZiBbWyAtbiAiJHtDVVJSRU5UX0RJUit4eHh9IiBdXTsgdGhlbgogICAgdGVzdEFicyAiJChGaWxlOjpjb25jYXRlbmF0ZVBhdGggIiR7Q1VSUkVOVF9ESVJ9IiAiJHtjb25mRm9sZGVyfSIpLyR7Y29uZn0ke2V4dGVuc2lvbn0iICYmIHJldHVybiAwCiAgZmkKICAjIGZyb20gaG9tZS8uYmFzaC10b29scy88Y29uZkZvbGRlcj4KICB0ZXN0QWJzICIkKEZpbGU6OmNvbmNhdGVuYXRlUGF0aCAiJHtIT01FfS8uYmFzaC10b29scyIgIiR7Y29uZkZvbGRlcn0iKS8ke2NvbmZ9JHtleHRlbnNpb259IiAmJiByZXR1cm4gMAoKICBpZiBbWyAtbiAiJHtGUkFNRVdPUktfUk9PVF9ESVIreHh4fSIgXV07IHRoZW4KICAgICMgZnJvbSBmcmFtZXdvcmsgY29uZi88Y29uZkZvbGRlcj4gKGluY2x1ZGluZyBleHRlbnNpb24pCiAgICB0ZXN0QWJzICIkKEZpbGU6OmNvbmNhdGVuYXRlUGF0aCAiJHtGUkFNRVdPUktfUk9PVF9ESVJ9L2NvbmYiICIke2NvbmZGb2xkZXJ9IikvJHtjb25mfSR7ZXh0ZW5zaW9ufSIgJiYgcmV0dXJuIDAKCiAgICAjIGZyb20gZnJhbWV3b3JrIGNvbmYvPGNvbmZGb2xkZXI+CiAgICB0ZXN0QWJzICIkKEZpbGU6OmNvbmNhdGVuYXRlUGF0aCAiJHtGUkFNRVdPUktfUk9PVF9ESVJ9L2NvbmYiICIke2NvbmZGb2xkZXJ9IikvJHtjb25mfSIgJiYgcmV0dXJuIDAKICBmaQoKICAjIGZpbGUgbm90IGZvdW5kCiAgTG9nOjpkaXNwbGF5RXJyb3IgImNvbmYgZmlsZSAnJHtjb25mfScgbm90IGZvdW5kIgoKICByZXR1cm4gMQp9CgojIEBkZXNjcmlwdGlvbiBjaGVjayBpZiBkc24gZmlsZSBoYXMgYWxsIHRoZSBtYW5kYXRvcnkgdmFyaWFibGVzIHNldAojIE1hbmRhdG9yeSB2YXJpYWJsZXMgYXJlOiBIT1NUTkFNRSwgVVNFUiwgUEFTU1dPUkQsIFBPUlQKIwojIEBhcmcgJDEgZHNuRmlsZU5hbWU6U3RyaW5nIGRzbiBhYnNvbHV0ZSBmaWxlbmFtZQojIEBzZXQgSE9TVE5BTUUgbG9hZGVkIGZyb20gZHNuIGZpbGUKIyBAc2V0IFBPUlQgbG9hZGVkIGZyb20gZHNuIGZpbGUKIyBAc2V0IFVTRVIgbG9hZGVkIGZyb20gZHNuIGZpbGUKIyBAc2V0IFBBU1NXT1JEIGxvYWRlZCBmcm9tIGRzbiBmaWxlCiMgQGV4aXRjb2RlIDAgb24gdmFsaWQgZmlsZQojIEBleGl0Y29kZSAxIGlmIG9uZSBvZiB0aGUgcHJvcGVydGllcyBvZiB0aGUgY29uZiBmaWxlIGlzIGludmFsaWQgb3IgaWYgZmlsZSBub3QgZm91bmQKIyBAc3RkZXJyIGxvZyBvdXRwdXQgaWYgZXJyb3IgZm91bmQgaW4gY29uZiBmaWxlCkRhdGFiYXNlOjpjaGVja0RzbkZpbGUoKSB7CiAgbG9jYWwgZHNuRmlsZU5hbWU9IiQxIgogIGlmIFtbICEgLWYgIiR7ZHNuRmlsZU5hbWV9IiBdXTsgdGhlbgogICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IG5vdCBmb3VuZCIKICAgIHJldHVybiAxCiAgZmkKCiAgKAogICAgdW5zZXQgSE9TVE5BTUUgUE9SVCBQQVNTV09SRCBVU0VSCiAgICAjIHNoZWxsY2hlY2sgc291cmNlPS9zcmMvRGF0YWJhc2UvdGVzdHNEYXRhL2Rzbl92YWxpZC5lbnYKICAgIHNvdXJjZSAiJHtkc25GaWxlTmFtZX0iCiAgICBpZiBbWyAteiAke0hPU1ROQU1FK3h9IF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheUVycm9yICJkc24gZmlsZSAke2RzbkZpbGVOYW1lfSA6IEhPU1ROQU1FIG5vdCBwcm92aWRlZCIKICAgICAgcmV0dXJuIDEKICAgIGZpCiAgICBpZiBbWyAteiAiJHtIT1NUTkFNRX0iIF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheVdhcm5pbmcgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogSE9TVE5BTUUgdmFsdWUgbm90IHByb3ZpZGVkIgogICAgZmkKICAgIGlmIFtbICIke0hPU1ROQU1FfSIgPSAibG9jYWxob3N0IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlXYXJuaW5nICJkc24gZmlsZSAke2RzbkZpbGVOYW1lfSA6IGNoZWNrIHRoYXQgSE9TVE5BTUUgc2hvdWxkIG5vdCBiZSAxMjcuMC4wLjEgaW5zdGVhZCBvZiBsb2NhbGhvc3QiCiAgICBmaQogICAgaWYgW1sgLXogIiR7UE9SVCt4fSIgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogUE9SVCBub3QgcHJvdmlkZWQiCiAgICAgIHJldHVybiAxCiAgICBmaQogICAgaWYgISBbWyAke1BPUlR9ID1+IF5bMC05XSskIF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheUVycm9yICJkc24gZmlsZSAke2RzbkZpbGVOYW1lfSA6IFBPUlQgaW52YWxpZCIKICAgICAgcmV0dXJuIDEKICAgIGZpCiAgICBpZiBbWyAteiAiJHtVU0VSK3h9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlFcnJvciAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gOiBVU0VSIG5vdCBwcm92aWRlZCIKICAgICAgcmV0dXJuIDEKICAgIGZpCiAgICBpZiBbWyAteiAiJHtQQVNTV09SRCt4fSIgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogUEFTU1dPUkQgbm90IHByb3ZpZGVkIgogICAgICByZXR1cm4gMQogICAgZmkKICApCn0KCiMgQGRlc2NyaXB0aW9uIGxvZyBtZXNzYWdlIHRvIGZpbGUKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpsb2dGYXRhbCgpIHsKICBMb2c6OmxvZ01lc3NhZ2UgIiR7MjotRkFUQUx9IiAiJDEiCn0KCiMgQGRlc2NyaXB0aW9uIEludGVybmFsOiBjb21tb24gbG9nIG1lc3NhZ2UKIyBAZXhhbXBsZSB0ZXh0CiMgICBbZGF0ZV18W2xldmVsTXNnXXxtZXNzYWdlCiMKIyBAZXhhbXBsZSB0ZXh0CiMgICAyMDIwLTAxLTE5IDE5OjIwOjIxfEVSUk9SICB8bG9nIGVycm9yCiMgICAyMDIwLTAxLTE5IDE5OjIwOjIxfFNLSVBQRUR8bG9nIHNraXBwZWQKIwojIEBhcmcgJDEgbGV2ZWxNc2c6U3RyaW5nIG1lc3NhZ2UncyBsZXZlbCBkZXNjcmlwdGlvbiAoZWc6IFNUQVRVUywgRVJST1IsIC4uLikKIyBAYXJnICQyIG1zZzpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQojIEBlbnYgQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEUgU3RyaW5nIGxvZyBmaWxlIHRvIHVzZSwgZG8gbm90aGluZyBpZiBlbXB0eQojIEBlbnYgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMIGludCBsb2cgbGV2ZWwgbG9nIG9ubHkgaWYgPiBPRkYgb3IgZmF0YWwgbWVzc2FnZXMKIyBAc3RkZXJyIGRpYWdub3N0aWNzIGluZm9ybWF0aW9uIGlzIGRpc3BsYXllZAojIEByZXF1aXJlIEVudjo6cmVxdWlyZUxvYWQKIyBAcmVxdWlyZSBMb2c6OnJlcXVpcmVMb2FkCkxvZzo6bG9nTWVzc2FnZSgpIHsKICBsb2NhbCBsZXZlbE1zZz0iJDEiCiAgbG9jYWwgbXNnPSIkMiIKICBsb2NhbCBkYXRlCgogIGlmIFtbIC1uICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSIgXV0gJiYgKChCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgPiBfX0xFVkVMX09GRikpOyB0aGVuCiAgICBkYXRlPSIkKGRhdGUgJyslWS0lbS0lZCAlSDolTTolUycpIgogICAgdG91Y2ggIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IgogICAgcHJpbnRmICIlc3wlN3N8JXNcbiIgIiR7ZGF0ZX0iICIke2xldmVsTXNnfSIgIiR7bXNnfSIgPj4iJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gY29uY2F0ZW5hdGUgMiBwYXRocyBhbmQgZW5zdXJlIHRoZSBwYXRoIGlzIGNvcnJlY3QgdXNpbmcgcmVhbHBhdGggLW0KIyBAYXJnICQxIGJhc2VQYXRoOlN0cmluZwojIEBhcmcgJDIgc3ViUGF0aDpTdHJpbmcKIyBAcmVxdWlyZSBMaW51eDo6cmVxdWlyZVJlYWxwYXRoQ29tbWFuZApGaWxlOjpjb25jYXRlbmF0ZVBhdGgoKSB7CiAgbG9jYWwgYmFzZVBhdGg9IiQxIgogIGxvY2FsIHN1YlBhdGg9IiQyIgogIGxvY2FsIGZ1bGxQYXRoPSIke2Jhc2VQYXRoOiske2Jhc2VQYXRofS99JHtzdWJQYXRofSIKCiAgcmVhbHBhdGggLW0gIiR7ZnVsbFBhdGh9IiAyPi9kZXYvbnVsbAp9CgojIEBkZXNjcmlwdGlvbiBEaXNwbGF5IG1lc3NhZ2UgdXNpbmcgZXJyb3IgY29sb3IgKHJlZCkKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpkaXNwbGF5RXJyb3IoKSB7CiAgaWYgKChCQVNIX0ZSQU1FV09SS19ESVNQTEFZX0xFVkVMID49IF9fTEVWRUxfRVJST1IpKTsgdGhlbgogICAgZWNobyAtZSAiJHtfX0VSUk9SX0NPTE9SfUVSUk9SICAgLSAkezF9JHtfX1JFU0VUX0NPTE9SfSIgPiYyCiAgZmkKICBMb2c6OmxvZ0Vycm9yICIkMSIKfQoKIyBAZGVzY3JpcHRpb24gRGlzcGxheSBtZXNzYWdlIHVzaW5nIHdhcm5pbmcgY29sb3IgKHllbGxvdykKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpkaXNwbGF5V2FybmluZygpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUwgPj0gX19MRVZFTF9XQVJOSU5HKSk7IHRoZW4KICAgIGVjaG8gLWUgIiR7X19XQVJOSU5HX0NPTE9SfVdBUk4gICAgLSAkezF9JHtfX1JFU0VUX0NPTE9SfSIgPiYyCiAgZmkKICBMb2c6OmxvZ1dhcm5pbmcgIiQxIgp9CgojIEBkZXNjcmlwdGlvbiBlbnN1cmUgZW52IGZpbGVzIGFyZSBsb2FkZWQKIyBAbm9hcmdzCiMgQGV4aXRjb2RlIDEgaWYgZ2V0T3JkZXJlZENvbmZGaWxlcyBmYWlscwojIEBleGl0Y29kZSAyIGlmIG9uZSBvZiBlbnYgZmlsZXMgZmFpbHMgdG8gbG9hZAojIEBzdGRlcnIgZGlhZ25vc3RpY3MgaW5mb3JtYXRpb24gaXMgZGlzcGxheWVkCkVudjo6cmVxdWlyZUxvYWQoKSB7CiAgbG9jYWwgY29uZmlnRmlsZXNTdHIKICBjb25maWdGaWxlc1N0cj0iJChFbnY6OmdldE9yZGVyZWRDb25mRmlsZXMpIiB8fCByZXR1cm4gMQoKICBsb2NhbCAtYSBjb25maWdGaWxlcwogIHJlYWRhcnJheSAtdCBjb25maWdGaWxlcyA8PDwiJHtjb25maWdGaWxlc1N0cn0iCgogICMgaWYgZW1wdHkgc3RyaW5nLCB0aGVyZSB3aWxsIGJlIG9uZSBlbGVtZW50CiAgaWYgKCgkeyNjb25maWdGaWxlc1tAXX0gPT0gMCkpIHx8IFtbIC16ICIke2NvbmZpZ0ZpbGVzU3RyfSIgXV07IHRoZW4KICAgICMgc2hvdWxkIG5vdCBoYXBwZW4sIGFzIHRoZXJlIGlzIGFsd2F5cyBkZWZhdWx0IGZpbGUKICAgIExvZzo6ZGlzcGxheVNraXBwZWQgIm5vIGVudiBmaWxlIHRvIGxvYWQiCiAgICByZXR1cm4gMAogIGZpCgogIEVudjo6bWVyZ2VDb25mRmlsZXMgIiR7Y29uZmlnRmlsZXNbQF19IiB8fCB7CiAgICBMb2c6OmRpc3BsYXlFcnJvciAid2hpbGUgbG9hZGluZyBjb25maWcgZmlsZXM6ICR7Y29uZmlnRmlsZXNbKl19IgogICAgcmV0dXJuIDIKICB9Cn0KCiMgQGRlc2NyaXB0aW9uIGFjdGl2YXRlIG9yIG5vdCBMb2c6OmRpc3BsYXkqIGFuZCBMb2c6OmxvZyogZnVuY3Rpb25zCiMgYmFzZWQgb24gQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCBhbmQgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMCiMgZW52aXJvbm1lbnQgdmFyaWFibGVzIGxvYWRlZCBieSBFbnY6OnJlcXVpcmVMb2FkCiMgdHJ5IHRvIGNyZWF0ZSBsb2cgZmlsZSBhbmQgcm90YXRlIGl0IGlmIG5lY2Vzc2FyeQojIEBub2FyZ3MKIyBAc2V0IEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCBpbnQgdG8gT0ZGIGxldmVsIGlmIEJBU0hfRlJBTUVXT1JLX0xPR19GSUxFIGlzIGVtcHR5IG9yIG5vdCB3cml0YWJsZQojIEBlbnYgQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCBpbnQKIyBAZW52IEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCBpbnQKIyBAZW52IEJBU0hfRlJBTUVXT1JLX0xPR19GSUxFIFN0cmluZwojIEBlbnYgQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEVfTUFYX1JPVEFUSU9OIGludCBkbyBsb2cgcm90YXRpb24gaWYgPiAwCiMgQGV4aXRjb2RlIDAgYWx3YXlzIHN1Y2Nlc3NmdWwKIyBAc3RkZXJyIGRpYWdub3N0aWNzIGluZm9ybWF0aW9uIGFib3V0IGxvZyBmaWxlIGlzIGRpc3BsYXllZAojIEByZXF1aXJlIEVudjo6cmVxdWlyZUxvYWQKIyBAcmVxdWlyZSBVSTo6cmVxdWlyZVRoZW1lCkxvZzo6cmVxdWlyZUxvYWQoKSB7CiAgaWYgW1sgLXogIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEU6LX0iIF1dOyB0aGVuCiAgICBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUw9JHtfX0xFVkVMX09GRn0KICAgIGV4cG9ydCBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwKICBmaQoKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+IF9fTEVWRUxfT0ZGKSk7IHRoZW4KICAgIGlmIFtbICEgLWYgIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IiBdXTsgdGhlbgogICAgICBpZgogICAgICAgICEgbWtkaXIgLXAgIiQoZGlybmFtZSAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iKSIgMj4vZGV2L251bGwgfHwKICAgICAgICAgICEgdG91Y2ggLS1uby1jcmVhdGUgIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IiAyPi9kZXYvbnVsbAogICAgICB0aGVuCiAgICAgICAgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMPSR7X19MRVZFTF9PRkZ9CiAgICAgICAgZWNobyAtZSAiJHtfX0VSUk9SX0NPTE9SfUVSUk9SICAgLSBGaWxlICR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IGlzIG5vdCB3cml0YWJsZSR7X19SRVNFVF9DT0xPUn0iID4mMgogICAgICBmaQogICAgZWxpZiBbWyAhIC13ICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSIgXV07IHRoZW4KICAgICAgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMPSR7X19MRVZFTF9PRkZ9CiAgICAgIGVjaG8gLWUgIiR7X19FUlJPUl9DT0xPUn1FUlJPUiAgIC0gRmlsZSAke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSBpcyBub3Qgd3JpdGFibGUke19fUkVTRVRfQ09MT1J9IiA+JjIKICAgIGZpCgogIGZpCgogIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMID4gX19MRVZFTF9PRkYpKTsgdGhlbgogICAgIyB3aWxsIGFsd2F5cyBiZSBjcmVhdGVkIGV2ZW4gaWYgbm90IGluIGluZm8gbGV2ZWwKICAgIExvZzo6bG9nTWVzc2FnZSAiSU5GTyIgIkxvZ2dpbmcgdG8gZmlsZSAke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSAtIExvZyBsZXZlbCAke0JBU0hfRlJBTUVXT1JLX0xPR19MRVZFTH0iCiAgICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19GSUxFX01BWF9ST1RBVElPTiA+IDApKTsgdGhlbgogICAgICBMb2c6OnJvdGF0ZSAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFX01BWF9ST1RBVElPTn0iCiAgICBmaQogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGdldCBsaXN0IG9mIGVudiBmaWxlcyB0byBsb2FkCiMgaW4gb3JkZXIgdG8gbWFrZSB0aGVtIGF2YWlsYWJsZSBmb3IgRW52OjpyZXF1aXJlTG9hZAojIEBlbnYgQkFTSF9GUkFNRVdPUktfRU5WX0ZJTEVTIFN0cmluZ1tdIGxpc3Qgb2YgZW52IGZpbGVzIHRoYXQgc2hvdWxkIGJlIGxvYWRlZAojIEBleGl0Y29kZSAxIGlmIG9uZSBvZiB0aGUgZW52IGZpbGUgY2Fubm90IGJlIGdlbmVyYXRlZAojIEBleGl0Y29kZSAyIGlmIG9uZSBvZiB0aGUgZW52IGZpbGUgaXMgbm90IGEgZmlsZSBvciByZWFkYWJsZQojIEBzdGRvdXQgdGhlIGVudiBmaWxlcyBhc2tlZCB0byBiZSBsb2FkZWQKIyBAc3RkZXJyIGRpYWdub3N0aWMgaW5mb3JtYXRpb24gb24gZmFpbHVyZQojIEBzZWUgaHR0cHM6Ly9naXRodWIuY29tL2ZjaGFzdGFuZXQvYmFzaC10b29scy1mcmFtZXdvcmsvYmxvYi9tYXN0ZXIvRnJhbWV3b3JrRG9jLm1kI2NvbmZpZ19maWxlX29yZGVyCkVudjo6Z2V0T3JkZXJlZENvbmZGaWxlcygpIHsKICBsb2NhbCAtYSBjb25maWdGaWxlcz0oKQoKICBpZiBbWyAtbiAiJHtCQVNIX0ZSQU1FV09SS19FTlZfRklMRVNbMF0rMX0iIF1dOyB0aGVuCiAgICAjIEJBU0hfRlJBTUVXT1JLX0VOVl9GSUxFUyBpcyBhbiBhcnJheQogICAgY29uZmlnRmlsZXMrPSgiJHtCQVNIX0ZSQU1FV09SS19FTlZfRklMRVNbQF19IikKICBmaQoKICBsb2NhbCBkZWZhdWx0RW52RmlsZQogIGRlZmF1bHRFbnZGaWxlPSIkKEVudjo6Y3JlYXRlRGVmYXVsdEVudkZpbGUpIiB8fCByZXR1cm4gMQogIGNvbmZpZ0ZpbGVzKz0oIiR7ZGVmYXVsdEVudkZpbGV9IikKCiAgbG9jYWwgZmlsZQogIGZvciBmaWxlIGluICIke2NvbmZpZ0ZpbGVzW0BdfSI7IGRvCiAgICBpZiBbWyAhIC1mICIke2ZpbGV9IiB8fCAhIC1yICIke2ZpbGV9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlFcnJvciAiT25lIG9mIHRoZSBjb25maWcgZmlsZSBpcyBub3QgYXZhaWxhYmxlICcke2ZpbGV9JyIKICAgICAgcmV0dXJuIDIKICAgIGZpCiAgICBlY2hvICIke2ZpbGV9IgogIGRvbmUKfQoKIyBAZGVzY3JpcHRpb24gbWVyZ2UgYW5kIGxvYWQgY29uZiBmaWxlcyBzcGVjaWZpZWQgYXMgYXJndW1lbnQKIyAtIGZpbGVzIGFyZSBjbGVhbmVkIGZyb20gYXkgY29tbWVudAojIC0gbWlzc2luZyBxdW90ZXMgYWZ0ZXIgcHJvcGVydHkgPSBzaWduIGFyZSBhZGRlZCBhdXRvbWF0aWNhbGx5CiMgLSBhdXRvbWF0aWMgcmVtb3ZlIG9mIGFsbCB3aGl0ZXNwYWNlIGJlZm9yZSBhbmQgYWZ0ZXIgZGVjbGFyYXRpb25zCiMgLSBiYXNoIGFycmF5cyBhcmUgbm90IHN1cHBvcnRlZAojIC0gaWYgYSB2YXJpYWJsZSBpcyBkZWNsYXJlZCBpbiBmaXJzdCBmaWxlIGFuZCBvdmVycmlkZGVuIGxhdGVyIG9uCiMgICBpbiB0aGUgc2FtZSBmaWxlIG9yIGluIHN1YnNlcXVlbnQgZmlsZXMsIHRob3NlIG92ZXJsb2FkcyB3aWxsIGJlCiMgICBpZ25vcmVkCiMgQHdhcm5pbmcgaWYgYW4gZXJyb3Igb2NjdXJzIHdoaWxlIGxvYWRpbmcgb25lIG9mIHRoZSBjb25maWcgZmlsZSwgZXhpdCBjb2RlIDMgYnV0IGVudmlyb25tZW50IGNvdWxkIGJlIHBhcnRpYWxseSBsb2FkZWQKIyBAYXJnICRAIGFyZ3M6U3RyaW5nW10gbGlzdCBvZiBjb25maWd1cmF0aW9uIGZpbGVzIHRvIGxvYWQgaW4gb3JkZXIKIyBAc2V0IGVudlZhcnMgU3RyaW5nIHdpbGwgc2V0IGluIGVudmlyb25tZW50IGFsbCB0aGUgdmFyaWFibGVzIHRoYXQgaGF2ZSBiZWVuIGRlY2xhcmVkIGluIHRoZSBjb25maWcgZmlsZXMKIyBAZW52IGVudlZhcnMgU3RyaW5nIHRoZSBlbnYgdmFyaWFibGVzIG9mIHRoZSBjdXJyZW50IHNjcmlwdCBjb3VsZCBiZSB1c2VkIHRvIGludGVycHJldCB2YXJpYWJsZXMgZHVyaW5nIGNvbmZpZyBmaWxlcyBwYXJzaW5nCiMgQGV4aXRjb2RlIDAgaWYgbm8gY29uZmlnIGZpbGVzIHByb3ZpZGVkIG9yIGxvYWQgY29tcGxldGVkIHN1Y2Nlc3NmdWxseQojIEBleGl0Y29kZSAxIGlmIGVycm9yIG9jY3VycmVkIGR1cmluZyBwYXJzaW5nIHRoZSBjb25maWcgZmlsZXMgKGZpbGUgbm90IGZvdW5kLCBncmVwLCBhd2sgb3Igc2VkIGVycm9yKQojIEBleGl0Y29kZSAyIGlmIHRlbXBvcmFyeSBmaWxlIGNhbm5vdCBiZSBjcmVhdGVkCiMgQGV4aXRjb2RlIDMgaWYgYW4gZXJyb3Igb2NjdXJyZWQgZHVyaW5nIGNvbmZpZyBmaWxlIHNvdXJjaW5nCiMgQHN0ZGVyciBkaWFnbm9zdGljcyBpbmZvcm1hdGlvbiBpcyBkaXNwbGF5ZWQKIyBAc2VlIGxhcmdlbHkgaW5zcGlyZWQgYnV0IG1vZGlmaWVkIGZyb20gaHR0cHM6Ly9vcGVuc291cmNlLmNvbS9hcnRpY2xlLzIxLzUvcHJvY2Vzc2luZy1jb25maWd1cmF0aW9uLWZpbGVzLXNoZWxsCkVudjo6bWVyZ2VDb25mRmlsZXMoKSB7CiAgbG9jYWwgLWEgY29uZmlnRmlsZUxpc3Q9KCIkQCIpCgogIGlmICgoJHsjY29uZmlnRmlsZUxpc3RbQF19ID09IDApKTsgdGhlbgogICAgcmV0dXJuIDAKICBmaQoKICBsb2NhbCBjb21iaW5lZENvbmZpZ0ZpbGUKICBjb21iaW5lZENvbmZpZ0ZpbGU9IiQoRnJhbWV3b3JrOjpjcmVhdGVUZW1wRmlsZSAibWVyZ2VDb25mRmlsZXMiKSIgfHwgcmV0dXJuIDIKCiAgKAogICAgIyByZW1vdmVzIGFueSB0cmFpbGluZyB3aGl0ZXNwYWNlIGZyb20gZWFjaCBmaWxlLCBpZiBhbnkKICAgICMgdGhpcyBpcyBhYnNvbHV0ZWx5IHJlcXVpcmVkIHdoZW4gaW1wb3J0aW5nIGludG8gQ29uZmlnTWFwcwogICAgIyBwdXQgcXVvdGVzIGFyb3VuZCB2YWx1ZXMKICAgIHNlZCAtRSAtZSAkJ3MvXHMqJC8vIDsgL14kL2QgOyAvXiMuKiQvZCA7IHMvPShbXiJcJ10uKikkLz0iXFwxIi8nICIke2NvbmZpZ0ZpbGVMaXN0W0BdfSIgfAogICAgICAjIHJlbW92ZSBhbGwgY29tbWVudCBsaW5lcwogICAgICBGaWx0ZXJzOjpjb21tZW50TGluZXMgfAogICAgICAjIGl0ZXJhdGVzIG92ZXIgZWFjaCBmaWxlIGFuZCBwcmludHMgKGRlZmF1bHQgYXdrIGJlaGF2aW9yKQogICAgICAjIGVhY2ggdW5pcXVlIGxpbmU7IG9ubHkgdGFrZXMgZmlyc3QgdmFsdWUgYW5kIGlnbm9yZXMgZHVwbGljYXRlcwogICAgICBhd2sgLUY9ICchbGluZVskMV0rKycKICApID4iJHtjb21iaW5lZENvbmZpZ0ZpbGV9IiB8fCByZXR1cm4gMQoKICAjIGhhdmUgdG8gZXhwb3J0IGV2ZXJ5dGhpbmcsIGFuZCBzb3VyY2UgaXQgdHdpY2U6CiAgIyAxKSBmaXJzdCBzb3VyY2UgaXMgdG8gcmVhbGl6ZSB2YXJpYWJsZXMKICAjIDIpIHNlY29uZCB0aW1lIGlzIHRvIHJlYWxpemUgcmVmZXJlbmNlcwogIHNldCAtbyBhbGxleHBvcnQKICAjIHNoZWxsY2hlY2sgc291cmNlPS5mcmFtZXdvcmstY29uZmlnCiAgc291cmNlICIke2NvbWJpbmVkQ29uZmlnRmlsZX0iIHx8IHJldHVybiAzCiAgIyBzaGVsbGNoZWNrIHNvdXJjZT0uZnJhbWV3b3JrLWNvbmZpZwogIHNvdXJjZSAiJHtjb21iaW5lZENvbmZpZ0ZpbGV9IiB8fCByZXR1cm4gMwogIHNldCArbyBhbGxleHBvcnQKfQoKIyBAZGVzY3JpcHRpb24gRGlzcGxheSBtZXNzYWdlIHVzaW5nIHNraXAgY29sb3IgKHllbGxvdykKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpkaXNwbGF5U2tpcHBlZCgpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUwgPj0gX19MRVZFTF9JTkZPKSk7IHRoZW4KICAgIGVjaG8gLWUgIiR7X19TS0lQUEVEX0NPTE9SfVNLSVBQRUQgLSAkezF9JHtfX1JFU0VUX0NPTE9SfSIgPiYyCiAgZmkKICBMb2c6OmxvZ1NraXBwZWQgIiQxIgp9CgojIEBkZXNjcmlwdGlvbiBsb2cgbWVzc2FnZSB0byBmaWxlCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6bG9nRXJyb3IoKSB7CiAgaWYgKChCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgPj0gX19MRVZFTF9FUlJPUikpOyB0aGVuCiAgICBMb2c6OmxvZ01lc3NhZ2UgIiR7MjotRVJST1J9IiAiJDEiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gbG9nIG1lc3NhZ2UgdG8gZmlsZQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmxvZ1dhcm5pbmcoKSB7CiAgaWYgKChCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgPj0gX19MRVZFTF9XQVJOSU5HKSk7IHRoZW4KICAgIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1XQVJOSU5HfSIgIiQxIgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIFRvIGJlIGNhbGxlZCBiZWZvcmUgbG9nZ2luZyBpbiB0aGUgbG9nIGZpbGUKIyBAYXJnICQxIGZpbGU6c3RyaW5nIGxvZyBmaWxlIG5hbWUKIyBAYXJnICQyIG1heExvZ0ZpbGVzQ291bnQ6aW50IG1heGltdW0gbnVtYmVyIG9mIGxvZyBmaWxlcwpMb2c6OnJvdGF0ZSgpIHsKICBsb2NhbCBmaWxlPSIkMSIKICBsb2NhbCBtYXhMb2dGaWxlc0NvdW50PSIkezI6LTV9IgoKICBpZiBbWyAhIC1mICIke2ZpbGV9IiBdXTsgdGhlbgogICAgTG9nOjpkaXNwbGF5U2tpcHBlZCAiTG9nIGZpbGUgJHtmaWxlfSBkb2Vzbid0IGV4aXN0IHlldCIKICAgIHJldHVybiAwCiAgZmkKICBmb3IgaSBpbiAkKHNlcSAkKChtYXhMb2dGaWxlc0NvdW50IC0gMSkpIC0xIDEpOyBkbwogICAgTG9nOjpkaXNwbGF5SW5mbyAiTG9nIHJvdGF0aW9uICR7ZmlsZX0uJHtpfSB0byAke2ZpbGV9LiQoKGkgKyAxKSkiCiAgICBtdiAiJHtmaWxlfS4ieyIke2l9IiwiJCgoaSArIDEpKSJ9ICY+L2Rldi9udWxsIHx8IHRydWUKICBkb25lCiAgaWYgY3AgIiR7ZmlsZX0iICIke2ZpbGV9LjEiICY+L2Rldi9udWxsOyB0aGVuCiAgICBlY2hvID4iJHtmaWxlfSIgIyByZXNldCBsb2cgZmlsZQogICAgTG9nOjpkaXNwbGF5SW5mbyAiTG9nIHJvdGF0aW9uICR7ZmlsZX0gdG8gJHtmaWxlfS4xIgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGVuc3VyZSBjb21tYW5kIHJlYWxwYXRoIGlzIGF2YWlsYWJsZQojIEBleGl0Y29kZSAxIGlmIHJlYWxwYXRoIGNvbW1hbmQgbm90IGF2YWlsYWJsZQojIEBzdGRlcnIgZGlhZ25vc3RpY3MgaW5mb3JtYXRpb24gaXMgZGlzcGxheWVkCkxpbnV4OjpyZXF1aXJlUmVhbHBhdGhDb21tYW5kKCkgewogIEFzc2VydDo6Y29tbWFuZEV4aXN0cyByZWFscGF0aAp9CgojIEBkZXNjcmlwdGlvbiBsb2FkIGNvbG9yIHRoZW1lCiMgQG5vYXJncwojIEBlbnYgQkFTSF9GUkFNRVdPUktfVEhFTUUgU3RyaW5nIHRoZW1lIHRvIHVzZQojIEBleGl0Y29kZSAwIGFsd2F5cyBzdWNjZXNzZnVsClVJOjpyZXF1aXJlVGhlbWUoKSB7CiAgVUk6OnRoZW1lICIke0JBU0hfRlJBTUVXT1JLX1RIRU1FLWRlZmF1bHR9Igp9CgojIEBkZXNjcmlwdGlvbiBjaGVjayBpZiBjb21tYW5kIHNwZWNpZmllZCBleGlzdHMgb3IgcmV0dXJuIDEKIyB3aXRoIGVycm9yIGFuZCBtZXNzYWdlIGlmIG5vdAojCiMgQGFyZyAkMSBjb21tYW5kTmFtZTpTdHJpbmcgb24gd2hpY2ggZXhpc3RlbmNlIG11c3QgYmUgY2hlY2tlZAojIEBhcmcgJDIgaGVscElmTm90RXhpc3RzOlN0cmluZyBhIGhlbHAgY29tbWFuZCB0byBkaXNwbGF5IGlmIHRoZSBjb21tYW5kIGRvZXMgbm90IGV4aXN0CiMKIyBAZXhpdGNvZGUgMSBpZiB0aGUgY29tbWFuZCBzcGVjaWZpZWQgZG9lcyBub3QgZXhpc3QKIyBAc3RkZXJyIGRpYWdub3N0aWMgaW5mb3JtYXRpb24gKyBoZWxwIGlmIHNlY29uZCBhcmd1bWVudCBpcyBwcm92aWRlZApBc3NlcnQ6OmNvbW1hbmRFeGlzdHMoKSB7CiAgbG9jYWwgY29tbWFuZE5hbWU9IiQxIgogIGxvY2FsIGhlbHBJZk5vdEV4aXN0cz0iJDIiCgogICIke0JBU0hfRlJBTUVXT1JLX0NPTU1BTkQ6LWNvbW1hbmR9IiAtdiAiJHtjb21tYW5kTmFtZX0iID4vZGV2L251bGwgMj4vZGV2L251bGwgfHwgewogICAgTG9nOjpkaXNwbGF5RXJyb3IgIiR7Y29tbWFuZE5hbWV9IGlzIG5vdCBpbnN0YWxsZWQsIHBsZWFzZSBpbnN0YWxsIGl0IgogICAgaWYgW1sgLW4gIiR7aGVscElmTm90RXhpc3RzfSIgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5SW5mbyAiJHtoZWxwSWZOb3RFeGlzdHN9IgogICAgZmkKICAgIHJldHVybiAxCiAgfQogIHJldHVybiAwCn0KCiMgQGRlc2NyaXB0aW9uIGRlZmF1bHQgZW52IGZpbGUgd2l0aCBhbGwgZGVmYXVsdCB2YWx1ZXMKIyBAc3Rkb3V0IHRoZSBkZWZhdWx0IGVudiBmaWxlcGF0aApFbnY6OmNyZWF0ZURlZmF1bHRFbnZGaWxlKCkgewogIGxvY2FsIGVudkZpbGUKICBlbnZGaWxlPSIkKEZyYW1ld29yazo6Y3JlYXRlVGVtcEZpbGUgImNyZWF0ZURlZmF1bHRFbnZGaWxlRW52RmlsZSIpIiB8fCByZXR1cm4gMgoKICAoCiAgICBlY2hvICJCQVNIX0ZSQU1FV09SS19USEVNRT0ke0JBU0hfRlJBTUVXT1JLX1RIRU1FOi1kZWZhdWx0fSIKICAgIGVjaG8gIkJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTD0ke0JBU0hfRlJBTUVXT1JLX0xPR19MRVZFTDotMH0iCiAgICBlY2hvICJCQVNIX0ZSQU1FV09SS19ESVNQTEFZX0xFVkVMPSR7QkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTDotJHtfX0xFVkVMX1dBUk5JTkd9fSIKICAgICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAxNgogICAgZWNobyAnQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEU9IiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEU6LSIke0ZSQU1FV09SS19ST09UX0RJUn0vbG9ncy8ke1NDUklQVF9OQU1FfS5sb2cifSInCiAgICBlY2hvICJCQVNIX0ZSQU1FV09SS19MT0dfRklMRV9NQVhfUk9UQVRJT049JHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRV9NQVhfUk9UQVRJT046LTV9IgogICkgPiIke2VudkZpbGV9IgogIGVjaG8gIiR7ZW52RmlsZX0iCn0KCiMgQGRlc2NyaXB0aW9uIHJlbW92ZSBjb21tZW50IGxpbmVzIGZyb20gaW5wdXQgb3IgZmlsZXMgcHJvdmlkZWQgYXMgYXJndW1lbnRzCiMgQGFyZyAkQCBmaWxlczpTdHJpbmdbXSAob3B0aW9uYWwpIHRoZSBmaWxlcyB0byBmaWx0ZXIKIyBAZW52IGNvbW1lbnRMaW5lUHJlZml4IFN0cmluZyB0aGUgY29tbWVudCBsaW5lIHByZWZpeCAoZGVmYXVsdCB2YWx1ZTogIykKIyBAZXhpdGNvZGUgMCBpZiBsaW5lcyBmaWx0ZXJlZCBvciBub3QKIyBAZXhpdGNvZGUgMiBpZiBncmVwIGZhaWxzIGZvciBhbnkgb3RoZXIgcmVhc29ucyB0aGFuIG5vdCBmb3VuZAojIEBzdGRpbiB0aGUgZmlsZSBhcyBzdGRpbiB0byBmaWx0ZXIgKGFsdGVybmF0aXZlIHRvIGZpbGVzIGFyZ3VtZW50KQojIEBzdGRvdXQgdGhlIGZpbHRlcmVkIGxpbmVzCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjEyMApGaWx0ZXJzOjpjb21tZW50TGluZXMoKSB7CiAgZ3JlcCAtdnhFICJbWzpibGFuazpdXSooJHtjb21tZW50TGluZVByZWZpeDotI30uKik/IiAiJEAiIHx8IHRlc3QgJD8gPSAxCn0KCiMgQGRlc2NyaXB0aW9uIGNyZWF0ZSBhIHRlbXAgZmlsZSB1c2luZyBkZWZhdWx0IFRNUERJUiB2YXJpYWJsZQojIGluaXRpYWxpemVkIGluIF9pbmNsdWRlcy9fY29tbW9uSGVhZGVyLnNoCiMgQGVudiBUTVBESVIgU3RyaW5nIChkZWZhdWx0IHZhbHVlIC90bXApCiMgQGFyZyAkMSB0ZW1wbGF0ZU5hbWU6U3RyaW5nIHRlbXBsYXRlIG5hbWUgdG8gdXNlKG9wdGlvbmFsKQpGcmFtZXdvcms6OmNyZWF0ZVRlbXBGaWxlKCkgewogIG1rdGVtcCAtcCAiJHtUTVBESVI6LS90bXB9IiAtdCAiJHsxOi19LlhYWFhYWFhYWFhYWCIKfQoKIyBAZGVzY3JpcHRpb24gbG9nIG1lc3NhZ2UgdG8gZmlsZQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmxvZ1NraXBwZWQoKSB7CiAgaWYgKChCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgPj0gX19MRVZFTF9JTkZPKSk7IHRoZW4KICAgIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1TS0lQUEVEfSIgIiQxIgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGxvYWQgY29sb3JzIHRoZW1lIGNvbnN0YW50cwojIEB3YXJuaW5nIGlmIHR0eSBub3Qgb3BlbmVkLCBub0NvbG9yIHRoZW1lIHdpbGwgYmUgY2hvc2VuCiMgQGFyZyAkMSB0aGVtZTpTdHJpbmcgdGhlIHRoZW1lIHRvIHVzZSAoZGVmYXVsdCwgbm9Db2xvcikKIyBAYXJnICRAIGFyZ3M6U3RyaW5nW10KIyBAc2V0IF9fRVJST1JfQ09MT1IgU3RyaW5nIGluZGljYXRlIGVycm9yIHN0YXR1cwojIEBzZXQgX19JTkZPX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSBpbmZvIHN0YXR1cwojIEBzZXQgX19TVUNDRVNTX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSBzdWNjZXNzIHN0YXR1cwojIEBzZXQgX19XQVJOSU5HX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSB3YXJuaW5nIHN0YXR1cwojIEBzZXQgX19TS0lQUEVEX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSBza2lwcGVkIHN0YXR1cwojIEBzZXQgX19ERUJVR19DT0xPUiBTdHJpbmcgaW5kaWNhdGUgZGVidWcgc3RhdHVzCiMgQHNldCBfX0hFTFBfQ09MT1IgU3RyaW5nIGluZGljYXRlIGhlbHAgc3RhdHVzCiMgQHNldCBfX1RFU1RfQ09MT1IgU3RyaW5nIG5vdCB1c2VkCiMgQHNldCBfX1RFU1RfRVJST1JfQ09MT1IgU3RyaW5nIG5vdCB1c2VkCiMgQHNldCBfX0hFTFBfVElUTEVfQ09MT1IgU3RyaW5nIHVzZWQgdG8gZGlzcGxheSBoZWxwIHRpdGxlIGluIGhlbHAgc3RyaW5ncwojIEBzZXQgX19IRUxQX09QVElPTl9DT0xPUiBTdHJpbmcgdXNlZCB0byBkaXNwbGF5IGhpZ2hsaWdodCBvcHRpb25zIGluIGhlbHAgc3RyaW5ncwojCiMgQHNldCBfX1JFU0VUX0NPTE9SIFN0cmluZyByZXNldCBkZWZhdWx0IGNvbG9yCiMKIyBAc2V0IF9fSEVMUF9FWEFNUExFIFN0cmluZyB0byByZW1vdmUKIyBAc2V0IF9fSEVMUF9USVRMRSBTdHJpbmcgdG8gcmVtb3ZlCiMgQHNldCBfX0hFTFBfTk9STUFMIFN0cmluZyB0byByZW1vdmUKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0ClVJOjp0aGVtZSgpIHsKICBsb2NhbCB0aGVtZT0iJHsxLWRlZmF1bHR9IgogIGlmIFtbICEgIiR7dGhlbWV9IiA9fiAtZm9yY2UkIF1dICYmICEgQXNzZXJ0Ojp0dHk7IHRoZW4KICAgIHRoZW1lPSJub0NvbG9yIgogIGZpCiAgY2FzZSAiJHt0aGVtZX0iIGluCiAgICBkZWZhdWx0IHwgZGVmYXVsdC1mb3JjZSkKICAgICAgdGhlbWU9ImRlZmF1bHQiCiAgICAgIDs7CiAgICBub0NvbG9yKSA7OwogICAgKikKICAgICAgTG9nOjpmYXRhbCAiaW52YWxpZCB0aGVtZSBwcm92aWRlZCIKICAgICAgOzsKICBlc2FjCiAgaWYgW1sgIiR7dGhlbWV9IiA9ICJkZWZhdWx0IiBdXTsgdGhlbgogICAgQkFTSF9GUkFNRVdPUktfVEhFTUU9ImRlZmF1bHQiCiAgICAjIGNoZWNrIGNvbG9ycyBhcHBsaWNhYmxlIGh0dHBzOi8vbWlzYy5mbG9naXNvZnQuY29tL2Jhc2gvdGlwX2NvbG9yc19hbmRfZm9ybWF0dGluZwogICAgX19FUlJPUl9DT0xPUj0nXGVbMzFtJyAgICAgICAgICMgUmVkCiAgICBfX0lORk9fQ09MT1I9J1xlWzQ0bScgICAgICAgICAgIyB3aGl0ZSBvbiBsaWdodEJsdWUKICAgIF9fU1VDQ0VTU19DT0xPUj0nXGVbMzJtJyAgICAgICAjIEdyZWVuCiAgICBfX1dBUk5JTkdfQ09MT1I9J1xlWzMzbScgICAgICAgIyBZZWxsb3cKICAgIF9fU0tJUFBFRF9DT0xPUj0nXGVbMzNtJyAgICAgICAjIFllbGxvdwogICAgX19ERUJVR19DT0xPUj0nXGVbMzdtJyAgICAgICAgICMgR3JleQogICAgX19IRUxQX0NPTE9SPSdcZVs3OzQ5OzMzbScgICAgICMgQmxhY2sgb24gR29sZAogICAgX19URVNUX0NPTE9SPSdcZVsxMDBtJyAgICAgICAgICMgTGlnaHQgbWFnZW50YQogICAgX19URVNUX0VSUk9SX0NPTE9SPSdcZVs0MW0nICAgICMgd2hpdGUgb24gcmVkCiAgICBfX0hFTFBfVElUTEVfQ09MT1I9IlxlWzE7MzdtIiAgIyBCb2xkCiAgICBfX0hFTFBfT1BUSU9OX0NPTE9SPSJcZVsxOzM0bSIgIyBCbHVlCiAgICAjIEludGVybmFsOiByZXNldCBjb2xvcgogICAgX19SRVNFVF9DT0xPUj0nXGVbMG0nICMgUmVzZXQgQ29sb3IKICAgICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjE1NSxTQzIwMzQKICAgIF9fSEVMUF9FWEFNUExFPSIkKGVjaG8gLWUgIlxlWzI7OTdtIikiCiAgICAjIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIxNTUsU0MyMDM0CiAgICBfX0hFTFBfVElUTEU9IiQoZWNobyAtZSAiXGVbMTszN20iKSIKICAgICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjE1NSxTQzIwMzQKICAgIF9fSEVMUF9OT1JNQUw9IiQoZWNobyAtZSAiXDAzM1swbSIpIgogIGVsc2UKICAgIEJBU0hfRlJBTUVXT1JLX1RIRU1FPSJub0NvbG9yIgogICAgIyBjaGVjayBjb2xvcnMgYXBwbGljYWJsZSBodHRwczovL21pc2MuZmxvZ2lzb2Z0LmNvbS9iYXNoL3RpcF9jb2xvcnNfYW5kX2Zvcm1hdHRpbmcKICAgIF9fRVJST1JfQ09MT1I9JycKICAgIF9fSU5GT19DT0xPUj0nJwogICAgX19TVUNDRVNTX0NPTE9SPScnCiAgICBfX1dBUk5JTkdfQ09MT1I9JycKICAgIF9fU0tJUFBFRF9DT0xPUj0nJwogICAgX19ERUJVR19DT0xPUj0nJwogICAgX19IRUxQX0NPTE9SPScnCiAgICBfX1RFU1RfQ09MT1I9JycKICAgIF9fVEVTVF9FUlJPUl9DT0xPUj0nJwogICAgX19IRUxQX1RJVExFX0NPTE9SPScnCiAgICBfX0hFTFBfT1BUSU9OX0NPTE9SPScnCiAgICAjIEludGVybmFsOiByZXNldCBjb2xvcgogICAgX19SRVNFVF9DT0xPUj0nJwogICAgX19IRUxQX0VYQU1QTEU9JycKICAgIF9fSEVMUF9USVRMRT0nJwogICAgX19IRUxQX05PUk1BTD0nJwogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGNoZWNrIGlmIHR0eSAoaW50ZXJhY3RpdmUgbW9kZSkgaXMgYWN0aXZlCiMgQG5vYXJncwojIEBleGl0Y29kZSAxIGlmIHR0eSBub3QgYWN0aXZlCiMgQGVudiBOT05fSU5URVJBQ1RJVkUgaWYgMSBjb25zaWRlciBhcyBub3QgaW50ZXJhY3RpdmUgZXZlbiBpZiBlbnZpcm9ubWVudCBpcyBpbnRlcmFjdGl2ZQojIEBlbnYgSU5URVJBQ1RJVkUgaWYgMSBjb25zaWRlciBhcyBpbnRlcmFjdGl2ZSBldmVuIGlmIGVudmlyb25tZW50IGlzIG5vdCBpbnRlcmFjdGl2ZQojIEBzdGRlcnIgZGlhZ25vc3RpYyBpbmZvcm1hdGlvbiArIGhlbHAgaWYgc2Vjb25kIGFyZ3VtZW50IGlzIHByb3ZpZGVkCkFzc2VydDo6dHR5KCkgewogIGlmIFtbICIke05PTl9JTlRFUkFDVElWRTotMH0iID0gIjEiIF1dOyB0aGVuCiAgICByZXR1cm4gMQogIGZpCiAgaWYgW1sgIiR7SU5URVJBQ1RJVkU6LTB9IiA9ICIxIiBdXTsgdGhlbgogICAgcmV0dXJuIDAKICBmaQogIFtbIC10IDEgfHwgLXQgMiBdXQp9CgojIEZVTkNUSU9OUwoKZmFjYWRlX21haW5fZW1iZWRGcmFtZXdvcmtGdW5jdGlvbmJpbkZpbGV0cGwoKSB7CiMgUkVRVUlSRVMKTGludXg6OnJlcXVpcmVFeGVjdXRlZEFzVXNlcgpFbnY6OnJlcXVpcmVMb2FkCkxvZzo6cmVxdWlyZUxvYWQKTGludXg6OnJlcXVpcmVSZWFscGF0aENvbW1hbmQKVUk6OnJlcXVpcmVUaGVtZQpDb21waWxlcjo6RmFjYWRlOjpyZXF1aXJlQ29tbWFuZEJpbkRpcgoKIyBAcmVxdWlyZSBDb21waWxlcjo6RmFjYWRlOjpyZXF1aXJlQ29tbWFuZEJpbkRpcgoKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTU0LFNDMjAxNgpmdW5jdGlvblRvQ2FsbD0nRGI6OnF1ZXJ5T25lRGF0YWJhc2UnCiIke2Z1bmN0aW9uVG9DYWxsfSIgIiRAIgoKfQoKZmFjYWRlX21haW5fZW1iZWRGcmFtZXdvcmtGdW5jdGlvbmJpbkZpbGV0cGwgIiRAIgo=" +declare -gx embed_function_DbQueryOneDatabase="${PERSISTENT_TMPDIR:-/tmp}/bin/381679fc2529dfab4ed52310d74a2fe5/dbQueryOneDatabase" +declare -gx encoded_binary_file_DbQueryOneDatabase="IyEvdXNyL2Jpbi9lbnYgYmFzaAojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgR0VORVJBVEVEIEZBQ0FERSBGUk9NIGh0dHBzOi8vZ2l0aHViLmNvbS9mY2hhc3RhbmV0L2Jhc2gtdG9vbHMvdHJlZS9tYXN0ZXIvLi4vLi4vLmNhY2hlL3ByZS1jb21taXQvcmVwb3oyNmR2OV83L3NyYy9Db21waWxlci9FbWJlZC9lbWJlZEZyYW1ld29ya0Z1bmN0aW9uLmJpbkZpbGUudHBsCiMgRE8gTk9UIEVESVQgSVQKIyBAZ2VuZXJhdGVkCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMjg4LFNDMjAzNAojIEJJTl9GSUxFPSR7QklOX0ZJTEV9CiMgRkFDQURFCgojIGVuc3VyZSB0aGF0IG5vIHVzZXIgYWxpYXNlcyBjb3VsZCBpbnRlcmZlcmUgd2l0aAojIGNvbW1hbmRzIHVzZWQgaW4gdGhpcyBzY3JpcHQKdW5hbGlhcyAtYSB8fCB0cnVlCnNob3B0IC11IGV4cGFuZF9hbGlhc2VzCgojIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIwMzQKKChmYWlsdXJlcyA9IDApKSB8fCB0cnVlCgojIEJhc2ggd2lsbCByZW1lbWJlciAmIHJldHVybiB0aGUgaGlnaGVzdCBleGl0IGNvZGUgaW4gYSBjaGFpbiBvZiBwaXBlcy4KIyBUaGlzIHdheSB5b3UgY2FuIGNhdGNoIHRoZSBlcnJvciBpbnNpZGUgcGlwZXMsIGUuZy4gbXlzcWxkdW1wIHwgZ3ppcApzZXQgLW8gcGlwZWZhaWwKc2V0IC1vIGVycmV4aXQKCiMgQ29tbWFuZCBTdWJzdGl0dXRpb24gY2FuIGluaGVyaXQgZXJyZXhpdCBvcHRpb24gc2luY2UgYmFzaCB2NC40CnNob3B0IC1zIGluaGVyaXRfZXJyZXhpdCB8fCB0cnVlCgojIGEgbG9nIGlzIGdlbmVyYXRlZCB3aGVuIGEgY29tbWFuZCBmYWlscwpzZXQgLW8gZXJydHJhY2UKCiMgdXNlIG51bGxnbG9iIHNvIHRoYXQgKGZpbGUqLnBocCkgd2lsbCByZXR1cm4gYW4gZW1wdHkgYXJyYXkgaWYgbm8gZmlsZSBtYXRjaGVzIHRoZSB3aWxkY2FyZApzaG9wdCAtcyBudWxsZ2xvYgoKIyBlbnN1cmUgcmVnZXhwIGFyZSBpbnRlcnByZXRlZCB3aXRob3V0IGFjY2VudHVhdGVkIGNoYXJhY3RlcnMKZXhwb3J0IExDX0FMTD1QT1NJWAoKZXhwb3J0IFRFUk09eHRlcm0tMjU2Y29sb3IKCiMgYXZvaWQgaW50ZXJhY3RpdmUgaW5zdGFsbApleHBvcnQgREVCSUFOX0ZST05URU5EPW5vbmludGVyYWN0aXZlCmV4cG9ydCBERUJDT05GX05PTklOVEVSQUNUSVZFX1NFRU49dHJ1ZQoKIyBzdG9yZSBjb21tYW5kIGFyZ3VtZW50cyBmb3IgbGF0ZXIgdXNhZ2UKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0CmRlY2xhcmUgLWEgQkFTSF9GUkFNRVdPUktfQVJHVj0oIiRAIikKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0CmRlY2xhcmUgLWEgT1JJR0lOQUxfQkFTSF9GUkFNRVdPUktfQVJHVj0oIiRAIikKCiMgQHNlZSBodHRwczovL3VuaXguc3RhY2tleGNoYW5nZS5jb20vYS8zODY4NTYKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMzE3CmludGVycnVwdE1hbmFnZW1lbnQoKSB7CiAgIyByZXN0b3JlIFNJR0lOVCBoYW5kbGVyCiAgdHJhcCAtIElOVAogICMgZW5zdXJlIHRoYXQgQ3RybC1DIGlzIHRyYXBwZWQgYnkgdGhpcyBzY3JpcHQgYW5kIG5vdCBieSBzdWIgcHJvY2VzcwogICMgcmVwb3J0IHRvIHRoZSBwYXJlbnQgdGhhdCB3ZSBoYXZlIGluZGVlZCBiZWVuIGludGVycnVwdGVkCiAga2lsbCAtcyBJTlQgIiQkIgp9CnRyYXAgaW50ZXJydXB0TWFuYWdlbWVudCBJTlQKU0NSSVBUX05BTUU9JHswIyMqL30KUkVBTF9TQ1JJUFRfRklMRT0iJChyZWFkbGluayAtZSAiJChyZWFscGF0aCAiJHtCQVNIX1NPVVJDRVswXX0iKSIpIgppZiBbWyAtbiAiJHtFTUJFRF9DVVJSRU5UX0RJUn0iIF1dOyB0aGVuCiAgQ1VSUkVOVF9ESVI9IiR7RU1CRURfQ1VSUkVOVF9ESVJ9IgplbHNlCiAgQ1VSUkVOVF9ESVI9IiQoY2QgIiQocmVhZGxpbmsgLWUgIiR7UkVBTF9TQ1JJUFRfRklMRSUvKn0iKSIgJiYgcHdkIC1QKSIKZmkKCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojIFRlbXAgZGlyIG1hbmFnZW1lbnQKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgpLRUVQX1RFTVBfRklMRVM9IiR7S0VFUF9URU1QX0ZJTEVTOi0wfSIKZXhwb3J0IEtFRVBfVEVNUF9GSUxFUwoKIyBQRVJTSVNURU5UX1RNUERJUiBpcyBub3QgZGVsZXRlZCBieSB0cmFwcwpQRVJTSVNURU5UX1RNUERJUj0iJHtUTVBESVI6LS90bXB9L2Jhc2gtZnJhbWV3b3JrIgpleHBvcnQgUEVSU0lTVEVOVF9UTVBESVIKbWtkaXIgLXAgIiR7UEVSU0lTVEVOVF9UTVBESVJ9IgoKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0ClRNUERJUj0iJChta3RlbXAgLWQgLXAgIiR7UEVSU0lTVEVOVF9UTVBESVI6LS90bXB9IiAtdCBiYXNoLWZyYW1ld29yay0kJC1YWFhYWFgpIgpleHBvcnQgVE1QRElSCgojIHRlbXAgZGlyIGNsZWFuaW5nCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjMxNwpjbGVhbk9uRXhpdCgpIHsKICBpZiBbWyAiJHtLRUVQX1RFTVBfRklMRVM6LTB9IiA9ICIxIiBdXTsgdGhlbgogICAgTG9nOjpkaXNwbGF5SW5mbyAiS0VFUF9URU1QX0ZJTEVTPTEgdGVtcCBmaWxlcyBrZXB0IGhlcmUgJyR7VE1QRElSfSciCiAgZWxpZiBbWyAtbiAiJHtUTVBESVIreHh4fSIgXV07IHRoZW4KICAgIExvZzo6ZGlzcGxheURlYnVnICJLRUVQX1RFTVBfRklMRVM9MCByZW1vdmluZyB0ZW1wIGZpbGVzICcke1RNUERJUn0nIgogICAgcm0gLVJmICIke1RNUERJUjotL3RtcC9mYWtlfSIgPi9kZXYvbnVsbCAyPiYxCiAgZmkKfQp0cmFwIGNsZWFuT25FeGl0IEVYSVQgSFVQIFFVSVQgQUJSVCBURVJNCgojIFZBUl9NQUlOX0ZVTkNUSU9OX1ZBUl9OQU1FPWRiUXVlcnlBbGxEYXRhYmFzZXNGYWNhZGUKCiMgQGRlc2NyaXB0aW9uIHVzZWQgdG8gZXhlY3V0ZSBnaXZlbiBxdWVyeSB3aGVuIHVzaW5nCiMgZGJTY3JpcHRBbGxEYXRhYmFzZXMKIyBAYXJnICQxIGRzbjpTdHJpbmcKIyBAYXJnICQyIGRiOlN0cmluZwojIEBlbnYgcXVlcnkgU3RyaW5nCiMgQGVudiBvcHRpb25TZXBhcmF0b3IgU3RyaW5nCiMgQHJlcXVpcmUgTGludXg6OnJlcXVpcmVFeGVjdXRlZEFzVXNlcgpEYjo6cXVlcnlPbmVEYXRhYmFzZSgpIHsKICAjIHF1ZXJ5IGFuZCBvcHRpb25TZXBhcmF0b3IgYXJlIHBhc3NlZCB2aWEgZXhwb3J0CiAgbG9jYWwgZHNuPSIkMSIKICBsb2NhbCBkYj0iJDIiCgogIGxvY2FsIC1BIGRiSW5zdGFuY2UKICBEYXRhYmFzZTo6bmV3SW5zdGFuY2UgZGJJbnN0YW5jZSAiJHtkc259IgogIERhdGFiYXNlOjpzZXRRdWVyeU9wdGlvbnMgZGJJbnN0YW5jZSAiJHtkYkluc3RhbmNlW1FVRVJZX09QVElPTlNdfSAtLWNvbm5lY3QtdGltZW91dD01IgoKICAjIGlkZW50aWZ5IGNvbHVtbnMgaGVhZGVyCiAgZWNobyAtbiAiQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAiCiAgRGF0YWJhc2U6OnNraXBDb2x1bW5OYW1lcyBkYkluc3RhbmNlIDAKCiAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTU0CiAgaWYgISBEYXRhYmFzZTo6cXVlcnkgZGJJbnN0YW5jZSAiJHtxdWVyeX0iICIke2RifSIgfCBzZWQgInMvXHQvJHtvcHRpb25TZXBhcmF0b3J9L2ciOyB0aGVuCiAgICBMb2c6OmZhdGFsICJkYXRhYmFzZSAke2RifSBlcnJvciIgMT4mMgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIExvZyBuYW1lc3BhY2UgcHJvdmlkZXMgMiBraW5kIG9mIGZ1bmN0aW9ucwojIC0gTG9nOjpkaXNwbGF5KiBhbGxvd3MgdG8gZGlzcGxheSBnaXZlbiBtZXNzYWdlIHdpdGgKIyAgIGdpdmVuIGRpc3BsYXkgbGV2ZWwKIyAtIExvZzo6bG9nKiBhbGxvd3MgdG8gbG9nIGdpdmVuIG1lc3NhZ2Ugd2l0aAojICAgZ2l2ZW4gbG9nIGxldmVsCiMgTG9nOjpkaXNwbGF5KiBmdW5jdGlvbnMgYXV0b21hdGljYWxseSBsb2cgdGhlIG1lc3NhZ2UgdG9vCiMgQHNlZSBFbnY6OnJlcXVpcmVMb2FkIHRvIGxvYWQgdGhlIGRpc3BsYXkgYW5kIGxvZyBsZXZlbCBmcm9tIC5lbnYgZmlsZQoKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIG9mZgpleHBvcnQgX19MRVZFTF9PRkY9MAojIEBkZXNjcmlwdGlvbiBsb2cgbGV2ZWwgZXJyb3IKZXhwb3J0IF9fTEVWRUxfRVJST1I9MQojIEBkZXNjcmlwdGlvbiBsb2cgbGV2ZWwgd2FybmluZwpleHBvcnQgX19MRVZFTF9XQVJOSU5HPTIKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIGluZm8KZXhwb3J0IF9fTEVWRUxfSU5GTz0zCiMgQGRlc2NyaXB0aW9uIGxvZyBsZXZlbCBzdWNjZXNzCmV4cG9ydCBfX0xFVkVMX1NVQ0NFU1M9MwojIEBkZXNjcmlwdGlvbiBsb2cgbGV2ZWwgZGVidWcKZXhwb3J0IF9fTEVWRUxfREVCVUc9NAoKIyBAZGVzY3JpcHRpb24gdmVyYm9zZSBsZXZlbCBvZmYKZXhwb3J0IF9fVkVSQk9TRV9MRVZFTF9PRkY9MAojIEBkZXNjcmlwdGlvbiB2ZXJib3NlIGxldmVsIGluZm8KZXhwb3J0IF9fVkVSQk9TRV9MRVZFTF9JTkZPPTEKIyBAZGVzY3JpcHRpb24gdmVyYm9zZSBsZXZlbCBpbmZvCmV4cG9ydCBfX1ZFUkJPU0VfTEVWRUxfREVCVUc9MgojIEBkZXNjcmlwdGlvbiB2ZXJib3NlIGxldmVsIGluZm8KZXhwb3J0IF9fVkVSQk9TRV9MRVZFTF9UUkFDRT0zCgojIEBkZXNjcmlwdGlvbiBEaXNwbGF5IG1lc3NhZ2UgdXNpbmcgZGVidWcgY29sb3IgKGdyZXkpCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6ZGlzcGxheURlYnVnKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCA+PSBfX0xFVkVMX0RFQlVHKSk7IHRoZW4KICAgIGVjaG8gLWUgIiR7X19ERUJVR19DT0xPUn1ERUJVRyAgIC0gJHsxfSR7X19SRVNFVF9DT0xPUn0iID4mMgogIGZpCiAgTG9nOjpsb2dEZWJ1ZyAiJDEiCn0KCiMgQGRlc2NyaXB0aW9uIERpc3BsYXkgbWVzc2FnZSB1c2luZyBpbmZvIGNvbG9yIChiZyBsaWdodCBibHVlL2ZnIHdoaXRlKQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlJbmZvKCkgewogIGxvY2FsIHR5cGU9IiR7MjotSU5GT30iCiAgaWYgKChCQVNIX0ZSQU1FV09SS19ESVNQTEFZX0xFVkVMID49IF9fTEVWRUxfSU5GTykpOyB0aGVuCiAgICBlY2hvIC1lICIke19fSU5GT19DT0xPUn0ke3R5cGV9ICAgIC0gJHsxfSR7X19SRVNFVF9DT0xPUn0iID4mMgogIGZpCiAgTG9nOjpsb2dJbmZvICIkMSIgIiR7dHlwZX0iCn0KCiMgQGRlc2NyaXB0aW9uIGVuc3VyZSBDT01NQU5EX0JJTl9ESVIgZW52IHZhciBpcyBzZXQKIyBhbmQgUEFUSCBjb3JyZWN0bHkgcHJlcGFyZWQKIyBAbm9hcmdzCiMgQHNldCBDT01NQU5EX0JJTl9ESVIgc3RyaW5nIHRoZSBkaXJlY3Rvcnkgd2hlcmUgdG8gZmluZCB0aGlzIGNvbW1hbmQKIyBAc2V0IFBBVEggc3RyaW5nIGFkZCBkaXJlY3Rvcnkgd2hlcmUgdG8gZmluZCB0aGlzIGNvbW1hbmQgYmluYXJ5CkNvbXBpbGVyOjpGYWNhZGU6OnJlcXVpcmVDb21tYW5kQmluRGlyKCkgewogIENPTU1BTkRfQklOX0RJUj0iJHtDVVJSRU5UX0RJUn0iCiAgRW52OjpwYXRoUHJlcGVuZCAiJHtDT01NQU5EX0JJTl9ESVJ9Igp9CgojIEBkZXNjcmlwdGlvbiBjcmVhdGUgYSBuZXcgZGIgaW5zdGFuY2UKIyBSZXR1cm5zIGltbWVkaWF0ZWx5IGlmIHRoZSBpbnN0YW5jZSBpcyBhbHJlYWR5IGluaXRpYWxpemVkCiMKIyBAYXJnICQxIGluc3RhbmNlTmV3SW5zdGFuY2U6Jk1hcDxTdHJpbmcsU3RyaW5nPiAocGFzc2VkIGJ5IHJlZmVyZW5jZSkgZGF0YWJhc2UgaW5zdGFuY2UgdG8gdXNlCiMgQGFyZyAkMiBkc246U3RyaW5nIGRzbiBwcm9maWxlIC0gbG9hZCB0aGUgZHNuLmVudiBwcm9maWxlIGRlZHVjZWQgdXNpbmcgcnVsZXMgZGVmaW5lZCBpbiBDb25mOjpnZXRBYnNvbHV0ZUZpbGUKIwojIEBleGFtcGxlCiMgICBkZWNsYXJlIC1BZ3ggZGJJbnN0YW5jZQojICAgRGF0YWJhc2U6Om5ld0luc3RhbmNlIGRiSW5zdGFuY2UgImRlZmF1bHQubG9jYWwiCiMKIyBAZXhpdGNvZGUgMSBpZiBkbnMgZmlsZSBub3QgYWJsZSB0byBsb2FkZWQKRGF0YWJhc2U6Om5ld0luc3RhbmNlKCkgewogIGxvY2FsIC1uIGluc3RhbmNlTmV3SW5zdGFuY2U9JDEKICBsb2NhbCBkc249IiQyIgogIGxvY2FsIERTTl9GSUxFCgogIGlmIFtbIC12IGluc3RhbmNlTmV3SW5zdGFuY2VbJ0lOSVRJQUxJWkVEJ10gJiYgIiR7aW5zdGFuY2VOZXdJbnN0YW5jZVsnSU5JVElBTElaRUQnXTotMH0iID09ICIxIiBdXTsgdGhlbgogICAgcmV0dXJuCiAgZmkKCiAgIyBmaW5hbCBhdXRoIGZpbGUgZ2VuZXJhdGVkIGZyb20gZG5zIGZpbGUKICBpbnN0YW5jZU5ld0luc3RhbmNlWydBVVRIX0ZJTEUnXT0iIgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0RTTl9GSUxFJ109IiIKCiAgIyBjaGVjayBkc24gZmlsZQogIERTTl9GSUxFPSIkKENvbmY6OmdldEFic29sdXRlRmlsZSAiZHNuIiAiJHtkc259IiAiZW52IikiIHx8IHJldHVybiAxCiAgRGF0YWJhc2U6OmNoZWNrRHNuRmlsZSAiJHtEU05fRklMRX0iIHx8IHJldHVybiAxCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnRFNOX0ZJTEUnXT0iJHtEU05fRklMRX0iCgogICMgc2hlbGxjaGVjayBzb3VyY2U9L3NyYy9EYXRhYmFzZS90ZXN0c0RhdGEvZHNuX3ZhbGlkLmVudgogIHNvdXJjZSAiJHtpbnN0YW5jZU5ld0luc3RhbmNlWydEU05fRklMRSddfSIKCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnVVNFUiddPSIke1VTRVJ9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1BBU1NXT1JEJ109IiR7UEFTU1dPUkR9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0hPU1ROQU1FJ109IiR7SE9TVE5BTUV9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1BPUlQnXT0iJHtQT1JUfSIKCiAgIyBnZW5lcmF0ZSBhdXRoRmlsZSBmb3IgZWFzeSBhdXRoZW50aWNhdGlvbgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0FVVEhfRklMRSddPSQobWt0ZW1wIC1wICIke1RNUERJUjotL3RtcH0iIC10ICJteXNxbC5YWFhYWFhYWFhYWFgiKQogICgKICAgIGVjaG8gIltjbGllbnRdIgogICAgZWNobyAidXNlciA9ICR7VVNFUn0iCiAgICBlY2hvICJwYXNzd29yZCA9ICR7UEFTU1dPUkR9IgogICAgZWNobyAiaG9zdCA9ICR7SE9TVE5BTUV9IgogICAgZWNobyAicG9ydCA9ICR7UE9SVH0iCiAgKSA+IiR7aW5zdGFuY2VOZXdJbnN0YW5jZVsnQVVUSF9GSUxFJ119IgoKICAjIHNvbWUgb2YgdGhvc2UgdmFsdWVzIGNhbiBiZSBvdmVycmlkZGVuIHVzaW5nIHRoZSBkc24gZmlsZQogICMgU0tJUF9DT0xVTU5fTkFNRVMgZW5hYmxlZCBieSBkZWZhdWx0CiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnU0tJUF9DT0xVTU5fTkFNRVMnXT0iJHtTS0lQX0NPTFVNTl9OQU1FUzotMX0iCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnU1NMX09QVElPTlMnXT0iJHtNWVNRTF9TU0xfT1BUSU9OUzotLS1zc2wtbW9kZT1ESVNBQkxFRH0iCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnUVVFUllfT1BUSU9OUyddPSIke01ZU1FMX1FVRVJZX09QVElPTlM6LS0tYmF0Y2ggLS1yYXcgLS1kZWZhdWx0LWNoYXJhY3Rlci1zZXQ9dXRmOH0iCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnRFVNUF9PUFRJT05TJ109IiR7TVlTUUxfRFVNUF9PUFRJT05TOi0tLWRlZmF1bHQtY2hhcmFjdGVyLXNldD11dGY4IC0tY29tcHJlc3MgLS1oZXgtYmxvYiAtLXJvdXRpbmVzIC0tdHJpZ2dlcnMgLS1zaW5nbGUtdHJhbnNhY3Rpb24gLS1zZXQtZ3RpZC1wdXJnZWQ9T0ZGIC0tY29sdW1uLXN0YXRpc3RpY3M9MCAke2luc3RhbmNlTmV3SW5zdGFuY2VbJ1NTTF9PUFRJT05TJ119fSIKICBpbnN0YW5jZU5ld0luc3RhbmNlWydEQl9JTVBPUlRfT1BUSU9OUyddPSIke0RCX0lNUE9SVF9PUFRJT05TOi0tLWNvbm5lY3QtdGltZW91dD01IC0tYmF0Y2ggLS1yYXcgLS1kZWZhdWx0LWNoYXJhY3Rlci1zZXQ9dXRmOH0iCgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0lOSVRJQUxJWkVEJ109MQp9CgojIEBkZXNjcmlwdGlvbiBteXNxbCBxdWVyeSBvbiBhIGdpdmVuIGRiCiMgQHdhcm5pbmcgY291bGQgdXNlIFFVRVJZX09QVElPTlMgdmFyaWFibGUgZnJvbSBkc24gaWYgZGVmaW5lZAojIEBleGFtcGxlCiMgICBjYXQgZmlsZS5zcWwgfCBEYXRhYmFzZTo6cXVlcnkgLi4uCiMgQGFyZyAkMSBpbnN0YW5jZVF1ZXJ5OiZNYXA8U3RyaW5nLFN0cmluZz4gKHBhc3NlZCBieSByZWZlcmVuY2UpIGRhdGFiYXNlIGluc3RhbmNlIHRvIHVzZQojIEBhcmcgJDIgc3FsUXVlcnk6U3RyaW5nIChvcHRpb25hbCkgc3FsIHF1ZXJ5IG9yIHNxbCBmaWxlIHRvIGV4ZWN1dGUuIGlmIG5vdCBwcm92aWRlZCBvciBlbXB0eSwgdGhlIGNvbW1hbmQgY2FuIGJlIHBpcGVkCiMgQGFyZyAkMyBkYk5hbWU6U3RyaW5nIChvcHRpb25hbCkgdGhlIGRiIG5hbWUKIwojIEBleGl0Y29kZSBteXNxbCBjb21tYW5kIHN0YXR1cyBjb2RlCkRhdGFiYXNlOjpxdWVyeSgpIHsKICBsb2NhbCAtbiBpbnN0YW5jZVF1ZXJ5PSQxCiAgbG9jYWwgLWEgbXlzcWxDb21tYW5kPSgpCiAgbG9jYWwgLWEgcXVlcnlPcHRpb25zCgogIG15c3FsQ29tbWFuZCs9KG15c3FsKQogIG15c3FsQ29tbWFuZCs9KCItLWRlZmF1bHRzLWV4dHJhLWZpbGU9JHtpbnN0YW5jZVF1ZXJ5WydBVVRIX0ZJTEUnXX0iKQogIElGUz0nICcgcmVhZCAtciAtYSBxdWVyeU9wdGlvbnMgPDw8IiR7aW5zdGFuY2VRdWVyeVsnUVVFUllfT1BUSU9OUyddfSIKICBteXNxbENvbW1hbmQrPSgiJHtxdWVyeU9wdGlvbnNbQF19IikKICBpZiBbWyAiJHtpbnN0YW5jZVF1ZXJ5WydTS0lQX0NPTFVNTl9OQU1FUyddfSIgPSAiMSIgXV07IHRoZW4KICAgIG15c3FsQ29tbWFuZCs9KCItcyIgIi0tc2tpcC1jb2x1bW4tbmFtZXMiKQogIGZpCiAgIyBhZGQgb3B0aW9uYWwgZGIgbmFtZQogIGlmIFtbIC1uICIkezMreH0iIF1dOyB0aGVuCiAgICBteXNxbENvbW1hbmQrPSgiJDMiKQogIGZpCiAgIyBhZGQgb3B0aW9uYWwgc3FsIHF1ZXJ5CiAgaWYgW1sgLW4gIiR7Mit4fSIgJiYgLW4gIiQyIiAmJiAhIC1mICIkMiIgXV07IHRoZW4KICAgIG15c3FsQ29tbWFuZCs9KCItZSIpCiAgICBteXNxbENvbW1hbmQrPSgiJDIiKQogIGZpCiAgTG9nOjpkaXNwbGF5RGVidWcgIiQocHJpbnRmICJleGVjdXRlIGNvbW1hbmQ6ICclcyciICIke215c3FsQ29tbWFuZFsqXX0iKSIKCiAgaWYgW1sgLWYgIiQyIiBdXTsgdGhlbgogICAgIiR7bXlzcWxDb21tYW5kW0BdfSIgPCIkMiIKICBlbHNlCiAgICAiJHtteXNxbENvbW1hbmRbQF19IgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIHNldCB0aGUgZ2VuZXJhbCBvcHRpb25zIHRvIHVzZSBvbiBteXNxbCBjb21tYW5kIHRvIHF1ZXJ5IHRoZSBkYXRhYmFzZQojIERpZmZlcnMgdGhhbiBzZXRPcHRpb25zIGluIHRoZSB3YXkgdGhhdCB0aGVzZSBvcHRpb25zIGNvdWxkIGNoYW5nZSBlYWNoIHRpbWUKIwojIEBhcmcgJDEgaW5zdGFuY2VTZXRRdWVyeU9wdGlvbnM6Jk1hcDxTdHJpbmcsU3RyaW5nPiAocGFzc2VkIGJ5IHJlZmVyZW5jZSkgZGF0YWJhc2UgaW5zdGFuY2UgdG8gdXNlCiMgQGFyZyAkMiBvcHRpb25zTGlzdDpTdHJpbmcgcXVlcnkgb3B0aW9ucyBsaXN0CkRhdGFiYXNlOjpzZXRRdWVyeU9wdGlvbnMoKSB7CiAgbG9jYWwgLW4gaW5zdGFuY2VTZXRRdWVyeU9wdGlvbnM9JDEKICAjIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIwMzQKICBpbnN0YW5jZVNldFF1ZXJ5T3B0aW9uc1snUVVFUllfT1BUSU9OUyddPSIkMiIKfQoKIyBAZGVzY3JpcHRpb24gYnkgZGVmYXVsdCB3ZSBza2lwIHRoZSBjb2x1bW4gbmFtZXMKIyBidXQgc29tZXRpbWVzIHdlIG5lZWQgY29sdW1uIG5hbWVzIHRvIGRpc3BsYXkgc29tZSByZXN1bHRzCiMgZGlzYWJsZSB0aGlzIG9wdGlvbiB0ZW1wb3JhcmlseSBhbmQgdGhlbiByZXN0b3JlIGl0IHRvIHRydWUKIwojIEBhcmcgJDEgaW5zdGFuY2VTZXRRdWVyeU9wdGlvbnM6Jk1hcDxTdHJpbmcsU3RyaW5nPiAocGFzc2VkIGJ5IHJlZmVyZW5jZSkgZGF0YWJhc2UgaW5zdGFuY2UgdG8gdXNlCiMgQGFyZyAkMiBza2lwQ29sdW1uTmFtZXM6Qm9vbGVhbiAwIHRvIGRpc2FibGUsIDEgdG8gZW5hYmxlIChoaWRlIGNvbHVtbiBuYW1lcykKRGF0YWJhc2U6OnNraXBDb2x1bW5OYW1lcygpIHsKICBsb2NhbCAtbiBpbnN0YW5jZVNraXBDb2x1bW5OYW1lcz0kMQogICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNAogIGluc3RhbmNlU2tpcENvbHVtbk5hbWVzWydTS0lQX0NPTFVNTl9OQU1FUyddPSIkMiIKfQoKIyBAZGVzY3JpcHRpb24gcHJlcGVuZCBkaXJlY3RvcmllcyB0byB0aGUgUEFUSCBlbnZpcm9ubWVudCB2YXJpYWJsZQojIEBhcmcgJEAgYXJnczpTdHJpbmdbXSBsaXN0IG9mIGRpcmVjdG9yaWVzIHRvIHByZXBlbmQKIyBAc2V0IFBBVEggdXBkYXRlIFBBVEggd2l0aCB0aGUgZGlyZWN0b3JpZXMgcHJlcGVuZGVkCkVudjo6cGF0aFByZXBlbmQoKSB7CiAgbG9jYWwgYXJnCiAgZm9yIGFyZyBpbiAiJEAiOyBkbwogICAgaWYgW1sgLWQgIiR7YXJnfSIgJiYgIjoke1BBVEh9OiIgIT0gKiI6JHthcmd9OiIqIF1dOyB0aGVuCiAgICAgIFBBVEg9IiQocmVhbHBhdGggIiR7YXJnfSIpOiR7UEFUSH0iCiAgICBmaQogIGRvbmUKfQoKIyBAZGVzY3JpcHRpb24gRGlzcGxheSBtZXNzYWdlIHVzaW5nIGVycm9yIGNvbG9yIChyZWQpIGFuZCBleGl0IGltbWVkaWF0ZWx5IHdpdGggZXJyb3Igc3RhdHVzIDEKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpmYXRhbCgpIHsKICBlY2hvIC1lICIke19fRVJST1JfQ09MT1J9RkFUQUwgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBMb2c6OmxvZ0ZhdGFsICIkMSIKICBleGl0IDEKfQoKIyBAZGVzY3JpcHRpb24gbG9nIG1lc3NhZ2UgdG8gZmlsZQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmxvZ0RlYnVnKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMID49IF9fTEVWRUxfREVCVUcpKTsgdGhlbgogICAgTG9nOjpsb2dNZXNzYWdlICIkezI6LURFQlVHfSIgIiQxIgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGxvZyBtZXNzYWdlIHRvIGZpbGUKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpsb2dJbmZvKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMID49IF9fTEVWRUxfSU5GTykpOyB0aGVuCiAgICBMb2c6OmxvZ01lc3NhZ2UgIiR7MjotSU5GT30iICIkMSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBlbnN1cmUgcnVubmluZyB1c2VyIGlzIG5vdCByb290CiMgQGV4aXRjb2RlIDEgaWYgY3VycmVudCB1c2VyIGlzIHJvb3QKIyBAc3RkZXJyIGRpYWdub3N0aWNzIGluZm9ybWF0aW9uIGlzIGRpc3BsYXllZApMaW51eDo6cmVxdWlyZUV4ZWN1dGVkQXNVc2VyKCkgewogIGlmIFtbICIkKGlkIC11KSIgPSAiMCIgXV07IHRoZW4KICAgIExvZzo6ZmF0YWwgInRoaXMgc2NyaXB0IHNob3VsZCBiZSBleGVjdXRlZCBhcyBub3JtYWwgdXNlciIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBnZXQgYWJzb2x1dGUgY29uZiBmaWxlIGZyb20gc3BlY2lmaWVkIGNvbmYgZm9sZGVyIGRlZHVjZWQgdXNpbmcgdGhlc2UgcnVsZXMKIyAgICogZnJvbSBhYnNvbHV0ZSBmaWxlIChpZ25vcmVzIDxjb25mRm9sZGVyPiBhbmQgPGV4dGVuc2lvbj4pCiMgICAqIHJlbGF0aXZlIHRvIHdoZXJlIHNjcmlwdCBpcyBleGVjdXRlZCAoaWdub3JlcyA8Y29uZkZvbGRlcj4gYW5kIDxleHRlbnNpb24+KQojICAgKiBmcm9tIGhvbWUvLmJhc2gtdG9vbHMvPGNvbmZGb2xkZXI+CiMgICAqIGZyb20gZnJhbWV3b3JrIGNvbmYvPGNvbmZGb2xkZXI+CiMKIyBAYXJnICQxIGNvbmZGb2xkZXI6U3RyaW5nIHRoZSBkaXJlY3RvcnkgbmFtZSAobm90IHRoZSBwYXRoKSB0byBsaXN0CiMgQGFyZyAkMiBjb25mOlN0cmluZyBmaWxlIHRvIHVzZSB3aXRob3V0IGV4dGVuc2lvbgojIEBhcmcgJDMgZXh0ZW5zaW9uOlN0cmluZyB0aGUgZXh0ZW5zaW9uICguc2ggYnkgZGVmYXVsdCkKIwojIEBzdGRvdXQgYWJzb2x1dGUgY29uZiBmaWxlbmFtZQojIEBleGl0Y29kZSAxIGlmIGZpbGUgaXMgbm90IGZvdW5kIGluIGFueSBsb2NhdGlvbgpDb25mOjpnZXRBYnNvbHV0ZUZpbGUoKSB7CiAgbG9jYWwgY29uZkZvbGRlcj0iJDEiCiAgbG9jYWwgY29uZj0iJDIiCiAgbG9jYWwgZXh0ZW5zaW9uPSIkezMtLnNofSIKICBpZiBbWyAtbiAiJHtleHRlbnNpb259IiAmJiAiJHtleHRlbnNpb246MDoxfSIgIT0gIi4iIF1dOyB0aGVuCiAgICBleHRlbnNpb249Ii4ke2V4dGVuc2lvbn0iCiAgZmkKCiAgdGVzdEFicygpIHsKICAgIGxvY2FsIHJlc3VsdAogICAgcmVzdWx0PSIkKHJlYWxwYXRoIC1lICIkMSIgMj4vZGV2L251bGwpIgogICAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTgxCiAgICBpZiBbWyAiJD8iID0gIjAiICYmIC1mICIke3Jlc3VsdH0iIF1dOyB0aGVuCiAgICAgIGVjaG8gIiR7cmVzdWx0fSIKICAgICAgcmV0dXJuIDAKICAgIGZpCiAgICByZXR1cm4gMQogIH0KCiAgIyBjb25mIGlzIGFic29sdXRlIGZpbGUgKGluY2x1ZGluZyBleHRlbnNpb24pCiAgdGVzdEFicyAiJHtjb25mRm9sZGVyfSR7ZXh0ZW5zaW9ufSIgJiYgcmV0dXJuIDAKICAjIGNvbmYgaXMgYWJzb2x1dGUgZmlsZQogIHRlc3RBYnMgIiR7Y29uZkZvbGRlcn0iICYmIHJldHVybiAwCiAgIyBjb25mIGlzIGFic29sdXRlIGZpbGUgKGluY2x1ZGluZyBleHRlbnNpb24pCiAgdGVzdEFicyAiJHtjb25mfSR7ZXh0ZW5zaW9ufSIgJiYgcmV0dXJuIDAKICAjIGNvbmYgaXMgYWJzb2x1dGUgZmlsZQogIHRlc3RBYnMgIiR7Y29uZn0iICYmIHJldHVybiAwCgogICMgcmVsYXRpdmUgdG8gd2hlcmUgc2NyaXB0IGlzIGV4ZWN1dGVkIChpbmNsdWRpbmcgZXh0ZW5zaW9uKQogIGlmIFtbIC1uICIke0NVUlJFTlRfRElSK3h4eH0iIF1dOyB0aGVuCiAgICB0ZXN0QWJzICIkKEZpbGU6OmNvbmNhdGVuYXRlUGF0aCAiJHtDVVJSRU5UX0RJUn0iICIke2NvbmZGb2xkZXJ9IikvJHtjb25mfSR7ZXh0ZW5zaW9ufSIgJiYgcmV0dXJuIDAKICBmaQogICMgZnJvbSBob21lLy5iYXNoLXRvb2xzLzxjb25mRm9sZGVyPgogIHRlc3RBYnMgIiQoRmlsZTo6Y29uY2F0ZW5hdGVQYXRoICIke0hPTUV9Ly5iYXNoLXRvb2xzIiAiJHtjb25mRm9sZGVyfSIpLyR7Y29uZn0ke2V4dGVuc2lvbn0iICYmIHJldHVybiAwCgogIGlmIFtbIC1uICIke0ZSQU1FV09SS19ST09UX0RJUit4eHh9IiBdXTsgdGhlbgogICAgIyBmcm9tIGZyYW1ld29yayBjb25mLzxjb25mRm9sZGVyPiAoaW5jbHVkaW5nIGV4dGVuc2lvbikKICAgIHRlc3RBYnMgIiQoRmlsZTo6Y29uY2F0ZW5hdGVQYXRoICIke0ZSQU1FV09SS19ST09UX0RJUn0vY29uZiIgIiR7Y29uZkZvbGRlcn0iKS8ke2NvbmZ9JHtleHRlbnNpb259IiAmJiByZXR1cm4gMAoKICAgICMgZnJvbSBmcmFtZXdvcmsgY29uZi88Y29uZkZvbGRlcj4KICAgIHRlc3RBYnMgIiQoRmlsZTo6Y29uY2F0ZW5hdGVQYXRoICIke0ZSQU1FV09SS19ST09UX0RJUn0vY29uZiIgIiR7Y29uZkZvbGRlcn0iKS8ke2NvbmZ9IiAmJiByZXR1cm4gMAogIGZpCgogICMgZmlsZSBub3QgZm91bmQKICBMb2c6OmRpc3BsYXlFcnJvciAiY29uZiBmaWxlICcke2NvbmZ9JyBub3QgZm91bmQiCgogIHJldHVybiAxCn0KCiMgQGRlc2NyaXB0aW9uIGNoZWNrIGlmIGRzbiBmaWxlIGhhcyBhbGwgdGhlIG1hbmRhdG9yeSB2YXJpYWJsZXMgc2V0CiMgTWFuZGF0b3J5IHZhcmlhYmxlcyBhcmU6IEhPU1ROQU1FLCBVU0VSLCBQQVNTV09SRCwgUE9SVAojCiMgQGFyZyAkMSBkc25GaWxlTmFtZTpTdHJpbmcgZHNuIGFic29sdXRlIGZpbGVuYW1lCiMgQHNldCBIT1NUTkFNRSBsb2FkZWQgZnJvbSBkc24gZmlsZQojIEBzZXQgUE9SVCBsb2FkZWQgZnJvbSBkc24gZmlsZQojIEBzZXQgVVNFUiBsb2FkZWQgZnJvbSBkc24gZmlsZQojIEBzZXQgUEFTU1dPUkQgbG9hZGVkIGZyb20gZHNuIGZpbGUKIyBAZXhpdGNvZGUgMCBvbiB2YWxpZCBmaWxlCiMgQGV4aXRjb2RlIDEgaWYgb25lIG9mIHRoZSBwcm9wZXJ0aWVzIG9mIHRoZSBjb25mIGZpbGUgaXMgaW52YWxpZCBvciBpZiBmaWxlIG5vdCBmb3VuZAojIEBzdGRlcnIgbG9nIG91dHB1dCBpZiBlcnJvciBmb3VuZCBpbiBjb25mIGZpbGUKRGF0YWJhc2U6OmNoZWNrRHNuRmlsZSgpIHsKICBsb2NhbCBkc25GaWxlTmFtZT0iJDEiCiAgaWYgW1sgISAtZiAiJHtkc25GaWxlTmFtZX0iIF1dOyB0aGVuCiAgICBMb2c6OmRpc3BsYXlFcnJvciAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gbm90IGZvdW5kIgogICAgcmV0dXJuIDEKICBmaQoKICAoCiAgICB1bnNldCBIT1NUTkFNRSBQT1JUIFBBU1NXT1JEIFVTRVIKICAgICMgc2hlbGxjaGVjayBzb3VyY2U9L3NyYy9EYXRhYmFzZS90ZXN0c0RhdGEvZHNuX3ZhbGlkLmVudgogICAgc291cmNlICIke2RzbkZpbGVOYW1lfSIKICAgIGlmIFtbIC16ICR7SE9TVE5BTUUreH0gXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogSE9TVE5BTUUgbm90IHByb3ZpZGVkIgogICAgICByZXR1cm4gMQogICAgZmkKICAgIGlmIFtbIC16ICIke0hPU1ROQU1FfSIgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5V2FybmluZyAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gOiBIT1NUTkFNRSB2YWx1ZSBub3QgcHJvdmlkZWQiCiAgICBmaQogICAgaWYgW1sgIiR7SE9TVE5BTUV9IiA9ICJsb2NhbGhvc3QiIF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheVdhcm5pbmcgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogY2hlY2sgdGhhdCBIT1NUTkFNRSBzaG91bGQgbm90IGJlIDEyNy4wLjAuMSBpbnN0ZWFkIG9mIGxvY2FsaG9zdCIKICAgIGZpCiAgICBpZiBbWyAteiAiJHtQT1JUK3h9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlFcnJvciAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gOiBQT1JUIG5vdCBwcm92aWRlZCIKICAgICAgcmV0dXJuIDEKICAgIGZpCiAgICBpZiAhIFtbICR7UE9SVH0gPX4gXlswLTldKyQgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogUE9SVCBpbnZhbGlkIgogICAgICByZXR1cm4gMQogICAgZmkKICAgIGlmIFtbIC16ICIke1VTRVIreH0iIF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheUVycm9yICJkc24gZmlsZSAke2RzbkZpbGVOYW1lfSA6IFVTRVIgbm90IHByb3ZpZGVkIgogICAgICByZXR1cm4gMQogICAgZmkKICAgIGlmIFtbIC16ICIke1BBU1NXT1JEK3h9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlFcnJvciAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gOiBQQVNTV09SRCBub3QgcHJvdmlkZWQiCiAgICAgIHJldHVybiAxCiAgICBmaQogICkKfQoKIyBAZGVzY3JpcHRpb24gbG9nIG1lc3NhZ2UgdG8gZmlsZQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmxvZ0ZhdGFsKCkgewogIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1GQVRBTH0iICIkMSIKfQoKIyBAZGVzY3JpcHRpb24gSW50ZXJuYWw6IGNvbW1vbiBsb2cgbWVzc2FnZQojIEBleGFtcGxlIHRleHQKIyAgIFtkYXRlXXxbbGV2ZWxNc2ddfG1lc3NhZ2UKIwojIEBleGFtcGxlIHRleHQKIyAgIDIwMjAtMDEtMTkgMTk6MjA6MjF8RVJST1IgIHxsb2cgZXJyb3IKIyAgIDIwMjAtMDEtMTkgMTk6MjA6MjF8U0tJUFBFRHxsb2cgc2tpcHBlZAojCiMgQGFyZyAkMSBsZXZlbE1zZzpTdHJpbmcgbWVzc2FnZSdzIGxldmVsIGRlc2NyaXB0aW9uIChlZzogU1RBVFVTLCBFUlJPUiwgLi4uKQojIEBhcmcgJDIgbXNnOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CiMgQGVudiBCQVNIX0ZSQU1FV09SS19MT0dfRklMRSBTdHJpbmcgbG9nIGZpbGUgdG8gdXNlLCBkbyBub3RoaW5nIGlmIGVtcHR5CiMgQGVudiBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgaW50IGxvZyBsZXZlbCBsb2cgb25seSBpZiA+IE9GRiBvciBmYXRhbCBtZXNzYWdlcwojIEBzdGRlcnIgZGlhZ25vc3RpY3MgaW5mb3JtYXRpb24gaXMgZGlzcGxheWVkCiMgQHJlcXVpcmUgRW52OjpyZXF1aXJlTG9hZAojIEByZXF1aXJlIExvZzo6cmVxdWlyZUxvYWQKTG9nOjpsb2dNZXNzYWdlKCkgewogIGxvY2FsIGxldmVsTXNnPSIkMSIKICBsb2NhbCBtc2c9IiQyIgogIGxvY2FsIGRhdGUKCiAgaWYgW1sgLW4gIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IiBdXSAmJiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+IF9fTEVWRUxfT0ZGKSk7IHRoZW4KICAgIGRhdGU9IiQoZGF0ZSAnKyVZLSVtLSVkICVIOiVNOiVTJykiCiAgICB0b3VjaCAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iCiAgICBwcmludGYgIiVzfCU3c3wlc1xuIiAiJHtkYXRlfSIgIiR7bGV2ZWxNc2d9IiAiJHttc2d9IiA+PiIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBjb25jYXRlbmF0ZSAyIHBhdGhzIGFuZCBlbnN1cmUgdGhlIHBhdGggaXMgY29ycmVjdCB1c2luZyByZWFscGF0aCAtbQojIEBhcmcgJDEgYmFzZVBhdGg6U3RyaW5nCiMgQGFyZyAkMiBzdWJQYXRoOlN0cmluZwojIEByZXF1aXJlIExpbnV4OjpyZXF1aXJlUmVhbHBhdGhDb21tYW5kCkZpbGU6OmNvbmNhdGVuYXRlUGF0aCgpIHsKICBsb2NhbCBiYXNlUGF0aD0iJDEiCiAgbG9jYWwgc3ViUGF0aD0iJDIiCiAgbG9jYWwgZnVsbFBhdGg9IiR7YmFzZVBhdGg6KyR7YmFzZVBhdGh9L30ke3N1YlBhdGh9IgoKICByZWFscGF0aCAtbSAiJHtmdWxsUGF0aH0iIDI+L2Rldi9udWxsCn0KCiMgQGRlc2NyaXB0aW9uIERpc3BsYXkgbWVzc2FnZSB1c2luZyBlcnJvciBjb2xvciAocmVkKQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlFcnJvcigpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUwgPj0gX19MRVZFTF9FUlJPUikpOyB0aGVuCiAgICBlY2hvIC1lICIke19fRVJST1JfQ09MT1J9RVJST1IgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBmaQogIExvZzo6bG9nRXJyb3IgIiQxIgp9CgojIEBkZXNjcmlwdGlvbiBEaXNwbGF5IG1lc3NhZ2UgdXNpbmcgd2FybmluZyBjb2xvciAoeWVsbG93KQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlXYXJuaW5nKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCA+PSBfX0xFVkVMX1dBUk5JTkcpKTsgdGhlbgogICAgZWNobyAtZSAiJHtfX1dBUk5JTkdfQ09MT1J9V0FSTiAgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBmaQogIExvZzo6bG9nV2FybmluZyAiJDEiCn0KCiMgQGRlc2NyaXB0aW9uIGVuc3VyZSBlbnYgZmlsZXMgYXJlIGxvYWRlZAojIEBhcmcgJEAgbGlzdCBvZiBkZWZhdWx0IGZpbGVzIHRvIGxvYWQgYXQgdGhlIGVuZAojIEBleGl0Y29kZSAxIGlmIG9uZSBvZiBlbnYgZmlsZXMgZmFpbHMgdG8gbG9hZAojIEBzdGRlcnIgZGlhZ25vc3RpY3MgaW5mb3JtYXRpb24gaXMgZGlzcGxheWVkCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjEyMApFbnY6OnJlcXVpcmVMb2FkKCkgewogIGxvY2FsIC1hIGRlZmF1bHRGaWxlcz0oIiRAIikKICAjIGdldCBsaXN0IG9mIHBvc3NpYmxlIGNvbmZpZyBmaWxlcwogIGxvY2FsIC1hIGNvbmZpZ0ZpbGVzPSgpCiAgaWYgW1sgLW4gIiR7QkFTSF9GUkFNRVdPUktfRU5WX0ZJTEVTWzBdKzF9IiBdXTsgdGhlbgogICAgIyBCQVNIX0ZSQU1FV09SS19FTlZfRklMRVMgaXMgYW4gYXJyYXkKICAgIGNvbmZpZ0ZpbGVzKz0oIiR7QkFTSF9GUkFNRVdPUktfRU5WX0ZJTEVTW0BdfSIpCiAgZmkKICBpZiBbWyAtZiAiJChwd2QpLy5mcmFtZXdvcmstY29uZmlnIiBdXTsgdGhlbgogICAgY29uZmlnRmlsZXMrPSgiJChwd2QpLy5mcmFtZXdvcmstY29uZmlnIikKICBmaQogIGlmIFtbIC1mICIke0ZSQU1FV09SS19ST09UX0RJUn0vLmZyYW1ld29yay1jb25maWciIF1dOyB0aGVuCiAgICBjb25maWdGaWxlcys9KCIke0ZSQU1FV09SS19ST09UX0RJUn0vLmZyYW1ld29yay1jb25maWciKQogIGZpCiAgaWYgW1sgLW4gIiR7b3B0aW9uQmFzaEZyYW1ld29ya0NvbmZpZ30iICYmIC1mICIke29wdGlvbkJhc2hGcmFtZXdvcmtDb25maWd9IiBdXTsgdGhlbgogICAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0CiAgICBjb25maWdGaWxlcys9KCIke29wdGlvbkJhc2hGcmFtZXdvcmtDb25maWd9IikKICBmaQogIGNvbmZpZ0ZpbGVzKz0oIiR7b3B0aW9uRW52RmlsZXNbQF19IikKICBjb25maWdGaWxlcys9KCIke2RlZmF1bHRGaWxlc1tAXX0iKQoKICBmb3IgZmlsZSBpbiAiJHtjb25maWdGaWxlc1tAXX0iOyBkbwogICAgIyBzaGVsbGNoZWNrIHNvdXJjZT0vLmZyYW1ld29yay1jb25maWcKICAgIHNvdXJjZSAiJHtmaWxlfSIgfHwgewogICAgICBMb2c6OmRpc3BsYXlFcnJvciAid2hpbGUgbG9hZGluZyBjb25maWcgZmlsZTogJHtmaWxlfSIKICAgICAgcmV0dXJuIDEKICAgIH0KICBkb25lCn0KCiMgQGRlc2NyaXB0aW9uIGFjdGl2YXRlIG9yIG5vdCBMb2c6OmRpc3BsYXkqIGFuZCBMb2c6OmxvZyogZnVuY3Rpb25zCiMgYmFzZWQgb24gQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCBhbmQgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMCiMgZW52aXJvbm1lbnQgdmFyaWFibGVzIGxvYWRlZCBieSBFbnY6OnJlcXVpcmVMb2FkCiMgdHJ5IHRvIGNyZWF0ZSBsb2cgZmlsZSBhbmQgcm90YXRlIGl0IGlmIG5lY2Vzc2FyeQojIEBub2FyZ3MKIyBAc2V0IEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCBpbnQgdG8gT0ZGIGxldmVsIGlmIEJBU0hfRlJBTUVXT1JLX0xPR19GSUxFIGlzIGVtcHR5IG9yIG5vdCB3cml0YWJsZQojIEBlbnYgQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCBpbnQKIyBAZW52IEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCBpbnQKIyBAZW52IEJBU0hfRlJBTUVXT1JLX0xPR19GSUxFIFN0cmluZwojIEBlbnYgQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEVfTUFYX1JPVEFUSU9OIGludCBkbyBsb2cgcm90YXRpb24gaWYgPiAwCiMgQGV4aXRjb2RlIDAgYWx3YXlzIHN1Y2Nlc3NmdWwKIyBAc3RkZXJyIGRpYWdub3N0aWNzIGluZm9ybWF0aW9uIGFib3V0IGxvZyBmaWxlIGlzIGRpc3BsYXllZAojIEByZXF1aXJlIEVudjo6cmVxdWlyZUxvYWQKIyBAcmVxdWlyZSBVSTo6cmVxdWlyZVRoZW1lCkxvZzo6cmVxdWlyZUxvYWQoKSB7CiAgaWYgW1sgLXogIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEU6LX0iIF1dOyB0aGVuCiAgICBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUw9JHtfX0xFVkVMX09GRn0KICAgIGV4cG9ydCBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwKICBmaQoKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+IF9fTEVWRUxfT0ZGKSk7IHRoZW4KICAgIGlmIFtbICEgLWYgIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IiBdXTsgdGhlbgogICAgICBpZgogICAgICAgICEgbWtkaXIgLXAgIiQoZGlybmFtZSAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iKSIgMj4vZGV2L251bGwgfHwKICAgICAgICAgICEgdG91Y2ggLS1uby1jcmVhdGUgIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IiAyPi9kZXYvbnVsbAogICAgICB0aGVuCiAgICAgICAgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMPSR7X19MRVZFTF9PRkZ9CiAgICAgICAgZWNobyAtZSAiJHtfX0VSUk9SX0NPTE9SfUVSUk9SICAgLSBGaWxlICR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IGlzIG5vdCB3cml0YWJsZSR7X19SRVNFVF9DT0xPUn0iID4mMgogICAgICBmaQogICAgZWxpZiBbWyAhIC13ICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSIgXV07IHRoZW4KICAgICAgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMPSR7X19MRVZFTF9PRkZ9CiAgICAgIGVjaG8gLWUgIiR7X19FUlJPUl9DT0xPUn1FUlJPUiAgIC0gRmlsZSAke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSBpcyBub3Qgd3JpdGFibGUke19fUkVTRVRfQ09MT1J9IiA+JjIKICAgIGZpCgogIGZpCgogIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMID4gX19MRVZFTF9PRkYpKTsgdGhlbgogICAgIyB3aWxsIGFsd2F5cyBiZSBjcmVhdGVkIGV2ZW4gaWYgbm90IGluIGluZm8gbGV2ZWwKICAgIExvZzo6bG9nTWVzc2FnZSAiSU5GTyIgIkxvZ2dpbmcgdG8gZmlsZSAke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSAtIExvZyBsZXZlbCAke0JBU0hfRlJBTUVXT1JLX0xPR19MRVZFTH0iCiAgICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19GSUxFX01BWF9ST1RBVElPTiA+IDApKTsgdGhlbgogICAgICBMb2c6OnJvdGF0ZSAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFX01BWF9ST1RBVElPTn0iCiAgICBmaQogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGxvZyBtZXNzYWdlIHRvIGZpbGUKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpsb2dFcnJvcigpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+PSBfX0xFVkVMX0VSUk9SKSk7IHRoZW4KICAgIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1FUlJPUn0iICIkMSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBsb2cgbWVzc2FnZSB0byBmaWxlCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6bG9nV2FybmluZygpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+PSBfX0xFVkVMX1dBUk5JTkcpKTsgdGhlbgogICAgTG9nOjpsb2dNZXNzYWdlICIkezI6LVdBUk5JTkd9IiAiJDEiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gVG8gYmUgY2FsbGVkIGJlZm9yZSBsb2dnaW5nIGluIHRoZSBsb2cgZmlsZQojIEBhcmcgJDEgZmlsZTpzdHJpbmcgbG9nIGZpbGUgbmFtZQojIEBhcmcgJDIgbWF4TG9nRmlsZXNDb3VudDppbnQgbWF4aW11bSBudW1iZXIgb2YgbG9nIGZpbGVzCkxvZzo6cm90YXRlKCkgewogIGxvY2FsIGZpbGU9IiQxIgogIGxvY2FsIG1heExvZ0ZpbGVzQ291bnQ9IiR7MjotNX0iCgogIGlmIFtbICEgLWYgIiR7ZmlsZX0iIF1dOyB0aGVuCiAgICBMb2c6OmRpc3BsYXlTa2lwcGVkICJMb2cgZmlsZSAke2ZpbGV9IGRvZXNuJ3QgZXhpc3QgeWV0IgogICAgcmV0dXJuIDAKICBmaQogIGZvciBpIGluICQoc2VxICQoKG1heExvZ0ZpbGVzQ291bnQgLSAxKSkgLTEgMSk7IGRvCiAgICBMb2c6OmRpc3BsYXlJbmZvICJMb2cgcm90YXRpb24gJHtmaWxlfS4ke2l9IHRvICR7ZmlsZX0uJCgoaSArIDEpKSIKICAgIG12ICIke2ZpbGV9LiJ7IiR7aX0iLCIkKChpICsgMSkpIn0gJj4vZGV2L251bGwgfHwgdHJ1ZQogIGRvbmUKICBpZiBjcCAiJHtmaWxlfSIgIiR7ZmlsZX0uMSIgJj4vZGV2L251bGw7IHRoZW4KICAgIGVjaG8gPiIke2ZpbGV9IiAjIHJlc2V0IGxvZyBmaWxlCiAgICBMb2c6OmRpc3BsYXlJbmZvICJMb2cgcm90YXRpb24gJHtmaWxlfSB0byAke2ZpbGV9LjEiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gZW5zdXJlIGNvbW1hbmQgcmVhbHBhdGggaXMgYXZhaWxhYmxlCiMgQGV4aXRjb2RlIDEgaWYgcmVhbHBhdGggY29tbWFuZCBub3QgYXZhaWxhYmxlCiMgQHN0ZGVyciBkaWFnbm9zdGljcyBpbmZvcm1hdGlvbiBpcyBkaXNwbGF5ZWQKTGludXg6OnJlcXVpcmVSZWFscGF0aENvbW1hbmQoKSB7CiAgQXNzZXJ0Ojpjb21tYW5kRXhpc3RzIHJlYWxwYXRoCn0KCiMgQGRlc2NyaXB0aW9uIGxvYWQgY29sb3IgdGhlbWUKIyBAbm9hcmdzCiMgQGVudiBCQVNIX0ZSQU1FV09SS19USEVNRSBTdHJpbmcgdGhlbWUgdG8gdXNlCiMgQGV4aXRjb2RlIDAgYWx3YXlzIHN1Y2Nlc3NmdWwKVUk6OnJlcXVpcmVUaGVtZSgpIHsKICBVSTo6dGhlbWUgIiR7QkFTSF9GUkFNRVdPUktfVEhFTUUtZGVmYXVsdH0iCn0KCiMgQGRlc2NyaXB0aW9uIGNoZWNrIGlmIGNvbW1hbmQgc3BlY2lmaWVkIGV4aXN0cyBvciByZXR1cm4gMQojIHdpdGggZXJyb3IgYW5kIG1lc3NhZ2UgaWYgbm90CiMKIyBAYXJnICQxIGNvbW1hbmROYW1lOlN0cmluZyBvbiB3aGljaCBleGlzdGVuY2UgbXVzdCBiZSBjaGVja2VkCiMgQGFyZyAkMiBoZWxwSWZOb3RFeGlzdHM6U3RyaW5nIGEgaGVscCBjb21tYW5kIHRvIGRpc3BsYXkgaWYgdGhlIGNvbW1hbmQgZG9lcyBub3QgZXhpc3QKIwojIEBleGl0Y29kZSAxIGlmIHRoZSBjb21tYW5kIHNwZWNpZmllZCBkb2VzIG5vdCBleGlzdAojIEBzdGRlcnIgZGlhZ25vc3RpYyBpbmZvcm1hdGlvbiArIGhlbHAgaWYgc2Vjb25kIGFyZ3VtZW50IGlzIHByb3ZpZGVkCkFzc2VydDo6Y29tbWFuZEV4aXN0cygpIHsKICBsb2NhbCBjb21tYW5kTmFtZT0iJDEiCiAgbG9jYWwgaGVscElmTm90RXhpc3RzPSIkMiIKCiAgIiR7QkFTSF9GUkFNRVdPUktfQ09NTUFORDotY29tbWFuZH0iIC12ICIke2NvbW1hbmROYW1lfSIgPi9kZXYvbnVsbCAyPi9kZXYvbnVsbCB8fCB7CiAgICBMb2c6OmRpc3BsYXlFcnJvciAiJHtjb21tYW5kTmFtZX0gaXMgbm90IGluc3RhbGxlZCwgcGxlYXNlIGluc3RhbGwgaXQiCiAgICBpZiBbWyAtbiAiJHtoZWxwSWZOb3RFeGlzdHN9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlJbmZvICIke2hlbHBJZk5vdEV4aXN0c30iCiAgICBmaQogICAgcmV0dXJuIDEKICB9CiAgcmV0dXJuIDAKfQoKIyBAZGVzY3JpcHRpb24gRGlzcGxheSBtZXNzYWdlIHVzaW5nIHNraXAgY29sb3IgKHllbGxvdykKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpkaXNwbGF5U2tpcHBlZCgpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUwgPj0gX19MRVZFTF9JTkZPKSk7IHRoZW4KICAgIGVjaG8gLWUgIiR7X19TS0lQUEVEX0NPTE9SfVNLSVBQRUQgLSAkezF9JHtfX1JFU0VUX0NPTE9SfSIgPiYyCiAgZmkKICBMb2c6OmxvZ1NraXBwZWQgIiQxIgp9CgojIEBkZXNjcmlwdGlvbiBsb2FkIGNvbG9ycyB0aGVtZSBjb25zdGFudHMKIyBAd2FybmluZyBpZiB0dHkgbm90IG9wZW5lZCwgbm9Db2xvciB0aGVtZSB3aWxsIGJlIGNob3NlbgojIEBhcmcgJDEgdGhlbWU6U3RyaW5nIHRoZSB0aGVtZSB0byB1c2UgKGRlZmF1bHQsIG5vQ29sb3IpCiMgQGFyZyAkQCBhcmdzOlN0cmluZ1tdCiMgQHNldCBfX0VSUk9SX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSBlcnJvciBzdGF0dXMKIyBAc2V0IF9fSU5GT19DT0xPUiBTdHJpbmcgaW5kaWNhdGUgaW5mbyBzdGF0dXMKIyBAc2V0IF9fU1VDQ0VTU19DT0xPUiBTdHJpbmcgaW5kaWNhdGUgc3VjY2VzcyBzdGF0dXMKIyBAc2V0IF9fV0FSTklOR19DT0xPUiBTdHJpbmcgaW5kaWNhdGUgd2FybmluZyBzdGF0dXMKIyBAc2V0IF9fU0tJUFBFRF9DT0xPUiBTdHJpbmcgaW5kaWNhdGUgc2tpcHBlZCBzdGF0dXMKIyBAc2V0IF9fREVCVUdfQ09MT1IgU3RyaW5nIGluZGljYXRlIGRlYnVnIHN0YXR1cwojIEBzZXQgX19IRUxQX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSBoZWxwIHN0YXR1cwojIEBzZXQgX19URVNUX0NPTE9SIFN0cmluZyBub3QgdXNlZAojIEBzZXQgX19URVNUX0VSUk9SX0NPTE9SIFN0cmluZyBub3QgdXNlZAojIEBzZXQgX19IRUxQX1RJVExFX0NPTE9SIFN0cmluZyB1c2VkIHRvIGRpc3BsYXkgaGVscCB0aXRsZSBpbiBoZWxwIHN0cmluZ3MKIyBAc2V0IF9fSEVMUF9PUFRJT05fQ09MT1IgU3RyaW5nIHVzZWQgdG8gZGlzcGxheSBoaWdobGlnaHQgb3B0aW9ucyBpbiBoZWxwIHN0cmluZ3MKIwojIEBzZXQgX19SRVNFVF9DT0xPUiBTdHJpbmcgcmVzZXQgZGVmYXVsdCBjb2xvcgojCiMgQHNldCBfX0hFTFBfRVhBTVBMRSBTdHJpbmcgdG8gcmVtb3ZlCiMgQHNldCBfX0hFTFBfVElUTEUgU3RyaW5nIHRvIHJlbW92ZQojIEBzZXQgX19IRUxQX05PUk1BTCBTdHJpbmcgdG8gcmVtb3ZlCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNApVSTo6dGhlbWUoKSB7CiAgbG9jYWwgdGhlbWU9IiR7MS1kZWZhdWx0fSIKICBpZiBbWyAhICIke3RoZW1lfSIgPX4gLWZvcmNlJCBdXSAmJiAhIEFzc2VydDo6dHR5OyB0aGVuCiAgICB0aGVtZT0ibm9Db2xvciIKICBmaQogIGNhc2UgIiR7dGhlbWV9IiBpbgogICAgZGVmYXVsdCB8IGRlZmF1bHQtZm9yY2UpCiAgICAgIHRoZW1lPSJkZWZhdWx0IgogICAgICA7OwogICAgbm9Db2xvcikgOzsKICAgICopCiAgICAgIExvZzo6ZmF0YWwgImludmFsaWQgdGhlbWUgcHJvdmlkZWQiCiAgICAgIDs7CiAgZXNhYwogIGlmIFtbICIke3RoZW1lfSIgPSAiZGVmYXVsdCIgXV07IHRoZW4KICAgIEJBU0hfRlJBTUVXT1JLX1RIRU1FPSJkZWZhdWx0IgogICAgIyBjaGVjayBjb2xvcnMgYXBwbGljYWJsZSBodHRwczovL21pc2MuZmxvZ2lzb2Z0LmNvbS9iYXNoL3RpcF9jb2xvcnNfYW5kX2Zvcm1hdHRpbmcKICAgIF9fRVJST1JfQ09MT1I9J1xlWzMxbScgICAgICAgICAjIFJlZAogICAgX19JTkZPX0NPTE9SPSdcZVs0NG0nICAgICAgICAgICMgd2hpdGUgb24gbGlnaHRCbHVlCiAgICBfX1NVQ0NFU1NfQ09MT1I9J1xlWzMybScgICAgICAgIyBHcmVlbgogICAgX19XQVJOSU5HX0NPTE9SPSdcZVszM20nICAgICAgICMgWWVsbG93CiAgICBfX1NLSVBQRURfQ09MT1I9J1xlWzMzbScgICAgICAgIyBZZWxsb3cKICAgIF9fREVCVUdfQ09MT1I9J1xlWzM3bScgICAgICAgICAjIEdyZXkKICAgIF9fSEVMUF9DT0xPUj0nXGVbNzs0OTszM20nICAgICAjIEJsYWNrIG9uIEdvbGQKICAgIF9fVEVTVF9DT0xPUj0nXGVbMTAwbScgICAgICAgICAjIExpZ2h0IG1hZ2VudGEKICAgIF9fVEVTVF9FUlJPUl9DT0xPUj0nXGVbNDFtJyAgICAjIHdoaXRlIG9uIHJlZAogICAgX19IRUxQX1RJVExFX0NPTE9SPSJcZVsxOzM3bSIgICMgQm9sZAogICAgX19IRUxQX09QVElPTl9DT0xPUj0iXGVbMTszNG0iICMgQmx1ZQogICAgIyBJbnRlcm5hbDogcmVzZXQgY29sb3IKICAgIF9fUkVTRVRfQ09MT1I9J1xlWzBtJyAjIFJlc2V0IENvbG9yCiAgICAjIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIxNTUsU0MyMDM0CiAgICBfX0hFTFBfRVhBTVBMRT0iJChlY2hvIC1lICJcZVsyOzk3bSIpIgogICAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTU1LFNDMjAzNAogICAgX19IRUxQX1RJVExFPSIkKGVjaG8gLWUgIlxlWzE7MzdtIikiCiAgICAjIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIxNTUsU0MyMDM0CiAgICBfX0hFTFBfTk9STUFMPSIkKGVjaG8gLWUgIlwwMzNbMG0iKSIKICBlbHNlCiAgICBCQVNIX0ZSQU1FV09SS19USEVNRT0ibm9Db2xvciIKICAgICMgY2hlY2sgY29sb3JzIGFwcGxpY2FibGUgaHR0cHM6Ly9taXNjLmZsb2dpc29mdC5jb20vYmFzaC90aXBfY29sb3JzX2FuZF9mb3JtYXR0aW5nCiAgICBfX0VSUk9SX0NPTE9SPScnCiAgICBfX0lORk9fQ09MT1I9JycKICAgIF9fU1VDQ0VTU19DT0xPUj0nJwogICAgX19XQVJOSU5HX0NPTE9SPScnCiAgICBfX1NLSVBQRURfQ09MT1I9JycKICAgIF9fREVCVUdfQ09MT1I9JycKICAgIF9fSEVMUF9DT0xPUj0nJwogICAgX19URVNUX0NPTE9SPScnCiAgICBfX1RFU1RfRVJST1JfQ09MT1I9JycKICAgIF9fSEVMUF9USVRMRV9DT0xPUj0nJwogICAgX19IRUxQX09QVElPTl9DT0xPUj0nJwogICAgIyBJbnRlcm5hbDogcmVzZXQgY29sb3IKICAgIF9fUkVTRVRfQ09MT1I9JycKICAgIF9fSEVMUF9FWEFNUExFPScnCiAgICBfX0hFTFBfVElUTEU9JycKICAgIF9fSEVMUF9OT1JNQUw9JycKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBjaGVjayBpZiB0dHkgKGludGVyYWN0aXZlIG1vZGUpIGlzIGFjdGl2ZQojIEBub2FyZ3MKIyBAZXhpdGNvZGUgMSBpZiB0dHkgbm90IGFjdGl2ZQojIEBlbnYgTk9OX0lOVEVSQUNUSVZFIGlmIDEgY29uc2lkZXIgYXMgbm90IGludGVyYWN0aXZlIGV2ZW4gaWYgZW52aXJvbm1lbnQgaXMgaW50ZXJhY3RpdmUKIyBAZW52IElOVEVSQUNUSVZFIGlmIDEgY29uc2lkZXIgYXMgaW50ZXJhY3RpdmUgZXZlbiBpZiBlbnZpcm9ubWVudCBpcyBub3QgaW50ZXJhY3RpdmUKIyBAc3RkZXJyIGRpYWdub3N0aWMgaW5mb3JtYXRpb24gKyBoZWxwIGlmIHNlY29uZCBhcmd1bWVudCBpcyBwcm92aWRlZApBc3NlcnQ6OnR0eSgpIHsKICBpZiBbWyAiJHtOT05fSU5URVJBQ1RJVkU6LTB9IiA9ICIxIiBdXTsgdGhlbgogICAgcmV0dXJuIDEKICBmaQogIGlmIFtbICIke0lOVEVSQUNUSVZFOi0wfSIgPSAiMSIgXV07IHRoZW4KICAgIHJldHVybiAwCiAgZmkKICBbWyAtdCAxIHx8IC10IDIgXV0KfQoKIyBAZGVzY3JpcHRpb24gbG9nIG1lc3NhZ2UgdG8gZmlsZQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmxvZ1NraXBwZWQoKSB7CiAgaWYgKChCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgPj0gX19MRVZFTF9JTkZPKSk7IHRoZW4KICAgIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1TS0lQUEVEfSIgIiQxIgogIGZpCn0KCiMgRlVOQ1RJT05TCgpmYWNhZGVfbWFpbl9lbWJlZEZyYW1ld29ya0Z1bmN0aW9uYmluRmlsZXRwbCgpIHsKIyBSRVFVSVJFUwpMaW51eDo6cmVxdWlyZUV4ZWN1dGVkQXNVc2VyCkVudjo6cmVxdWlyZUxvYWQKTG9nOjpyZXF1aXJlTG9hZApMaW51eDo6cmVxdWlyZVJlYWxwYXRoQ29tbWFuZApVSTo6cmVxdWlyZVRoZW1lCkNvbXBpbGVyOjpGYWNhZGU6OnJlcXVpcmVDb21tYW5kQmluRGlyCgojIEByZXF1aXJlIENvbXBpbGVyOjpGYWNhZGU6OnJlcXVpcmVDb21tYW5kQmluRGlyCgojIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIxNTQsU0MyMDE2CmZ1bmN0aW9uVG9DYWxsPSdEYjo6cXVlcnlPbmVEYXRhYmFzZScKIiR7ZnVuY3Rpb25Ub0NhbGx9IiAiJEAiCgp9CgpmYWNhZGVfbWFpbl9lbWJlZEZyYW1ld29ya0Z1bmN0aW9uYmluRmlsZXRwbCAiJEAiCg==" Compiler::Embed::extractFileFromBase64 \ "${embed_function_DbQueryOneDatabase}" \ @@ -1398,6 +1193,7 @@ optionVersionCallback() { # shellcheck disable=SC2317 # if function is overridden optionEnvFileCallback() { local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" exit 1 @@ -1532,54 +1328,47 @@ defaultFrameworkConfig="$( # copied from src/_includes/.framework-config.default # shellcheck disable=SC2034 +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")/../.." && pwd -P)}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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)" +NON_FRAMEWORK_FILES_REGEXP="${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)" +BATS_FILE_NOT_NEEDED_REGEXP="${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$" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|\.tpl$|/testsData/|^manualTests/|\.bats$}" # Source directories -FRAMEWORK_SRC_DIRS=( - "${FRAMEWORK_ROOT_DIR}/src" -) +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi # export here all the variables that will be used in your templates -export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/$(basename "$0").log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" 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 default template framework config + # shellcheck disable=SC2034 + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" > "${defaultEnvFile}" - # 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 + Env::requireLoad "${defaultEnvFile}" + Log::requireLoad if [[ "${optionConfig}" = "1" ]]; then displayConfig @@ -2050,7 +1839,7 @@ dbQueryAllDatabasesCommand() { echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file) + helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray diff --git a/bin/dbScriptAllDatabases b/bin/dbScriptAllDatabases index 3a6f5f7b..26915d32 100755 --- a/bin/dbScriptAllDatabases +++ b/bin/dbScriptAllDatabases @@ -440,42 +440,38 @@ Database::setQueryOptions() { } # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") + fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } -} - -# @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 "$@" + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description Log namespace provides 2 kind of functions @@ -769,41 +765,6 @@ Conf::list() { ) } -# @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" -# -# @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 # @@ -894,87 +855,6 @@ Database::query() { fi } -# @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 -} - -# @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 -} - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -1012,15 +892,6 @@ Filters::removeAnsiCodes() { # cspell:enable } -# @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" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -1115,81 +986,13 @@ 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 -# -# @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 [[ -f "${fromPath}" ]]; then - fromPath="$(dirname "${fromPath}")" - fi - while true; do - if [[ -f "${fromPath}/${fileName}" ]]; then - echo "${fromPath}/${fileName}" - return 0 - fi - if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then - return 1 - fi - fromPath="$(readlink -f "${fromPath}"/..)" - done - return 1 -} - -# @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 -} - -# @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" -} - -# @description log message to file +# @description Display message using skip color (yellow) # @arg $1 message:String the message to display -Log::logSkipped() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO)); then - Log::logMessage "${2:-SKIPPED}" "$1" +Log::displaySkipped() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO)); then + echo -e "${__SKIPPED_COLOR}SKIPPED - ${1}${__RESET_COLOR}" >&2 fi + Log::logSkipped "$1" } # @description ensure command realpath is available @@ -1199,28 +1002,20 @@ Linux::requireRealpathCommand() { Assert::commandExists realpath } -# @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 +# @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 } # FUNCTIONS # @require Compiler::Embed::requireEmbedBinDir -declare -gx embed_function_DbQueryOneDatabase="${PERSISTENT_TMPDIR:-/tmp}/bin/db083cb53bb15c706d27273bb2620268/dbQueryOneDatabase" -declare -gx encoded_binary_file_DbQueryOneDatabase="IyEvdXNyL2Jpbi9lbnYgYmFzaAojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgR0VORVJBVEVEIEZBQ0FERSBGUk9NIGh0dHBzOi8vZ2l0aHViLmNvbS9mY2hhc3RhbmV0L2Jhc2gtdG9vbHMvdHJlZS9tYXN0ZXIvLi4vYmFzaC1kZXYtZW52L3ZlbmRvci9iYXNoLXRvb2xzLWZyYW1ld29yay9zcmMvQ29tcGlsZXIvRW1iZWQvZW1iZWRGcmFtZXdvcmtGdW5jdGlvbi5iaW5GaWxlLnRwbAojIERPIE5PVCBFRElUIElUCiMgQGdlbmVyYXRlZAojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjI4OCxTQzIwMzQKIyBCSU5fRklMRT0ke0JJTl9GSUxFfQojIEZBQ0FERQoKIyBlbnN1cmUgdGhhdCBubyB1c2VyIGFsaWFzZXMgY291bGQgaW50ZXJmZXJlIHdpdGgKIyBjb21tYW5kcyB1c2VkIGluIHRoaXMgc2NyaXB0CnVuYWxpYXMgLWEgfHwgdHJ1ZQpzaG9wdCAtdSBleHBhbmRfYWxpYXNlcwoKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0CigoZmFpbHVyZXMgPSAwKSkgfHwgdHJ1ZQoKIyBCYXNoIHdpbGwgcmVtZW1iZXIgJiByZXR1cm4gdGhlIGhpZ2hlc3QgZXhpdCBjb2RlIGluIGEgY2hhaW4gb2YgcGlwZXMuCiMgVGhpcyB3YXkgeW91IGNhbiBjYXRjaCB0aGUgZXJyb3IgaW5zaWRlIHBpcGVzLCBlLmcuIG15c3FsZHVtcCB8IGd6aXAKc2V0IC1vIHBpcGVmYWlsCnNldCAtbyBlcnJleGl0CgojIENvbW1hbmQgU3Vic3RpdHV0aW9uIGNhbiBpbmhlcml0IGVycmV4aXQgb3B0aW9uIHNpbmNlIGJhc2ggdjQuNApzaG9wdCAtcyBpbmhlcml0X2VycmV4aXQgfHwgdHJ1ZQoKIyBhIGxvZyBpcyBnZW5lcmF0ZWQgd2hlbiBhIGNvbW1hbmQgZmFpbHMKc2V0IC1vIGVycnRyYWNlCgojIHVzZSBudWxsZ2xvYiBzbyB0aGF0IChmaWxlKi5waHApIHdpbGwgcmV0dXJuIGFuIGVtcHR5IGFycmF5IGlmIG5vIGZpbGUgbWF0Y2hlcyB0aGUgd2lsZGNhcmQKc2hvcHQgLXMgbnVsbGdsb2IKCiMgZW5zdXJlIHJlZ2V4cCBhcmUgaW50ZXJwcmV0ZWQgd2l0aG91dCBhY2NlbnR1YXRlZCBjaGFyYWN0ZXJzCmV4cG9ydCBMQ19BTEw9UE9TSVgKCmV4cG9ydCBURVJNPXh0ZXJtLTI1NmNvbG9yCgojIGF2b2lkIGludGVyYWN0aXZlIGluc3RhbGwKZXhwb3J0IERFQklBTl9GUk9OVEVORD1ub25pbnRlcmFjdGl2ZQpleHBvcnQgREVCQ09ORl9OT05JTlRFUkFDVElWRV9TRUVOPXRydWUKCiMgc3RvcmUgY29tbWFuZCBhcmd1bWVudHMgZm9yIGxhdGVyIHVzYWdlCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNApkZWNsYXJlIC1hIEJBU0hfRlJBTUVXT1JLX0FSR1Y9KCIkQCIpCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNApkZWNsYXJlIC1hIE9SSUdJTkFMX0JBU0hfRlJBTUVXT1JLX0FSR1Y9KCIkQCIpCgojIEBzZWUgaHR0cHM6Ly91bml4LnN0YWNrZXhjaGFuZ2UuY29tL2EvMzg2ODU2CiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjMxNwppbnRlcnJ1cHRNYW5hZ2VtZW50KCkgewogICMgcmVzdG9yZSBTSUdJTlQgaGFuZGxlcgogIHRyYXAgLSBJTlQKICAjIGVuc3VyZSB0aGF0IEN0cmwtQyBpcyB0cmFwcGVkIGJ5IHRoaXMgc2NyaXB0IGFuZCBub3QgYnkgc3ViIHByb2Nlc3MKICAjIHJlcG9ydCB0byB0aGUgcGFyZW50IHRoYXQgd2UgaGF2ZSBpbmRlZWQgYmVlbiBpbnRlcnJ1cHRlZAogIGtpbGwgLXMgSU5UICIkJCIKfQp0cmFwIGludGVycnVwdE1hbmFnZW1lbnQgSU5UClNDUklQVF9OQU1FPSR7MCMjKi99ClJFQUxfU0NSSVBUX0ZJTEU9IiQocmVhZGxpbmsgLWUgIiQocmVhbHBhdGggIiR7QkFTSF9TT1VSQ0VbMF19IikiKSIKaWYgW1sgLW4gIiR7RU1CRURfQ1VSUkVOVF9ESVJ9IiBdXTsgdGhlbgogIENVUlJFTlRfRElSPSIke0VNQkVEX0NVUlJFTlRfRElSfSIKZWxzZQogIENVUlJFTlRfRElSPSIkKGNkICIkKHJlYWRsaW5rIC1lICIke1JFQUxfU0NSSVBUX0ZJTEUlLyp9IikiICYmIHB3ZCAtUCkiCmZpCgojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKIyBUZW1wIGRpciBtYW5hZ2VtZW50CiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKS0VFUF9URU1QX0ZJTEVTPSIke0tFRVBfVEVNUF9GSUxFUzotMH0iCmV4cG9ydCBLRUVQX1RFTVBfRklMRVMKCiMgUEVSU0lTVEVOVF9UTVBESVIgaXMgbm90IGRlbGV0ZWQgYnkgdHJhcHMKUEVSU0lTVEVOVF9UTVBESVI9IiR7VE1QRElSOi0vdG1wfS9iYXNoLWZyYW1ld29yayIKZXhwb3J0IFBFUlNJU1RFTlRfVE1QRElSCm1rZGlyIC1wICIke1BFUlNJU1RFTlRfVE1QRElSfSIKCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNApUTVBESVI9IiQobWt0ZW1wIC1kIC1wICIke1BFUlNJU1RFTlRfVE1QRElSOi0vdG1wfSIgLXQgYmFzaC1mcmFtZXdvcmstJCQtWFhYWFhYKSIKZXhwb3J0IFRNUERJUgoKIyB0ZW1wIGRpciBjbGVhbmluZwojIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIzMTcKY2xlYW5PbkV4aXQoKSB7CiAgaWYgW1sgIiR7S0VFUF9URU1QX0ZJTEVTOi0wfSIgPSAiMSIgXV07IHRoZW4KICAgIExvZzo6ZGlzcGxheUluZm8gIktFRVBfVEVNUF9GSUxFUz0xIHRlbXAgZmlsZXMga2VwdCBoZXJlICcke1RNUERJUn0nIgogIGVsaWYgW1sgLW4gIiR7VE1QRElSK3h4eH0iIF1dOyB0aGVuCiAgICBMb2c6OmRpc3BsYXlEZWJ1ZyAiS0VFUF9URU1QX0ZJTEVTPTAgcmVtb3ZpbmcgdGVtcCBmaWxlcyAnJHtUTVBESVJ9JyIKICAgIHJtIC1SZiAiJHtUTVBESVI6LS90bXAvZmFrZX0iID4vZGV2L251bGwgMj4mMQogIGZpCn0KdHJhcCBjbGVhbk9uRXhpdCBFWElUIEhVUCBRVUlUIEFCUlQgVEVSTQoKIyBWQVJfTUFJTl9GVU5DVElPTl9WQVJfTkFNRT1kYlF1ZXJ5QWxsRGF0YWJhc2VzRmFjYWRlCgojIEBkZXNjcmlwdGlvbiB1c2VkIHRvIGV4ZWN1dGUgZ2l2ZW4gcXVlcnkgd2hlbiB1c2luZwojIGRiU2NyaXB0QWxsRGF0YWJhc2VzCiMgQGFyZyAkMSBkc246U3RyaW5nCiMgQGFyZyAkMiBkYjpTdHJpbmcKIyBAZW52IHF1ZXJ5IFN0cmluZwojIEBlbnYgb3B0aW9uU2VwYXJhdG9yIFN0cmluZwojIEByZXF1aXJlIExpbnV4OjpyZXF1aXJlRXhlY3V0ZWRBc1VzZXIKRGI6OnF1ZXJ5T25lRGF0YWJhc2UoKSB7CiAgIyBxdWVyeSBhbmQgb3B0aW9uU2VwYXJhdG9yIGFyZSBwYXNzZWQgdmlhIGV4cG9ydAogIGxvY2FsIGRzbj0iJDEiCiAgbG9jYWwgZGI9IiQyIgoKICBsb2NhbCAtQSBkYkluc3RhbmNlCiAgRGF0YWJhc2U6Om5ld0luc3RhbmNlIGRiSW5zdGFuY2UgIiR7ZHNufSIKICBEYXRhYmFzZTo6c2V0UXVlcnlPcHRpb25zIGRiSW5zdGFuY2UgIiR7ZGJJbnN0YW5jZVtRVUVSWV9PUFRJT05TXX0gLS1jb25uZWN0LXRpbWVvdXQ9NSIKCiAgIyBpZGVudGlmeSBjb2x1bW5zIGhlYWRlcgogIGVjaG8gLW4gIkBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAIgogIERhdGFiYXNlOjpza2lwQ29sdW1uTmFtZXMgZGJJbnN0YW5jZSAwCgogICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjE1NAogIGlmICEgRGF0YWJhc2U6OnF1ZXJ5IGRiSW5zdGFuY2UgIiR7cXVlcnl9IiAiJHtkYn0iIHwgc2VkICJzL1x0LyR7b3B0aW9uU2VwYXJhdG9yfS9nIjsgdGhlbgogICAgTG9nOjpmYXRhbCAiZGF0YWJhc2UgJHtkYn0gZXJyb3IiIDE+JjIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBMb2cgbmFtZXNwYWNlIHByb3ZpZGVzIDIga2luZCBvZiBmdW5jdGlvbnMKIyAtIExvZzo6ZGlzcGxheSogYWxsb3dzIHRvIGRpc3BsYXkgZ2l2ZW4gbWVzc2FnZSB3aXRoCiMgICBnaXZlbiBkaXNwbGF5IGxldmVsCiMgLSBMb2c6OmxvZyogYWxsb3dzIHRvIGxvZyBnaXZlbiBtZXNzYWdlIHdpdGgKIyAgIGdpdmVuIGxvZyBsZXZlbAojIExvZzo6ZGlzcGxheSogZnVuY3Rpb25zIGF1dG9tYXRpY2FsbHkgbG9nIHRoZSBtZXNzYWdlIHRvbwojIEBzZWUgRW52OjpyZXF1aXJlTG9hZCB0byBsb2FkIHRoZSBkaXNwbGF5IGFuZCBsb2cgbGV2ZWwgZnJvbSAuZW52IGZpbGUKCiMgQGRlc2NyaXB0aW9uIGxvZyBsZXZlbCBvZmYKZXhwb3J0IF9fTEVWRUxfT0ZGPTAKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIGVycm9yCmV4cG9ydCBfX0xFVkVMX0VSUk9SPTEKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIHdhcm5pbmcKZXhwb3J0IF9fTEVWRUxfV0FSTklORz0yCiMgQGRlc2NyaXB0aW9uIGxvZyBsZXZlbCBpbmZvCmV4cG9ydCBfX0xFVkVMX0lORk89MwojIEBkZXNjcmlwdGlvbiBsb2cgbGV2ZWwgc3VjY2VzcwpleHBvcnQgX19MRVZFTF9TVUNDRVNTPTMKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIGRlYnVnCmV4cG9ydCBfX0xFVkVMX0RFQlVHPTQKCiMgQGRlc2NyaXB0aW9uIHZlcmJvc2UgbGV2ZWwgb2ZmCmV4cG9ydCBfX1ZFUkJPU0VfTEVWRUxfT0ZGPTAKIyBAZGVzY3JpcHRpb24gdmVyYm9zZSBsZXZlbCBpbmZvCmV4cG9ydCBfX1ZFUkJPU0VfTEVWRUxfSU5GTz0xCiMgQGRlc2NyaXB0aW9uIHZlcmJvc2UgbGV2ZWwgaW5mbwpleHBvcnQgX19WRVJCT1NFX0xFVkVMX0RFQlVHPTIKIyBAZGVzY3JpcHRpb24gdmVyYm9zZSBsZXZlbCBpbmZvCmV4cG9ydCBfX1ZFUkJPU0VfTEVWRUxfVFJBQ0U9MwoKIyBAZGVzY3JpcHRpb24gRGlzcGxheSBtZXNzYWdlIHVzaW5nIGRlYnVnIGNvbG9yIChncmV5KQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlEZWJ1ZygpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUwgPj0gX19MRVZFTF9ERUJVRykpOyB0aGVuCiAgICBlY2hvIC1lICIke19fREVCVUdfQ09MT1J9REVCVUcgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBmaQogIExvZzo6bG9nRGVidWcgIiQxIgp9CgojIEBkZXNjcmlwdGlvbiBEaXNwbGF5IG1lc3NhZ2UgdXNpbmcgaW5mbyBjb2xvciAoYmcgbGlnaHQgYmx1ZS9mZyB3aGl0ZSkKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpkaXNwbGF5SW5mbygpIHsKICBsb2NhbCB0eXBlPSIkezI6LUlORk99IgogIGlmICgoQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCA+PSBfX0xFVkVMX0lORk8pKTsgdGhlbgogICAgZWNobyAtZSAiJHtfX0lORk9fQ09MT1J9JHt0eXBlfSAgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBmaQogIExvZzo6bG9nSW5mbyAiJDEiICIke3R5cGV9Igp9CgojIEBkZXNjcmlwdGlvbiBlbnN1cmUgQ09NTUFORF9CSU5fRElSIGVudiB2YXIgaXMgc2V0CiMgYW5kIFBBVEggY29ycmVjdGx5IHByZXBhcmVkCiMgQG5vYXJncwojIEBzZXQgQ09NTUFORF9CSU5fRElSIHN0cmluZyB0aGUgZGlyZWN0b3J5IHdoZXJlIHRvIGZpbmQgdGhpcyBjb21tYW5kCiMgQHNldCBQQVRIIHN0cmluZyBhZGQgZGlyZWN0b3J5IHdoZXJlIHRvIGZpbmQgdGhpcyBjb21tYW5kIGJpbmFyeQpDb21waWxlcjo6RmFjYWRlOjpyZXF1aXJlQ29tbWFuZEJpbkRpcigpIHsKICBDT01NQU5EX0JJTl9ESVI9IiR7Q1VSUkVOVF9ESVJ9IgogIEVudjo6cGF0aFByZXBlbmQgIiR7Q09NTUFORF9CSU5fRElSfSIKfQoKIyBAZGVzY3JpcHRpb24gY3JlYXRlIGEgbmV3IGRiIGluc3RhbmNlCiMgUmV0dXJucyBpbW1lZGlhdGVseSBpZiB0aGUgaW5zdGFuY2UgaXMgYWxyZWFkeSBpbml0aWFsaXplZAojCiMgQGFyZyAkMSBpbnN0YW5jZU5ld0luc3RhbmNlOiZNYXA8U3RyaW5nLFN0cmluZz4gKHBhc3NlZCBieSByZWZlcmVuY2UpIGRhdGFiYXNlIGluc3RhbmNlIHRvIHVzZQojIEBhcmcgJDIgZHNuOlN0cmluZyBkc24gcHJvZmlsZSAtIGxvYWQgdGhlIGRzbi5lbnYgcHJvZmlsZSBkZWR1Y2VkIHVzaW5nIHJ1bGVzIGRlZmluZWQgaW4gQ29uZjo6Z2V0QWJzb2x1dGVGaWxlCiMKIyBAZXhhbXBsZQojICAgZGVjbGFyZSAtQWd4IGRiSW5zdGFuY2UKIyAgIERhdGFiYXNlOjpuZXdJbnN0YW5jZSBkYkluc3RhbmNlICJkZWZhdWx0LmxvY2FsIgojCiMgQGV4aXRjb2RlIDEgaWYgZG5zIGZpbGUgbm90IGFibGUgdG8gbG9hZGVkCkRhdGFiYXNlOjpuZXdJbnN0YW5jZSgpIHsKICBsb2NhbCAtbiBpbnN0YW5jZU5ld0luc3RhbmNlPSQxCiAgbG9jYWwgZHNuPSIkMiIKICBsb2NhbCBEU05fRklMRQoKICBpZiBbWyAtdiBpbnN0YW5jZU5ld0luc3RhbmNlWydJTklUSUFMSVpFRCddICYmICIke2luc3RhbmNlTmV3SW5zdGFuY2VbJ0lOSVRJQUxJWkVEJ106LTB9IiA9PSAiMSIgXV07IHRoZW4KICAgIHJldHVybgogIGZpCgogICMgZmluYWwgYXV0aCBmaWxlIGdlbmVyYXRlZCBmcm9tIGRucyBmaWxlCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnQVVUSF9GSUxFJ109IiIKICBpbnN0YW5jZU5ld0luc3RhbmNlWydEU05fRklMRSddPSIiCgogICMgY2hlY2sgZHNuIGZpbGUKICBEU05fRklMRT0iJChDb25mOjpnZXRBYnNvbHV0ZUZpbGUgImRzbiIgIiR7ZHNufSIgImVudiIpIiB8fCByZXR1cm4gMQogIERhdGFiYXNlOjpjaGVja0RzbkZpbGUgIiR7RFNOX0ZJTEV9IiB8fCByZXR1cm4gMQogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0RTTl9GSUxFJ109IiR7RFNOX0ZJTEV9IgoKICAjIHNoZWxsY2hlY2sgc291cmNlPS9zcmMvRGF0YWJhc2UvdGVzdHNEYXRhL2Rzbl92YWxpZC5lbnYKICBzb3VyY2UgIiR7aW5zdGFuY2VOZXdJbnN0YW5jZVsnRFNOX0ZJTEUnXX0iCgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1VTRVInXT0iJHtVU0VSfSIKICBpbnN0YW5jZU5ld0luc3RhbmNlWydQQVNTV09SRCddPSIke1BBU1NXT1JEfSIKICBpbnN0YW5jZU5ld0luc3RhbmNlWydIT1NUTkFNRSddPSIke0hPU1ROQU1FfSIKICBpbnN0YW5jZU5ld0luc3RhbmNlWydQT1JUJ109IiR7UE9SVH0iCgogICMgZ2VuZXJhdGUgYXV0aEZpbGUgZm9yIGVhc3kgYXV0aGVudGljYXRpb24KICBpbnN0YW5jZU5ld0luc3RhbmNlWydBVVRIX0ZJTEUnXT0kKG1rdGVtcCAtcCAiJHtUTVBESVI6LS90bXB9IiAtdCAibXlzcWwuWFhYWFhYWFhYWFhYIikKICAoCiAgICBlY2hvICJbY2xpZW50XSIKICAgIGVjaG8gInVzZXIgPSAke1VTRVJ9IgogICAgZWNobyAicGFzc3dvcmQgPSAke1BBU1NXT1JEfSIKICAgIGVjaG8gImhvc3QgPSAke0hPU1ROQU1FfSIKICAgIGVjaG8gInBvcnQgPSAke1BPUlR9IgogICkgPiIke2luc3RhbmNlTmV3SW5zdGFuY2VbJ0FVVEhfRklMRSddfSIKCiAgIyBzb21lIG9mIHRob3NlIHZhbHVlcyBjYW4gYmUgb3ZlcnJpZGRlbiB1c2luZyB0aGUgZHNuIGZpbGUKICAjIFNLSVBfQ09MVU1OX05BTUVTIGVuYWJsZWQgYnkgZGVmYXVsdAogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1NLSVBfQ09MVU1OX05BTUVTJ109IiR7U0tJUF9DT0xVTU5fTkFNRVM6LTF9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1NTTF9PUFRJT05TJ109IiR7TVlTUUxfU1NMX09QVElPTlM6LS0tc3NsLW1vZGU9RElTQUJMRUR9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1FVRVJZX09QVElPTlMnXT0iJHtNWVNRTF9RVUVSWV9PUFRJT05TOi0tLWJhdGNoIC0tcmF3IC0tZGVmYXVsdC1jaGFyYWN0ZXItc2V0PXV0Zjh9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0RVTVBfT1BUSU9OUyddPSIke01ZU1FMX0RVTVBfT1BUSU9OUzotLS1kZWZhdWx0LWNoYXJhY3Rlci1zZXQ9dXRmOCAtLWNvbXByZXNzIC0taGV4LWJsb2IgLS1yb3V0aW5lcyAtLXRyaWdnZXJzIC0tc2luZ2xlLXRyYW5zYWN0aW9uIC0tc2V0LWd0aWQtcHVyZ2VkPU9GRiAtLWNvbHVtbi1zdGF0aXN0aWNzPTAgJHtpbnN0YW5jZU5ld0luc3RhbmNlWydTU0xfT1BUSU9OUyddfX0iCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnREJfSU1QT1JUX09QVElPTlMnXT0iJHtEQl9JTVBPUlRfT1BUSU9OUzotLS1jb25uZWN0LXRpbWVvdXQ9NSAtLWJhdGNoIC0tcmF3IC0tZGVmYXVsdC1jaGFyYWN0ZXItc2V0PXV0Zjh9IgoKICBpbnN0YW5jZU5ld0luc3RhbmNlWydJTklUSUFMSVpFRCddPTEKfQoKIyBAZGVzY3JpcHRpb24gbXlzcWwgcXVlcnkgb24gYSBnaXZlbiBkYgojIEB3YXJuaW5nIGNvdWxkIHVzZSBRVUVSWV9PUFRJT05TIHZhcmlhYmxlIGZyb20gZHNuIGlmIGRlZmluZWQKIyBAZXhhbXBsZQojICAgY2F0IGZpbGUuc3FsIHwgRGF0YWJhc2U6OnF1ZXJ5IC4uLgojIEBhcmcgJDEgaW5zdGFuY2VRdWVyeTomTWFwPFN0cmluZyxTdHJpbmc+IChwYXNzZWQgYnkgcmVmZXJlbmNlKSBkYXRhYmFzZSBpbnN0YW5jZSB0byB1c2UKIyBAYXJnICQyIHNxbFF1ZXJ5OlN0cmluZyAob3B0aW9uYWwpIHNxbCBxdWVyeSBvciBzcWwgZmlsZSB0byBleGVjdXRlLiBpZiBub3QgcHJvdmlkZWQgb3IgZW1wdHksIHRoZSBjb21tYW5kIGNhbiBiZSBwaXBlZAojIEBhcmcgJDMgZGJOYW1lOlN0cmluZyAob3B0aW9uYWwpIHRoZSBkYiBuYW1lCiMKIyBAZXhpdGNvZGUgbXlzcWwgY29tbWFuZCBzdGF0dXMgY29kZQpEYXRhYmFzZTo6cXVlcnkoKSB7CiAgbG9jYWwgLW4gaW5zdGFuY2VRdWVyeT0kMQogIGxvY2FsIC1hIG15c3FsQ29tbWFuZD0oKQogIGxvY2FsIC1hIHF1ZXJ5T3B0aW9ucwoKICBteXNxbENvbW1hbmQrPShteXNxbCkKICBteXNxbENvbW1hbmQrPSgiLS1kZWZhdWx0cy1leHRyYS1maWxlPSR7aW5zdGFuY2VRdWVyeVsnQVVUSF9GSUxFJ119IikKICBJRlM9JyAnIHJlYWQgLXIgLWEgcXVlcnlPcHRpb25zIDw8PCIke2luc3RhbmNlUXVlcnlbJ1FVRVJZX09QVElPTlMnXX0iCiAgbXlzcWxDb21tYW5kKz0oIiR7cXVlcnlPcHRpb25zW0BdfSIpCiAgaWYgW1sgIiR7aW5zdGFuY2VRdWVyeVsnU0tJUF9DT0xVTU5fTkFNRVMnXX0iID0gIjEiIF1dOyB0aGVuCiAgICBteXNxbENvbW1hbmQrPSgiLXMiICItLXNraXAtY29sdW1uLW5hbWVzIikKICBmaQogICMgYWRkIG9wdGlvbmFsIGRiIG5hbWUKICBpZiBbWyAtbiAiJHszK3h9IiBdXTsgdGhlbgogICAgbXlzcWxDb21tYW5kKz0oIiQzIikKICBmaQogICMgYWRkIG9wdGlvbmFsIHNxbCBxdWVyeQogIGlmIFtbIC1uICIkezIreH0iICYmIC1uICIkMiIgJiYgISAtZiAiJDIiIF1dOyB0aGVuCiAgICBteXNxbENvbW1hbmQrPSgiLWUiKQogICAgbXlzcWxDb21tYW5kKz0oIiQyIikKICBmaQogIExvZzo6ZGlzcGxheURlYnVnICIkKHByaW50ZiAiZXhlY3V0ZSBjb21tYW5kOiAnJXMnIiAiJHtteXNxbENvbW1hbmRbKl19IikiCgogIGlmIFtbIC1mICIkMiIgXV07IHRoZW4KICAgICIke215c3FsQ29tbWFuZFtAXX0iIDwiJDIiCiAgZWxzZQogICAgIiR7bXlzcWxDb21tYW5kW0BdfSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBzZXQgdGhlIGdlbmVyYWwgb3B0aW9ucyB0byB1c2Ugb24gbXlzcWwgY29tbWFuZCB0byBxdWVyeSB0aGUgZGF0YWJhc2UKIyBEaWZmZXJzIHRoYW4gc2V0T3B0aW9ucyBpbiB0aGUgd2F5IHRoYXQgdGhlc2Ugb3B0aW9ucyBjb3VsZCBjaGFuZ2UgZWFjaCB0aW1lCiMKIyBAYXJnICQxIGluc3RhbmNlU2V0UXVlcnlPcHRpb25zOiZNYXA8U3RyaW5nLFN0cmluZz4gKHBhc3NlZCBieSByZWZlcmVuY2UpIGRhdGFiYXNlIGluc3RhbmNlIHRvIHVzZQojIEBhcmcgJDIgb3B0aW9uc0xpc3Q6U3RyaW5nIHF1ZXJ5IG9wdGlvbnMgbGlzdApEYXRhYmFzZTo6c2V0UXVlcnlPcHRpb25zKCkgewogIGxvY2FsIC1uIGluc3RhbmNlU2V0UXVlcnlPcHRpb25zPSQxCiAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0CiAgaW5zdGFuY2VTZXRRdWVyeU9wdGlvbnNbJ1FVRVJZX09QVElPTlMnXT0iJDIiCn0KCiMgQGRlc2NyaXB0aW9uIGJ5IGRlZmF1bHQgd2Ugc2tpcCB0aGUgY29sdW1uIG5hbWVzCiMgYnV0IHNvbWV0aW1lcyB3ZSBuZWVkIGNvbHVtbiBuYW1lcyB0byBkaXNwbGF5IHNvbWUgcmVzdWx0cwojIGRpc2FibGUgdGhpcyBvcHRpb24gdGVtcG9yYXJpbHkgYW5kIHRoZW4gcmVzdG9yZSBpdCB0byB0cnVlCiMKIyBAYXJnICQxIGluc3RhbmNlU2V0UXVlcnlPcHRpb25zOiZNYXA8U3RyaW5nLFN0cmluZz4gKHBhc3NlZCBieSByZWZlcmVuY2UpIGRhdGFiYXNlIGluc3RhbmNlIHRvIHVzZQojIEBhcmcgJDIgc2tpcENvbHVtbk5hbWVzOkJvb2xlYW4gMCB0byBkaXNhYmxlLCAxIHRvIGVuYWJsZSAoaGlkZSBjb2x1bW4gbmFtZXMpCkRhdGFiYXNlOjpza2lwQ29sdW1uTmFtZXMoKSB7CiAgbG9jYWwgLW4gaW5zdGFuY2VTa2lwQ29sdW1uTmFtZXM9JDEKICAjIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIwMzQKICBpbnN0YW5jZVNraXBDb2x1bW5OYW1lc1snU0tJUF9DT0xVTU5fTkFNRVMnXT0iJDIiCn0KCiMgQGRlc2NyaXB0aW9uIHByZXBlbmQgZGlyZWN0b3JpZXMgdG8gdGhlIFBBVEggZW52aXJvbm1lbnQgdmFyaWFibGUKIyBAYXJnICRAIGFyZ3M6U3RyaW5nW10gbGlzdCBvZiBkaXJlY3RvcmllcyB0byBwcmVwZW5kCiMgQHNldCBQQVRIIHVwZGF0ZSBQQVRIIHdpdGggdGhlIGRpcmVjdG9yaWVzIHByZXBlbmRlZApFbnY6OnBhdGhQcmVwZW5kKCkgewogIGxvY2FsIGFyZwogIGZvciBhcmcgaW4gIiRAIjsgZG8KICAgIGlmIFtbIC1kICIke2FyZ30iICYmICI6JHtQQVRIfToiICE9ICoiOiR7YXJnfToiKiBdXTsgdGhlbgogICAgICBQQVRIPSIkKHJlYWxwYXRoICIke2FyZ30iKToke1BBVEh9IgogICAgZmkKICBkb25lCn0KCiMgQGRlc2NyaXB0aW9uIERpc3BsYXkgbWVzc2FnZSB1c2luZyBlcnJvciBjb2xvciAocmVkKSBhbmQgZXhpdCBpbW1lZGlhdGVseSB3aXRoIGVycm9yIHN0YXR1cyAxCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6ZmF0YWwoKSB7CiAgZWNobyAtZSAiJHtfX0VSUk9SX0NPTE9SfUZBVEFMICAgLSAkezF9JHtfX1JFU0VUX0NPTE9SfSIgPiYyCiAgTG9nOjpsb2dGYXRhbCAiJDEiCiAgZXhpdCAxCn0KCiMgQGRlc2NyaXB0aW9uIGxvZyBtZXNzYWdlIHRvIGZpbGUKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpsb2dEZWJ1ZygpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+PSBfX0xFVkVMX0RFQlVHKSk7IHRoZW4KICAgIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1ERUJVR30iICIkMSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBsb2cgbWVzc2FnZSB0byBmaWxlCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6bG9nSW5mbygpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+PSBfX0xFVkVMX0lORk8pKTsgdGhlbgogICAgTG9nOjpsb2dNZXNzYWdlICIkezI6LUlORk99IiAiJDEiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gZW5zdXJlIHJ1bm5pbmcgdXNlciBpcyBub3Qgcm9vdAojIEBleGl0Y29kZSAxIGlmIGN1cnJlbnQgdXNlciBpcyByb290CiMgQHN0ZGVyciBkaWFnbm9zdGljcyBpbmZvcm1hdGlvbiBpcyBkaXNwbGF5ZWQKTGludXg6OnJlcXVpcmVFeGVjdXRlZEFzVXNlcigpIHsKICBpZiBbWyAiJChpZCAtdSkiID0gIjAiIF1dOyB0aGVuCiAgICBMb2c6OmZhdGFsICJ0aGlzIHNjcmlwdCBzaG91bGQgYmUgZXhlY3V0ZWQgYXMgbm9ybWFsIHVzZXIiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gZ2V0IGFic29sdXRlIGNvbmYgZmlsZSBmcm9tIHNwZWNpZmllZCBjb25mIGZvbGRlciBkZWR1Y2VkIHVzaW5nIHRoZXNlIHJ1bGVzCiMgICAqIGZyb20gYWJzb2x1dGUgZmlsZSAoaWdub3JlcyA8Y29uZkZvbGRlcj4gYW5kIDxleHRlbnNpb24+KQojICAgKiByZWxhdGl2ZSB0byB3aGVyZSBzY3JpcHQgaXMgZXhlY3V0ZWQgKGlnbm9yZXMgPGNvbmZGb2xkZXI+IGFuZCA8ZXh0ZW5zaW9uPikKIyAgICogZnJvbSBob21lLy5iYXNoLXRvb2xzLzxjb25mRm9sZGVyPgojICAgKiBmcm9tIGZyYW1ld29yayBjb25mLzxjb25mRm9sZGVyPgojCiMgQGFyZyAkMSBjb25mRm9sZGVyOlN0cmluZyB0aGUgZGlyZWN0b3J5IG5hbWUgKG5vdCB0aGUgcGF0aCkgdG8gbGlzdAojIEBhcmcgJDIgY29uZjpTdHJpbmcgZmlsZSB0byB1c2Ugd2l0aG91dCBleHRlbnNpb24KIyBAYXJnICQzIGV4dGVuc2lvbjpTdHJpbmcgdGhlIGV4dGVuc2lvbiAoLnNoIGJ5IGRlZmF1bHQpCiMKIyBAc3Rkb3V0IGFic29sdXRlIGNvbmYgZmlsZW5hbWUKIyBAZXhpdGNvZGUgMSBpZiBmaWxlIGlzIG5vdCBmb3VuZCBpbiBhbnkgbG9jYXRpb24KQ29uZjo6Z2V0QWJzb2x1dGVGaWxlKCkgewogIGxvY2FsIGNvbmZGb2xkZXI9IiQxIgogIGxvY2FsIGNvbmY9IiQyIgogIGxvY2FsIGV4dGVuc2lvbj0iJHszLS5zaH0iCiAgaWYgW1sgLW4gIiR7ZXh0ZW5zaW9ufSIgJiYgIiR7ZXh0ZW5zaW9uOjA6MX0iICE9ICIuIiBdXTsgdGhlbgogICAgZXh0ZW5zaW9uPSIuJHtleHRlbnNpb259IgogIGZpCgogIHRlc3RBYnMoKSB7CiAgICBsb2NhbCByZXN1bHQKICAgIHJlc3VsdD0iJChyZWFscGF0aCAtZSAiJDEiIDI+L2Rldi9udWxsKSIKICAgICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjE4MQogICAgaWYgW1sgIiQ/IiA9ICIwIiAmJiAtZiAiJHtyZXN1bHR9IiBdXTsgdGhlbgogICAgICBlY2hvICIke3Jlc3VsdH0iCiAgICAgIHJldHVybiAwCiAgICBmaQogICAgcmV0dXJuIDEKICB9CgogICMgY29uZiBpcyBhYnNvbHV0ZSBmaWxlIChpbmNsdWRpbmcgZXh0ZW5zaW9uKQogIHRlc3RBYnMgIiR7Y29uZkZvbGRlcn0ke2V4dGVuc2lvbn0iICYmIHJldHVybiAwCiAgIyBjb25mIGlzIGFic29sdXRlIGZpbGUKICB0ZXN0QWJzICIke2NvbmZGb2xkZXJ9IiAmJiByZXR1cm4gMAogICMgY29uZiBpcyBhYnNvbHV0ZSBmaWxlIChpbmNsdWRpbmcgZXh0ZW5zaW9uKQogIHRlc3RBYnMgIiR7Y29uZn0ke2V4dGVuc2lvbn0iICYmIHJldHVybiAwCiAgIyBjb25mIGlzIGFic29sdXRlIGZpbGUKICB0ZXN0QWJzICIke2NvbmZ9IiAmJiByZXR1cm4gMAoKICAjIHJlbGF0aXZlIHRvIHdoZXJlIHNjcmlwdCBpcyBleGVjdXRlZCAoaW5jbHVkaW5nIGV4dGVuc2lvbikKICBpZiBbWyAtbiAiJHtDVVJSRU5UX0RJUit4eHh9IiBdXTsgdGhlbgogICAgdGVzdEFicyAiJChGaWxlOjpjb25jYXRlbmF0ZVBhdGggIiR7Q1VSUkVOVF9ESVJ9IiAiJHtjb25mRm9sZGVyfSIpLyR7Y29uZn0ke2V4dGVuc2lvbn0iICYmIHJldHVybiAwCiAgZmkKICAjIGZyb20gaG9tZS8uYmFzaC10b29scy88Y29uZkZvbGRlcj4KICB0ZXN0QWJzICIkKEZpbGU6OmNvbmNhdGVuYXRlUGF0aCAiJHtIT01FfS8uYmFzaC10b29scyIgIiR7Y29uZkZvbGRlcn0iKS8ke2NvbmZ9JHtleHRlbnNpb259IiAmJiByZXR1cm4gMAoKICBpZiBbWyAtbiAiJHtGUkFNRVdPUktfUk9PVF9ESVIreHh4fSIgXV07IHRoZW4KICAgICMgZnJvbSBmcmFtZXdvcmsgY29uZi88Y29uZkZvbGRlcj4gKGluY2x1ZGluZyBleHRlbnNpb24pCiAgICB0ZXN0QWJzICIkKEZpbGU6OmNvbmNhdGVuYXRlUGF0aCAiJHtGUkFNRVdPUktfUk9PVF9ESVJ9L2NvbmYiICIke2NvbmZGb2xkZXJ9IikvJHtjb25mfSR7ZXh0ZW5zaW9ufSIgJiYgcmV0dXJuIDAKCiAgICAjIGZyb20gZnJhbWV3b3JrIGNvbmYvPGNvbmZGb2xkZXI+CiAgICB0ZXN0QWJzICIkKEZpbGU6OmNvbmNhdGVuYXRlUGF0aCAiJHtGUkFNRVdPUktfUk9PVF9ESVJ9L2NvbmYiICIke2NvbmZGb2xkZXJ9IikvJHtjb25mfSIgJiYgcmV0dXJuIDAKICBmaQoKICAjIGZpbGUgbm90IGZvdW5kCiAgTG9nOjpkaXNwbGF5RXJyb3IgImNvbmYgZmlsZSAnJHtjb25mfScgbm90IGZvdW5kIgoKICByZXR1cm4gMQp9CgojIEBkZXNjcmlwdGlvbiBjaGVjayBpZiBkc24gZmlsZSBoYXMgYWxsIHRoZSBtYW5kYXRvcnkgdmFyaWFibGVzIHNldAojIE1hbmRhdG9yeSB2YXJpYWJsZXMgYXJlOiBIT1NUTkFNRSwgVVNFUiwgUEFTU1dPUkQsIFBPUlQKIwojIEBhcmcgJDEgZHNuRmlsZU5hbWU6U3RyaW5nIGRzbiBhYnNvbHV0ZSBmaWxlbmFtZQojIEBzZXQgSE9TVE5BTUUgbG9hZGVkIGZyb20gZHNuIGZpbGUKIyBAc2V0IFBPUlQgbG9hZGVkIGZyb20gZHNuIGZpbGUKIyBAc2V0IFVTRVIgbG9hZGVkIGZyb20gZHNuIGZpbGUKIyBAc2V0IFBBU1NXT1JEIGxvYWRlZCBmcm9tIGRzbiBmaWxlCiMgQGV4aXRjb2RlIDAgb24gdmFsaWQgZmlsZQojIEBleGl0Y29kZSAxIGlmIG9uZSBvZiB0aGUgcHJvcGVydGllcyBvZiB0aGUgY29uZiBmaWxlIGlzIGludmFsaWQgb3IgaWYgZmlsZSBub3QgZm91bmQKIyBAc3RkZXJyIGxvZyBvdXRwdXQgaWYgZXJyb3IgZm91bmQgaW4gY29uZiBmaWxlCkRhdGFiYXNlOjpjaGVja0RzbkZpbGUoKSB7CiAgbG9jYWwgZHNuRmlsZU5hbWU9IiQxIgogIGlmIFtbICEgLWYgIiR7ZHNuRmlsZU5hbWV9IiBdXTsgdGhlbgogICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IG5vdCBmb3VuZCIKICAgIHJldHVybiAxCiAgZmkKCiAgKAogICAgdW5zZXQgSE9TVE5BTUUgUE9SVCBQQVNTV09SRCBVU0VSCiAgICAjIHNoZWxsY2hlY2sgc291cmNlPS9zcmMvRGF0YWJhc2UvdGVzdHNEYXRhL2Rzbl92YWxpZC5lbnYKICAgIHNvdXJjZSAiJHtkc25GaWxlTmFtZX0iCiAgICBpZiBbWyAteiAke0hPU1ROQU1FK3h9IF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheUVycm9yICJkc24gZmlsZSAke2RzbkZpbGVOYW1lfSA6IEhPU1ROQU1FIG5vdCBwcm92aWRlZCIKICAgICAgcmV0dXJuIDEKICAgIGZpCiAgICBpZiBbWyAteiAiJHtIT1NUTkFNRX0iIF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheVdhcm5pbmcgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogSE9TVE5BTUUgdmFsdWUgbm90IHByb3ZpZGVkIgogICAgZmkKICAgIGlmIFtbICIke0hPU1ROQU1FfSIgPSAibG9jYWxob3N0IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlXYXJuaW5nICJkc24gZmlsZSAke2RzbkZpbGVOYW1lfSA6IGNoZWNrIHRoYXQgSE9TVE5BTUUgc2hvdWxkIG5vdCBiZSAxMjcuMC4wLjEgaW5zdGVhZCBvZiBsb2NhbGhvc3QiCiAgICBmaQogICAgaWYgW1sgLXogIiR7UE9SVCt4fSIgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogUE9SVCBub3QgcHJvdmlkZWQiCiAgICAgIHJldHVybiAxCiAgICBmaQogICAgaWYgISBbWyAke1BPUlR9ID1+IF5bMC05XSskIF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheUVycm9yICJkc24gZmlsZSAke2RzbkZpbGVOYW1lfSA6IFBPUlQgaW52YWxpZCIKICAgICAgcmV0dXJuIDEKICAgIGZpCiAgICBpZiBbWyAteiAiJHtVU0VSK3h9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlFcnJvciAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gOiBVU0VSIG5vdCBwcm92aWRlZCIKICAgICAgcmV0dXJuIDEKICAgIGZpCiAgICBpZiBbWyAteiAiJHtQQVNTV09SRCt4fSIgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogUEFTU1dPUkQgbm90IHByb3ZpZGVkIgogICAgICByZXR1cm4gMQogICAgZmkKICApCn0KCiMgQGRlc2NyaXB0aW9uIGxvZyBtZXNzYWdlIHRvIGZpbGUKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpsb2dGYXRhbCgpIHsKICBMb2c6OmxvZ01lc3NhZ2UgIiR7MjotRkFUQUx9IiAiJDEiCn0KCiMgQGRlc2NyaXB0aW9uIEludGVybmFsOiBjb21tb24gbG9nIG1lc3NhZ2UKIyBAZXhhbXBsZSB0ZXh0CiMgICBbZGF0ZV18W2xldmVsTXNnXXxtZXNzYWdlCiMKIyBAZXhhbXBsZSB0ZXh0CiMgICAyMDIwLTAxLTE5IDE5OjIwOjIxfEVSUk9SICB8bG9nIGVycm9yCiMgICAyMDIwLTAxLTE5IDE5OjIwOjIxfFNLSVBQRUR8bG9nIHNraXBwZWQKIwojIEBhcmcgJDEgbGV2ZWxNc2c6U3RyaW5nIG1lc3NhZ2UncyBsZXZlbCBkZXNjcmlwdGlvbiAoZWc6IFNUQVRVUywgRVJST1IsIC4uLikKIyBAYXJnICQyIG1zZzpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQojIEBlbnYgQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEUgU3RyaW5nIGxvZyBmaWxlIHRvIHVzZSwgZG8gbm90aGluZyBpZiBlbXB0eQojIEBlbnYgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMIGludCBsb2cgbGV2ZWwgbG9nIG9ubHkgaWYgPiBPRkYgb3IgZmF0YWwgbWVzc2FnZXMKIyBAc3RkZXJyIGRpYWdub3N0aWNzIGluZm9ybWF0aW9uIGlzIGRpc3BsYXllZAojIEByZXF1aXJlIEVudjo6cmVxdWlyZUxvYWQKIyBAcmVxdWlyZSBMb2c6OnJlcXVpcmVMb2FkCkxvZzo6bG9nTWVzc2FnZSgpIHsKICBsb2NhbCBsZXZlbE1zZz0iJDEiCiAgbG9jYWwgbXNnPSIkMiIKICBsb2NhbCBkYXRlCgogIGlmIFtbIC1uICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSIgXV0gJiYgKChCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgPiBfX0xFVkVMX09GRikpOyB0aGVuCiAgICBkYXRlPSIkKGRhdGUgJyslWS0lbS0lZCAlSDolTTolUycpIgogICAgdG91Y2ggIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IgogICAgcHJpbnRmICIlc3wlN3N8JXNcbiIgIiR7ZGF0ZX0iICIke2xldmVsTXNnfSIgIiR7bXNnfSIgPj4iJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gY29uY2F0ZW5hdGUgMiBwYXRocyBhbmQgZW5zdXJlIHRoZSBwYXRoIGlzIGNvcnJlY3QgdXNpbmcgcmVhbHBhdGggLW0KIyBAYXJnICQxIGJhc2VQYXRoOlN0cmluZwojIEBhcmcgJDIgc3ViUGF0aDpTdHJpbmcKIyBAcmVxdWlyZSBMaW51eDo6cmVxdWlyZVJlYWxwYXRoQ29tbWFuZApGaWxlOjpjb25jYXRlbmF0ZVBhdGgoKSB7CiAgbG9jYWwgYmFzZVBhdGg9IiQxIgogIGxvY2FsIHN1YlBhdGg9IiQyIgogIGxvY2FsIGZ1bGxQYXRoPSIke2Jhc2VQYXRoOiske2Jhc2VQYXRofS99JHtzdWJQYXRofSIKCiAgcmVhbHBhdGggLW0gIiR7ZnVsbFBhdGh9IiAyPi9kZXYvbnVsbAp9CgojIEBkZXNjcmlwdGlvbiBEaXNwbGF5IG1lc3NhZ2UgdXNpbmcgZXJyb3IgY29sb3IgKHJlZCkKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpkaXNwbGF5RXJyb3IoKSB7CiAgaWYgKChCQVNIX0ZSQU1FV09SS19ESVNQTEFZX0xFVkVMID49IF9fTEVWRUxfRVJST1IpKTsgdGhlbgogICAgZWNobyAtZSAiJHtfX0VSUk9SX0NPTE9SfUVSUk9SICAgLSAkezF9JHtfX1JFU0VUX0NPTE9SfSIgPiYyCiAgZmkKICBMb2c6OmxvZ0Vycm9yICIkMSIKfQoKIyBAZGVzY3JpcHRpb24gRGlzcGxheSBtZXNzYWdlIHVzaW5nIHdhcm5pbmcgY29sb3IgKHllbGxvdykKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpkaXNwbGF5V2FybmluZygpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUwgPj0gX19MRVZFTF9XQVJOSU5HKSk7IHRoZW4KICAgIGVjaG8gLWUgIiR7X19XQVJOSU5HX0NPTE9SfVdBUk4gICAgLSAkezF9JHtfX1JFU0VUX0NPTE9SfSIgPiYyCiAgZmkKICBMb2c6OmxvZ1dhcm5pbmcgIiQxIgp9CgojIEBkZXNjcmlwdGlvbiBlbnN1cmUgZW52IGZpbGVzIGFyZSBsb2FkZWQKIyBAbm9hcmdzCiMgQGV4aXRjb2RlIDEgaWYgZ2V0T3JkZXJlZENvbmZGaWxlcyBmYWlscwojIEBleGl0Y29kZSAyIGlmIG9uZSBvZiBlbnYgZmlsZXMgZmFpbHMgdG8gbG9hZAojIEBzdGRlcnIgZGlhZ25vc3RpY3MgaW5mb3JtYXRpb24gaXMgZGlzcGxheWVkCkVudjo6cmVxdWlyZUxvYWQoKSB7CiAgbG9jYWwgY29uZmlnRmlsZXNTdHIKICBjb25maWdGaWxlc1N0cj0iJChFbnY6OmdldE9yZGVyZWRDb25mRmlsZXMpIiB8fCByZXR1cm4gMQoKICBsb2NhbCAtYSBjb25maWdGaWxlcwogIHJlYWRhcnJheSAtdCBjb25maWdGaWxlcyA8PDwiJHtjb25maWdGaWxlc1N0cn0iCgogICMgaWYgZW1wdHkgc3RyaW5nLCB0aGVyZSB3aWxsIGJlIG9uZSBlbGVtZW50CiAgaWYgKCgkeyNjb25maWdGaWxlc1tAXX0gPT0gMCkpIHx8IFtbIC16ICIke2NvbmZpZ0ZpbGVzU3RyfSIgXV07IHRoZW4KICAgICMgc2hvdWxkIG5vdCBoYXBwZW4sIGFzIHRoZXJlIGlzIGFsd2F5cyBkZWZhdWx0IGZpbGUKICAgIExvZzo6ZGlzcGxheVNraXBwZWQgIm5vIGVudiBmaWxlIHRvIGxvYWQiCiAgICByZXR1cm4gMAogIGZpCgogIEVudjo6bWVyZ2VDb25mRmlsZXMgIiR7Y29uZmlnRmlsZXNbQF19IiB8fCB7CiAgICBMb2c6OmRpc3BsYXlFcnJvciAid2hpbGUgbG9hZGluZyBjb25maWcgZmlsZXM6ICR7Y29uZmlnRmlsZXNbKl19IgogICAgcmV0dXJuIDIKICB9Cn0KCiMgQGRlc2NyaXB0aW9uIGFjdGl2YXRlIG9yIG5vdCBMb2c6OmRpc3BsYXkqIGFuZCBMb2c6OmxvZyogZnVuY3Rpb25zCiMgYmFzZWQgb24gQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCBhbmQgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMCiMgZW52aXJvbm1lbnQgdmFyaWFibGVzIGxvYWRlZCBieSBFbnY6OnJlcXVpcmVMb2FkCiMgdHJ5IHRvIGNyZWF0ZSBsb2cgZmlsZSBhbmQgcm90YXRlIGl0IGlmIG5lY2Vzc2FyeQojIEBub2FyZ3MKIyBAc2V0IEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCBpbnQgdG8gT0ZGIGxldmVsIGlmIEJBU0hfRlJBTUVXT1JLX0xPR19GSUxFIGlzIGVtcHR5IG9yIG5vdCB3cml0YWJsZQojIEBlbnYgQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCBpbnQKIyBAZW52IEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCBpbnQKIyBAZW52IEJBU0hfRlJBTUVXT1JLX0xPR19GSUxFIFN0cmluZwojIEBlbnYgQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEVfTUFYX1JPVEFUSU9OIGludCBkbyBsb2cgcm90YXRpb24gaWYgPiAwCiMgQGV4aXRjb2RlIDAgYWx3YXlzIHN1Y2Nlc3NmdWwKIyBAc3RkZXJyIGRpYWdub3N0aWNzIGluZm9ybWF0aW9uIGFib3V0IGxvZyBmaWxlIGlzIGRpc3BsYXllZAojIEByZXF1aXJlIEVudjo6cmVxdWlyZUxvYWQKIyBAcmVxdWlyZSBVSTo6cmVxdWlyZVRoZW1lCkxvZzo6cmVxdWlyZUxvYWQoKSB7CiAgaWYgW1sgLXogIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEU6LX0iIF1dOyB0aGVuCiAgICBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUw9JHtfX0xFVkVMX09GRn0KICAgIGV4cG9ydCBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwKICBmaQoKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+IF9fTEVWRUxfT0ZGKSk7IHRoZW4KICAgIGlmIFtbICEgLWYgIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IiBdXTsgdGhlbgogICAgICBpZgogICAgICAgICEgbWtkaXIgLXAgIiQoZGlybmFtZSAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iKSIgMj4vZGV2L251bGwgfHwKICAgICAgICAgICEgdG91Y2ggLS1uby1jcmVhdGUgIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IiAyPi9kZXYvbnVsbAogICAgICB0aGVuCiAgICAgICAgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMPSR7X19MRVZFTF9PRkZ9CiAgICAgICAgZWNobyAtZSAiJHtfX0VSUk9SX0NPTE9SfUVSUk9SICAgLSBGaWxlICR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IGlzIG5vdCB3cml0YWJsZSR7X19SRVNFVF9DT0xPUn0iID4mMgogICAgICBmaQogICAgZWxpZiBbWyAhIC13ICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSIgXV07IHRoZW4KICAgICAgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMPSR7X19MRVZFTF9PRkZ9CiAgICAgIGVjaG8gLWUgIiR7X19FUlJPUl9DT0xPUn1FUlJPUiAgIC0gRmlsZSAke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSBpcyBub3Qgd3JpdGFibGUke19fUkVTRVRfQ09MT1J9IiA+JjIKICAgIGZpCgogIGZpCgogIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMID4gX19MRVZFTF9PRkYpKTsgdGhlbgogICAgIyB3aWxsIGFsd2F5cyBiZSBjcmVhdGVkIGV2ZW4gaWYgbm90IGluIGluZm8gbGV2ZWwKICAgIExvZzo6bG9nTWVzc2FnZSAiSU5GTyIgIkxvZ2dpbmcgdG8gZmlsZSAke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSAtIExvZyBsZXZlbCAke0JBU0hfRlJBTUVXT1JLX0xPR19MRVZFTH0iCiAgICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19GSUxFX01BWF9ST1RBVElPTiA+IDApKTsgdGhlbgogICAgICBMb2c6OnJvdGF0ZSAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFX01BWF9ST1RBVElPTn0iCiAgICBmaQogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGdldCBsaXN0IG9mIGVudiBmaWxlcyB0byBsb2FkCiMgaW4gb3JkZXIgdG8gbWFrZSB0aGVtIGF2YWlsYWJsZSBmb3IgRW52OjpyZXF1aXJlTG9hZAojIEBlbnYgQkFTSF9GUkFNRVdPUktfRU5WX0ZJTEVTIFN0cmluZ1tdIGxpc3Qgb2YgZW52IGZpbGVzIHRoYXQgc2hvdWxkIGJlIGxvYWRlZAojIEBleGl0Y29kZSAxIGlmIG9uZSBvZiB0aGUgZW52IGZpbGUgY2Fubm90IGJlIGdlbmVyYXRlZAojIEBleGl0Y29kZSAyIGlmIG9uZSBvZiB0aGUgZW52IGZpbGUgaXMgbm90IGEgZmlsZSBvciByZWFkYWJsZQojIEBzdGRvdXQgdGhlIGVudiBmaWxlcyBhc2tlZCB0byBiZSBsb2FkZWQKIyBAc3RkZXJyIGRpYWdub3N0aWMgaW5mb3JtYXRpb24gb24gZmFpbHVyZQojIEBzZWUgaHR0cHM6Ly9naXRodWIuY29tL2ZjaGFzdGFuZXQvYmFzaC10b29scy1mcmFtZXdvcmsvYmxvYi9tYXN0ZXIvRnJhbWV3b3JrRG9jLm1kI2NvbmZpZ19maWxlX29yZGVyCkVudjo6Z2V0T3JkZXJlZENvbmZGaWxlcygpIHsKICBsb2NhbCAtYSBjb25maWdGaWxlcz0oKQoKICBpZiBbWyAtbiAiJHtCQVNIX0ZSQU1FV09SS19FTlZfRklMRVNbMF0rMX0iIF1dOyB0aGVuCiAgICAjIEJBU0hfRlJBTUVXT1JLX0VOVl9GSUxFUyBpcyBhbiBhcnJheQogICAgY29uZmlnRmlsZXMrPSgiJHtCQVNIX0ZSQU1FV09SS19FTlZfRklMRVNbQF19IikKICBmaQoKICBsb2NhbCBkZWZhdWx0RW52RmlsZQogIGRlZmF1bHRFbnZGaWxlPSIkKEVudjo6Y3JlYXRlRGVmYXVsdEVudkZpbGUpIiB8fCByZXR1cm4gMQogIGNvbmZpZ0ZpbGVzKz0oIiR7ZGVmYXVsdEVudkZpbGV9IikKCiAgbG9jYWwgZmlsZQogIGZvciBmaWxlIGluICIke2NvbmZpZ0ZpbGVzW0BdfSI7IGRvCiAgICBpZiBbWyAhIC1mICIke2ZpbGV9IiB8fCAhIC1yICIke2ZpbGV9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlFcnJvciAiT25lIG9mIHRoZSBjb25maWcgZmlsZSBpcyBub3QgYXZhaWxhYmxlICcke2ZpbGV9JyIKICAgICAgcmV0dXJuIDIKICAgIGZpCiAgICBlY2hvICIke2ZpbGV9IgogIGRvbmUKfQoKIyBAZGVzY3JpcHRpb24gbWVyZ2UgYW5kIGxvYWQgY29uZiBmaWxlcyBzcGVjaWZpZWQgYXMgYXJndW1lbnQKIyAtIGZpbGVzIGFyZSBjbGVhbmVkIGZyb20gYXkgY29tbWVudAojIC0gbWlzc2luZyBxdW90ZXMgYWZ0ZXIgcHJvcGVydHkgPSBzaWduIGFyZSBhZGRlZCBhdXRvbWF0aWNhbGx5CiMgLSBhdXRvbWF0aWMgcmVtb3ZlIG9mIGFsbCB3aGl0ZXNwYWNlIGJlZm9yZSBhbmQgYWZ0ZXIgZGVjbGFyYXRpb25zCiMgLSBiYXNoIGFycmF5cyBhcmUgbm90IHN1cHBvcnRlZAojIC0gaWYgYSB2YXJpYWJsZSBpcyBkZWNsYXJlZCBpbiBmaXJzdCBmaWxlIGFuZCBvdmVycmlkZGVuIGxhdGVyIG9uCiMgICBpbiB0aGUgc2FtZSBmaWxlIG9yIGluIHN1YnNlcXVlbnQgZmlsZXMsIHRob3NlIG92ZXJsb2FkcyB3aWxsIGJlCiMgICBpZ25vcmVkCiMgQHdhcm5pbmcgaWYgYW4gZXJyb3Igb2NjdXJzIHdoaWxlIGxvYWRpbmcgb25lIG9mIHRoZSBjb25maWcgZmlsZSwgZXhpdCBjb2RlIDMgYnV0IGVudmlyb25tZW50IGNvdWxkIGJlIHBhcnRpYWxseSBsb2FkZWQKIyBAYXJnICRAIGFyZ3M6U3RyaW5nW10gbGlzdCBvZiBjb25maWd1cmF0aW9uIGZpbGVzIHRvIGxvYWQgaW4gb3JkZXIKIyBAc2V0IGVudlZhcnMgU3RyaW5nIHdpbGwgc2V0IGluIGVudmlyb25tZW50IGFsbCB0aGUgdmFyaWFibGVzIHRoYXQgaGF2ZSBiZWVuIGRlY2xhcmVkIGluIHRoZSBjb25maWcgZmlsZXMKIyBAZW52IGVudlZhcnMgU3RyaW5nIHRoZSBlbnYgdmFyaWFibGVzIG9mIHRoZSBjdXJyZW50IHNjcmlwdCBjb3VsZCBiZSB1c2VkIHRvIGludGVycHJldCB2YXJpYWJsZXMgZHVyaW5nIGNvbmZpZyBmaWxlcyBwYXJzaW5nCiMgQGV4aXRjb2RlIDAgaWYgbm8gY29uZmlnIGZpbGVzIHByb3ZpZGVkIG9yIGxvYWQgY29tcGxldGVkIHN1Y2Nlc3NmdWxseQojIEBleGl0Y29kZSAxIGlmIGVycm9yIG9jY3VycmVkIGR1cmluZyBwYXJzaW5nIHRoZSBjb25maWcgZmlsZXMgKGZpbGUgbm90IGZvdW5kLCBncmVwLCBhd2sgb3Igc2VkIGVycm9yKQojIEBleGl0Y29kZSAyIGlmIHRlbXBvcmFyeSBmaWxlIGNhbm5vdCBiZSBjcmVhdGVkCiMgQGV4aXRjb2RlIDMgaWYgYW4gZXJyb3Igb2NjdXJyZWQgZHVyaW5nIGNvbmZpZyBmaWxlIHNvdXJjaW5nCiMgQHN0ZGVyciBkaWFnbm9zdGljcyBpbmZvcm1hdGlvbiBpcyBkaXNwbGF5ZWQKIyBAc2VlIGxhcmdlbHkgaW5zcGlyZWQgYnV0IG1vZGlmaWVkIGZyb20gaHR0cHM6Ly9vcGVuc291cmNlLmNvbS9hcnRpY2xlLzIxLzUvcHJvY2Vzc2luZy1jb25maWd1cmF0aW9uLWZpbGVzLXNoZWxsCkVudjo6bWVyZ2VDb25mRmlsZXMoKSB7CiAgbG9jYWwgLWEgY29uZmlnRmlsZUxpc3Q9KCIkQCIpCgogIGlmICgoJHsjY29uZmlnRmlsZUxpc3RbQF19ID09IDApKTsgdGhlbgogICAgcmV0dXJuIDAKICBmaQoKICBsb2NhbCBjb21iaW5lZENvbmZpZ0ZpbGUKICBjb21iaW5lZENvbmZpZ0ZpbGU9IiQoRnJhbWV3b3JrOjpjcmVhdGVUZW1wRmlsZSAibWVyZ2VDb25mRmlsZXMiKSIgfHwgcmV0dXJuIDIKCiAgKAogICAgIyByZW1vdmVzIGFueSB0cmFpbGluZyB3aGl0ZXNwYWNlIGZyb20gZWFjaCBmaWxlLCBpZiBhbnkKICAgICMgdGhpcyBpcyBhYnNvbHV0ZWx5IHJlcXVpcmVkIHdoZW4gaW1wb3J0aW5nIGludG8gQ29uZmlnTWFwcwogICAgIyBwdXQgcXVvdGVzIGFyb3VuZCB2YWx1ZXMKICAgIHNlZCAtRSAtZSAkJ3MvXHMqJC8vIDsgL14kL2QgOyAvXiMuKiQvZCA7IHMvPShbXiJcJ10uKikkLz0iXFwxIi8nICIke2NvbmZpZ0ZpbGVMaXN0W0BdfSIgfAogICAgICAjIHJlbW92ZSBhbGwgY29tbWVudCBsaW5lcwogICAgICBGaWx0ZXJzOjpjb21tZW50TGluZXMgfAogICAgICAjIGl0ZXJhdGVzIG92ZXIgZWFjaCBmaWxlIGFuZCBwcmludHMgKGRlZmF1bHQgYXdrIGJlaGF2aW9yKQogICAgICAjIGVhY2ggdW5pcXVlIGxpbmU7IG9ubHkgdGFrZXMgZmlyc3QgdmFsdWUgYW5kIGlnbm9yZXMgZHVwbGljYXRlcwogICAgICBhd2sgLUY9ICchbGluZVskMV0rKycKICApID4iJHtjb21iaW5lZENvbmZpZ0ZpbGV9IiB8fCByZXR1cm4gMQoKICAjIGhhdmUgdG8gZXhwb3J0IGV2ZXJ5dGhpbmcsIGFuZCBzb3VyY2UgaXQgdHdpY2U6CiAgIyAxKSBmaXJzdCBzb3VyY2UgaXMgdG8gcmVhbGl6ZSB2YXJpYWJsZXMKICAjIDIpIHNlY29uZCB0aW1lIGlzIHRvIHJlYWxpemUgcmVmZXJlbmNlcwogIHNldCAtbyBhbGxleHBvcnQKICAjIHNoZWxsY2hlY2sgc291cmNlPS5mcmFtZXdvcmstY29uZmlnCiAgc291cmNlICIke2NvbWJpbmVkQ29uZmlnRmlsZX0iIHx8IHJldHVybiAzCiAgIyBzaGVsbGNoZWNrIHNvdXJjZT0uZnJhbWV3b3JrLWNvbmZpZwogIHNvdXJjZSAiJHtjb21iaW5lZENvbmZpZ0ZpbGV9IiB8fCByZXR1cm4gMwogIHNldCArbyBhbGxleHBvcnQKfQoKIyBAZGVzY3JpcHRpb24gRGlzcGxheSBtZXNzYWdlIHVzaW5nIHNraXAgY29sb3IgKHllbGxvdykKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpkaXNwbGF5U2tpcHBlZCgpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUwgPj0gX19MRVZFTF9JTkZPKSk7IHRoZW4KICAgIGVjaG8gLWUgIiR7X19TS0lQUEVEX0NPTE9SfVNLSVBQRUQgLSAkezF9JHtfX1JFU0VUX0NPTE9SfSIgPiYyCiAgZmkKICBMb2c6OmxvZ1NraXBwZWQgIiQxIgp9CgojIEBkZXNjcmlwdGlvbiBsb2cgbWVzc2FnZSB0byBmaWxlCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6bG9nRXJyb3IoKSB7CiAgaWYgKChCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgPj0gX19MRVZFTF9FUlJPUikpOyB0aGVuCiAgICBMb2c6OmxvZ01lc3NhZ2UgIiR7MjotRVJST1J9IiAiJDEiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gbG9nIG1lc3NhZ2UgdG8gZmlsZQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmxvZ1dhcm5pbmcoKSB7CiAgaWYgKChCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgPj0gX19MRVZFTF9XQVJOSU5HKSk7IHRoZW4KICAgIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1XQVJOSU5HfSIgIiQxIgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIFRvIGJlIGNhbGxlZCBiZWZvcmUgbG9nZ2luZyBpbiB0aGUgbG9nIGZpbGUKIyBAYXJnICQxIGZpbGU6c3RyaW5nIGxvZyBmaWxlIG5hbWUKIyBAYXJnICQyIG1heExvZ0ZpbGVzQ291bnQ6aW50IG1heGltdW0gbnVtYmVyIG9mIGxvZyBmaWxlcwpMb2c6OnJvdGF0ZSgpIHsKICBsb2NhbCBmaWxlPSIkMSIKICBsb2NhbCBtYXhMb2dGaWxlc0NvdW50PSIkezI6LTV9IgoKICBpZiBbWyAhIC1mICIke2ZpbGV9IiBdXTsgdGhlbgogICAgTG9nOjpkaXNwbGF5U2tpcHBlZCAiTG9nIGZpbGUgJHtmaWxlfSBkb2Vzbid0IGV4aXN0IHlldCIKICAgIHJldHVybiAwCiAgZmkKICBmb3IgaSBpbiAkKHNlcSAkKChtYXhMb2dGaWxlc0NvdW50IC0gMSkpIC0xIDEpOyBkbwogICAgTG9nOjpkaXNwbGF5SW5mbyAiTG9nIHJvdGF0aW9uICR7ZmlsZX0uJHtpfSB0byAke2ZpbGV9LiQoKGkgKyAxKSkiCiAgICBtdiAiJHtmaWxlfS4ieyIke2l9IiwiJCgoaSArIDEpKSJ9ICY+L2Rldi9udWxsIHx8IHRydWUKICBkb25lCiAgaWYgY3AgIiR7ZmlsZX0iICIke2ZpbGV9LjEiICY+L2Rldi9udWxsOyB0aGVuCiAgICBlY2hvID4iJHtmaWxlfSIgIyByZXNldCBsb2cgZmlsZQogICAgTG9nOjpkaXNwbGF5SW5mbyAiTG9nIHJvdGF0aW9uICR7ZmlsZX0gdG8gJHtmaWxlfS4xIgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGVuc3VyZSBjb21tYW5kIHJlYWxwYXRoIGlzIGF2YWlsYWJsZQojIEBleGl0Y29kZSAxIGlmIHJlYWxwYXRoIGNvbW1hbmQgbm90IGF2YWlsYWJsZQojIEBzdGRlcnIgZGlhZ25vc3RpY3MgaW5mb3JtYXRpb24gaXMgZGlzcGxheWVkCkxpbnV4OjpyZXF1aXJlUmVhbHBhdGhDb21tYW5kKCkgewogIEFzc2VydDo6Y29tbWFuZEV4aXN0cyByZWFscGF0aAp9CgojIEBkZXNjcmlwdGlvbiBsb2FkIGNvbG9yIHRoZW1lCiMgQG5vYXJncwojIEBlbnYgQkFTSF9GUkFNRVdPUktfVEhFTUUgU3RyaW5nIHRoZW1lIHRvIHVzZQojIEBleGl0Y29kZSAwIGFsd2F5cyBzdWNjZXNzZnVsClVJOjpyZXF1aXJlVGhlbWUoKSB7CiAgVUk6OnRoZW1lICIke0JBU0hfRlJBTUVXT1JLX1RIRU1FLWRlZmF1bHR9Igp9CgojIEBkZXNjcmlwdGlvbiBjaGVjayBpZiBjb21tYW5kIHNwZWNpZmllZCBleGlzdHMgb3IgcmV0dXJuIDEKIyB3aXRoIGVycm9yIGFuZCBtZXNzYWdlIGlmIG5vdAojCiMgQGFyZyAkMSBjb21tYW5kTmFtZTpTdHJpbmcgb24gd2hpY2ggZXhpc3RlbmNlIG11c3QgYmUgY2hlY2tlZAojIEBhcmcgJDIgaGVscElmTm90RXhpc3RzOlN0cmluZyBhIGhlbHAgY29tbWFuZCB0byBkaXNwbGF5IGlmIHRoZSBjb21tYW5kIGRvZXMgbm90IGV4aXN0CiMKIyBAZXhpdGNvZGUgMSBpZiB0aGUgY29tbWFuZCBzcGVjaWZpZWQgZG9lcyBub3QgZXhpc3QKIyBAc3RkZXJyIGRpYWdub3N0aWMgaW5mb3JtYXRpb24gKyBoZWxwIGlmIHNlY29uZCBhcmd1bWVudCBpcyBwcm92aWRlZApBc3NlcnQ6OmNvbW1hbmRFeGlzdHMoKSB7CiAgbG9jYWwgY29tbWFuZE5hbWU9IiQxIgogIGxvY2FsIGhlbHBJZk5vdEV4aXN0cz0iJDIiCgogICIke0JBU0hfRlJBTUVXT1JLX0NPTU1BTkQ6LWNvbW1hbmR9IiAtdiAiJHtjb21tYW5kTmFtZX0iID4vZGV2L251bGwgMj4vZGV2L251bGwgfHwgewogICAgTG9nOjpkaXNwbGF5RXJyb3IgIiR7Y29tbWFuZE5hbWV9IGlzIG5vdCBpbnN0YWxsZWQsIHBsZWFzZSBpbnN0YWxsIGl0IgogICAgaWYgW1sgLW4gIiR7aGVscElmTm90RXhpc3RzfSIgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5SW5mbyAiJHtoZWxwSWZOb3RFeGlzdHN9IgogICAgZmkKICAgIHJldHVybiAxCiAgfQogIHJldHVybiAwCn0KCiMgQGRlc2NyaXB0aW9uIGRlZmF1bHQgZW52IGZpbGUgd2l0aCBhbGwgZGVmYXVsdCB2YWx1ZXMKIyBAc3Rkb3V0IHRoZSBkZWZhdWx0IGVudiBmaWxlcGF0aApFbnY6OmNyZWF0ZURlZmF1bHRFbnZGaWxlKCkgewogIGxvY2FsIGVudkZpbGUKICBlbnZGaWxlPSIkKEZyYW1ld29yazo6Y3JlYXRlVGVtcEZpbGUgImNyZWF0ZURlZmF1bHRFbnZGaWxlRW52RmlsZSIpIiB8fCByZXR1cm4gMgoKICAoCiAgICBlY2hvICJCQVNIX0ZSQU1FV09SS19USEVNRT0ke0JBU0hfRlJBTUVXT1JLX1RIRU1FOi1kZWZhdWx0fSIKICAgIGVjaG8gIkJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTD0ke0JBU0hfRlJBTUVXT1JLX0xPR19MRVZFTDotMH0iCiAgICBlY2hvICJCQVNIX0ZSQU1FV09SS19ESVNQTEFZX0xFVkVMPSR7QkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTDotJHtfX0xFVkVMX1dBUk5JTkd9fSIKICAgICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAxNgogICAgZWNobyAnQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEU9IiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEU6LSIke0ZSQU1FV09SS19ST09UX0RJUn0vbG9ncy8ke1NDUklQVF9OQU1FfS5sb2cifSInCiAgICBlY2hvICJCQVNIX0ZSQU1FV09SS19MT0dfRklMRV9NQVhfUk9UQVRJT049JHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRV9NQVhfUk9UQVRJT046LTV9IgogICkgPiIke2VudkZpbGV9IgogIGVjaG8gIiR7ZW52RmlsZX0iCn0KCiMgQGRlc2NyaXB0aW9uIHJlbW92ZSBjb21tZW50IGxpbmVzIGZyb20gaW5wdXQgb3IgZmlsZXMgcHJvdmlkZWQgYXMgYXJndW1lbnRzCiMgQGFyZyAkQCBmaWxlczpTdHJpbmdbXSAob3B0aW9uYWwpIHRoZSBmaWxlcyB0byBmaWx0ZXIKIyBAZW52IGNvbW1lbnRMaW5lUHJlZml4IFN0cmluZyB0aGUgY29tbWVudCBsaW5lIHByZWZpeCAoZGVmYXVsdCB2YWx1ZTogIykKIyBAZXhpdGNvZGUgMCBpZiBsaW5lcyBmaWx0ZXJlZCBvciBub3QKIyBAZXhpdGNvZGUgMiBpZiBncmVwIGZhaWxzIGZvciBhbnkgb3RoZXIgcmVhc29ucyB0aGFuIG5vdCBmb3VuZAojIEBzdGRpbiB0aGUgZmlsZSBhcyBzdGRpbiB0byBmaWx0ZXIgKGFsdGVybmF0aXZlIHRvIGZpbGVzIGFyZ3VtZW50KQojIEBzdGRvdXQgdGhlIGZpbHRlcmVkIGxpbmVzCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjEyMApGaWx0ZXJzOjpjb21tZW50TGluZXMoKSB7CiAgZ3JlcCAtdnhFICJbWzpibGFuazpdXSooJHtjb21tZW50TGluZVByZWZpeDotI30uKik/IiAiJEAiIHx8IHRlc3QgJD8gPSAxCn0KCiMgQGRlc2NyaXB0aW9uIGNyZWF0ZSBhIHRlbXAgZmlsZSB1c2luZyBkZWZhdWx0IFRNUERJUiB2YXJpYWJsZQojIGluaXRpYWxpemVkIGluIF9pbmNsdWRlcy9fY29tbW9uSGVhZGVyLnNoCiMgQGVudiBUTVBESVIgU3RyaW5nIChkZWZhdWx0IHZhbHVlIC90bXApCiMgQGFyZyAkMSB0ZW1wbGF0ZU5hbWU6U3RyaW5nIHRlbXBsYXRlIG5hbWUgdG8gdXNlKG9wdGlvbmFsKQpGcmFtZXdvcms6OmNyZWF0ZVRlbXBGaWxlKCkgewogIG1rdGVtcCAtcCAiJHtUTVBESVI6LS90bXB9IiAtdCAiJHsxOi19LlhYWFhYWFhYWFhYWCIKfQoKIyBAZGVzY3JpcHRpb24gbG9nIG1lc3NhZ2UgdG8gZmlsZQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmxvZ1NraXBwZWQoKSB7CiAgaWYgKChCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgPj0gX19MRVZFTF9JTkZPKSk7IHRoZW4KICAgIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1TS0lQUEVEfSIgIiQxIgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGxvYWQgY29sb3JzIHRoZW1lIGNvbnN0YW50cwojIEB3YXJuaW5nIGlmIHR0eSBub3Qgb3BlbmVkLCBub0NvbG9yIHRoZW1lIHdpbGwgYmUgY2hvc2VuCiMgQGFyZyAkMSB0aGVtZTpTdHJpbmcgdGhlIHRoZW1lIHRvIHVzZSAoZGVmYXVsdCwgbm9Db2xvcikKIyBAYXJnICRAIGFyZ3M6U3RyaW5nW10KIyBAc2V0IF9fRVJST1JfQ09MT1IgU3RyaW5nIGluZGljYXRlIGVycm9yIHN0YXR1cwojIEBzZXQgX19JTkZPX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSBpbmZvIHN0YXR1cwojIEBzZXQgX19TVUNDRVNTX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSBzdWNjZXNzIHN0YXR1cwojIEBzZXQgX19XQVJOSU5HX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSB3YXJuaW5nIHN0YXR1cwojIEBzZXQgX19TS0lQUEVEX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSBza2lwcGVkIHN0YXR1cwojIEBzZXQgX19ERUJVR19DT0xPUiBTdHJpbmcgaW5kaWNhdGUgZGVidWcgc3RhdHVzCiMgQHNldCBfX0hFTFBfQ09MT1IgU3RyaW5nIGluZGljYXRlIGhlbHAgc3RhdHVzCiMgQHNldCBfX1RFU1RfQ09MT1IgU3RyaW5nIG5vdCB1c2VkCiMgQHNldCBfX1RFU1RfRVJST1JfQ09MT1IgU3RyaW5nIG5vdCB1c2VkCiMgQHNldCBfX0hFTFBfVElUTEVfQ09MT1IgU3RyaW5nIHVzZWQgdG8gZGlzcGxheSBoZWxwIHRpdGxlIGluIGhlbHAgc3RyaW5ncwojIEBzZXQgX19IRUxQX09QVElPTl9DT0xPUiBTdHJpbmcgdXNlZCB0byBkaXNwbGF5IGhpZ2hsaWdodCBvcHRpb25zIGluIGhlbHAgc3RyaW5ncwojCiMgQHNldCBfX1JFU0VUX0NPTE9SIFN0cmluZyByZXNldCBkZWZhdWx0IGNvbG9yCiMKIyBAc2V0IF9fSEVMUF9FWEFNUExFIFN0cmluZyB0byByZW1vdmUKIyBAc2V0IF9fSEVMUF9USVRMRSBTdHJpbmcgdG8gcmVtb3ZlCiMgQHNldCBfX0hFTFBfTk9STUFMIFN0cmluZyB0byByZW1vdmUKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0ClVJOjp0aGVtZSgpIHsKICBsb2NhbCB0aGVtZT0iJHsxLWRlZmF1bHR9IgogIGlmIFtbICEgIiR7dGhlbWV9IiA9fiAtZm9yY2UkIF1dICYmICEgQXNzZXJ0Ojp0dHk7IHRoZW4KICAgIHRoZW1lPSJub0NvbG9yIgogIGZpCiAgY2FzZSAiJHt0aGVtZX0iIGluCiAgICBkZWZhdWx0IHwgZGVmYXVsdC1mb3JjZSkKICAgICAgdGhlbWU9ImRlZmF1bHQiCiAgICAgIDs7CiAgICBub0NvbG9yKSA7OwogICAgKikKICAgICAgTG9nOjpmYXRhbCAiaW52YWxpZCB0aGVtZSBwcm92aWRlZCIKICAgICAgOzsKICBlc2FjCiAgaWYgW1sgIiR7dGhlbWV9IiA9ICJkZWZhdWx0IiBdXTsgdGhlbgogICAgQkFTSF9GUkFNRVdPUktfVEhFTUU9ImRlZmF1bHQiCiAgICAjIGNoZWNrIGNvbG9ycyBhcHBsaWNhYmxlIGh0dHBzOi8vbWlzYy5mbG9naXNvZnQuY29tL2Jhc2gvdGlwX2NvbG9yc19hbmRfZm9ybWF0dGluZwogICAgX19FUlJPUl9DT0xPUj0nXGVbMzFtJyAgICAgICAgICMgUmVkCiAgICBfX0lORk9fQ09MT1I9J1xlWzQ0bScgICAgICAgICAgIyB3aGl0ZSBvbiBsaWdodEJsdWUKICAgIF9fU1VDQ0VTU19DT0xPUj0nXGVbMzJtJyAgICAgICAjIEdyZWVuCiAgICBfX1dBUk5JTkdfQ09MT1I9J1xlWzMzbScgICAgICAgIyBZZWxsb3cKICAgIF9fU0tJUFBFRF9DT0xPUj0nXGVbMzNtJyAgICAgICAjIFllbGxvdwogICAgX19ERUJVR19DT0xPUj0nXGVbMzdtJyAgICAgICAgICMgR3JleQogICAgX19IRUxQX0NPTE9SPSdcZVs3OzQ5OzMzbScgICAgICMgQmxhY2sgb24gR29sZAogICAgX19URVNUX0NPTE9SPSdcZVsxMDBtJyAgICAgICAgICMgTGlnaHQgbWFnZW50YQogICAgX19URVNUX0VSUk9SX0NPTE9SPSdcZVs0MW0nICAgICMgd2hpdGUgb24gcmVkCiAgICBfX0hFTFBfVElUTEVfQ09MT1I9IlxlWzE7MzdtIiAgIyBCb2xkCiAgICBfX0hFTFBfT1BUSU9OX0NPTE9SPSJcZVsxOzM0bSIgIyBCbHVlCiAgICAjIEludGVybmFsOiByZXNldCBjb2xvcgogICAgX19SRVNFVF9DT0xPUj0nXGVbMG0nICMgUmVzZXQgQ29sb3IKICAgICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjE1NSxTQzIwMzQKICAgIF9fSEVMUF9FWEFNUExFPSIkKGVjaG8gLWUgIlxlWzI7OTdtIikiCiAgICAjIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIxNTUsU0MyMDM0CiAgICBfX0hFTFBfVElUTEU9IiQoZWNobyAtZSAiXGVbMTszN20iKSIKICAgICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjE1NSxTQzIwMzQKICAgIF9fSEVMUF9OT1JNQUw9IiQoZWNobyAtZSAiXDAzM1swbSIpIgogIGVsc2UKICAgIEJBU0hfRlJBTUVXT1JLX1RIRU1FPSJub0NvbG9yIgogICAgIyBjaGVjayBjb2xvcnMgYXBwbGljYWJsZSBodHRwczovL21pc2MuZmxvZ2lzb2Z0LmNvbS9iYXNoL3RpcF9jb2xvcnNfYW5kX2Zvcm1hdHRpbmcKICAgIF9fRVJST1JfQ09MT1I9JycKICAgIF9fSU5GT19DT0xPUj0nJwogICAgX19TVUNDRVNTX0NPTE9SPScnCiAgICBfX1dBUk5JTkdfQ09MT1I9JycKICAgIF9fU0tJUFBFRF9DT0xPUj0nJwogICAgX19ERUJVR19DT0xPUj0nJwogICAgX19IRUxQX0NPTE9SPScnCiAgICBfX1RFU1RfQ09MT1I9JycKICAgIF9fVEVTVF9FUlJPUl9DT0xPUj0nJwogICAgX19IRUxQX1RJVExFX0NPTE9SPScnCiAgICBfX0hFTFBfT1BUSU9OX0NPTE9SPScnCiAgICAjIEludGVybmFsOiByZXNldCBjb2xvcgogICAgX19SRVNFVF9DT0xPUj0nJwogICAgX19IRUxQX0VYQU1QTEU9JycKICAgIF9fSEVMUF9USVRMRT0nJwogICAgX19IRUxQX05PUk1BTD0nJwogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGNoZWNrIGlmIHR0eSAoaW50ZXJhY3RpdmUgbW9kZSkgaXMgYWN0aXZlCiMgQG5vYXJncwojIEBleGl0Y29kZSAxIGlmIHR0eSBub3QgYWN0aXZlCiMgQGVudiBOT05fSU5URVJBQ1RJVkUgaWYgMSBjb25zaWRlciBhcyBub3QgaW50ZXJhY3RpdmUgZXZlbiBpZiBlbnZpcm9ubWVudCBpcyBpbnRlcmFjdGl2ZQojIEBlbnYgSU5URVJBQ1RJVkUgaWYgMSBjb25zaWRlciBhcyBpbnRlcmFjdGl2ZSBldmVuIGlmIGVudmlyb25tZW50IGlzIG5vdCBpbnRlcmFjdGl2ZQojIEBzdGRlcnIgZGlhZ25vc3RpYyBpbmZvcm1hdGlvbiArIGhlbHAgaWYgc2Vjb25kIGFyZ3VtZW50IGlzIHByb3ZpZGVkCkFzc2VydDo6dHR5KCkgewogIGlmIFtbICIke05PTl9JTlRFUkFDVElWRTotMH0iID0gIjEiIF1dOyB0aGVuCiAgICByZXR1cm4gMQogIGZpCiAgaWYgW1sgIiR7SU5URVJBQ1RJVkU6LTB9IiA9ICIxIiBdXTsgdGhlbgogICAgcmV0dXJuIDAKICBmaQogIFtbIC10IDEgfHwgLXQgMiBdXQp9CgojIEZVTkNUSU9OUwoKZmFjYWRlX21haW5fZW1iZWRGcmFtZXdvcmtGdW5jdGlvbmJpbkZpbGV0cGwoKSB7CiMgUkVRVUlSRVMKTGludXg6OnJlcXVpcmVFeGVjdXRlZEFzVXNlcgpFbnY6OnJlcXVpcmVMb2FkCkxvZzo6cmVxdWlyZUxvYWQKTGludXg6OnJlcXVpcmVSZWFscGF0aENvbW1hbmQKVUk6OnJlcXVpcmVUaGVtZQpDb21waWxlcjo6RmFjYWRlOjpyZXF1aXJlQ29tbWFuZEJpbkRpcgoKIyBAcmVxdWlyZSBDb21waWxlcjo6RmFjYWRlOjpyZXF1aXJlQ29tbWFuZEJpbkRpcgoKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTU0LFNDMjAxNgpmdW5jdGlvblRvQ2FsbD0nRGI6OnF1ZXJ5T25lRGF0YWJhc2UnCiIke2Z1bmN0aW9uVG9DYWxsfSIgIiRAIgoKfQoKZmFjYWRlX21haW5fZW1iZWRGcmFtZXdvcmtGdW5jdGlvbmJpbkZpbGV0cGwgIiRAIgo=" +declare -gx embed_function_DbQueryOneDatabase="${PERSISTENT_TMPDIR:-/tmp}/bin/381679fc2529dfab4ed52310d74a2fe5/dbQueryOneDatabase" +declare -gx encoded_binary_file_DbQueryOneDatabase="IyEvdXNyL2Jpbi9lbnYgYmFzaAojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgR0VORVJBVEVEIEZBQ0FERSBGUk9NIGh0dHBzOi8vZ2l0aHViLmNvbS9mY2hhc3RhbmV0L2Jhc2gtdG9vbHMvdHJlZS9tYXN0ZXIvLi4vLi4vLmNhY2hlL3ByZS1jb21taXQvcmVwb3oyNmR2OV83L3NyYy9Db21waWxlci9FbWJlZC9lbWJlZEZyYW1ld29ya0Z1bmN0aW9uLmJpbkZpbGUudHBsCiMgRE8gTk9UIEVESVQgSVQKIyBAZ2VuZXJhdGVkCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMjg4LFNDMjAzNAojIEJJTl9GSUxFPSR7QklOX0ZJTEV9CiMgRkFDQURFCgojIGVuc3VyZSB0aGF0IG5vIHVzZXIgYWxpYXNlcyBjb3VsZCBpbnRlcmZlcmUgd2l0aAojIGNvbW1hbmRzIHVzZWQgaW4gdGhpcyBzY3JpcHQKdW5hbGlhcyAtYSB8fCB0cnVlCnNob3B0IC11IGV4cGFuZF9hbGlhc2VzCgojIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIwMzQKKChmYWlsdXJlcyA9IDApKSB8fCB0cnVlCgojIEJhc2ggd2lsbCByZW1lbWJlciAmIHJldHVybiB0aGUgaGlnaGVzdCBleGl0IGNvZGUgaW4gYSBjaGFpbiBvZiBwaXBlcy4KIyBUaGlzIHdheSB5b3UgY2FuIGNhdGNoIHRoZSBlcnJvciBpbnNpZGUgcGlwZXMsIGUuZy4gbXlzcWxkdW1wIHwgZ3ppcApzZXQgLW8gcGlwZWZhaWwKc2V0IC1vIGVycmV4aXQKCiMgQ29tbWFuZCBTdWJzdGl0dXRpb24gY2FuIGluaGVyaXQgZXJyZXhpdCBvcHRpb24gc2luY2UgYmFzaCB2NC40CnNob3B0IC1zIGluaGVyaXRfZXJyZXhpdCB8fCB0cnVlCgojIGEgbG9nIGlzIGdlbmVyYXRlZCB3aGVuIGEgY29tbWFuZCBmYWlscwpzZXQgLW8gZXJydHJhY2UKCiMgdXNlIG51bGxnbG9iIHNvIHRoYXQgKGZpbGUqLnBocCkgd2lsbCByZXR1cm4gYW4gZW1wdHkgYXJyYXkgaWYgbm8gZmlsZSBtYXRjaGVzIHRoZSB3aWxkY2FyZApzaG9wdCAtcyBudWxsZ2xvYgoKIyBlbnN1cmUgcmVnZXhwIGFyZSBpbnRlcnByZXRlZCB3aXRob3V0IGFjY2VudHVhdGVkIGNoYXJhY3RlcnMKZXhwb3J0IExDX0FMTD1QT1NJWAoKZXhwb3J0IFRFUk09eHRlcm0tMjU2Y29sb3IKCiMgYXZvaWQgaW50ZXJhY3RpdmUgaW5zdGFsbApleHBvcnQgREVCSUFOX0ZST05URU5EPW5vbmludGVyYWN0aXZlCmV4cG9ydCBERUJDT05GX05PTklOVEVSQUNUSVZFX1NFRU49dHJ1ZQoKIyBzdG9yZSBjb21tYW5kIGFyZ3VtZW50cyBmb3IgbGF0ZXIgdXNhZ2UKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0CmRlY2xhcmUgLWEgQkFTSF9GUkFNRVdPUktfQVJHVj0oIiRAIikKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0CmRlY2xhcmUgLWEgT1JJR0lOQUxfQkFTSF9GUkFNRVdPUktfQVJHVj0oIiRAIikKCiMgQHNlZSBodHRwczovL3VuaXguc3RhY2tleGNoYW5nZS5jb20vYS8zODY4NTYKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMzE3CmludGVycnVwdE1hbmFnZW1lbnQoKSB7CiAgIyByZXN0b3JlIFNJR0lOVCBoYW5kbGVyCiAgdHJhcCAtIElOVAogICMgZW5zdXJlIHRoYXQgQ3RybC1DIGlzIHRyYXBwZWQgYnkgdGhpcyBzY3JpcHQgYW5kIG5vdCBieSBzdWIgcHJvY2VzcwogICMgcmVwb3J0IHRvIHRoZSBwYXJlbnQgdGhhdCB3ZSBoYXZlIGluZGVlZCBiZWVuIGludGVycnVwdGVkCiAga2lsbCAtcyBJTlQgIiQkIgp9CnRyYXAgaW50ZXJydXB0TWFuYWdlbWVudCBJTlQKU0NSSVBUX05BTUU9JHswIyMqL30KUkVBTF9TQ1JJUFRfRklMRT0iJChyZWFkbGluayAtZSAiJChyZWFscGF0aCAiJHtCQVNIX1NPVVJDRVswXX0iKSIpIgppZiBbWyAtbiAiJHtFTUJFRF9DVVJSRU5UX0RJUn0iIF1dOyB0aGVuCiAgQ1VSUkVOVF9ESVI9IiR7RU1CRURfQ1VSUkVOVF9ESVJ9IgplbHNlCiAgQ1VSUkVOVF9ESVI9IiQoY2QgIiQocmVhZGxpbmsgLWUgIiR7UkVBTF9TQ1JJUFRfRklMRSUvKn0iKSIgJiYgcHdkIC1QKSIKZmkKCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojIFRlbXAgZGlyIG1hbmFnZW1lbnQKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgpLRUVQX1RFTVBfRklMRVM9IiR7S0VFUF9URU1QX0ZJTEVTOi0wfSIKZXhwb3J0IEtFRVBfVEVNUF9GSUxFUwoKIyBQRVJTSVNURU5UX1RNUERJUiBpcyBub3QgZGVsZXRlZCBieSB0cmFwcwpQRVJTSVNURU5UX1RNUERJUj0iJHtUTVBESVI6LS90bXB9L2Jhc2gtZnJhbWV3b3JrIgpleHBvcnQgUEVSU0lTVEVOVF9UTVBESVIKbWtkaXIgLXAgIiR7UEVSU0lTVEVOVF9UTVBESVJ9IgoKIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0ClRNUERJUj0iJChta3RlbXAgLWQgLXAgIiR7UEVSU0lTVEVOVF9UTVBESVI6LS90bXB9IiAtdCBiYXNoLWZyYW1ld29yay0kJC1YWFhYWFgpIgpleHBvcnQgVE1QRElSCgojIHRlbXAgZGlyIGNsZWFuaW5nCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjMxNwpjbGVhbk9uRXhpdCgpIHsKICBpZiBbWyAiJHtLRUVQX1RFTVBfRklMRVM6LTB9IiA9ICIxIiBdXTsgdGhlbgogICAgTG9nOjpkaXNwbGF5SW5mbyAiS0VFUF9URU1QX0ZJTEVTPTEgdGVtcCBmaWxlcyBrZXB0IGhlcmUgJyR7VE1QRElSfSciCiAgZWxpZiBbWyAtbiAiJHtUTVBESVIreHh4fSIgXV07IHRoZW4KICAgIExvZzo6ZGlzcGxheURlYnVnICJLRUVQX1RFTVBfRklMRVM9MCByZW1vdmluZyB0ZW1wIGZpbGVzICcke1RNUERJUn0nIgogICAgcm0gLVJmICIke1RNUERJUjotL3RtcC9mYWtlfSIgPi9kZXYvbnVsbCAyPiYxCiAgZmkKfQp0cmFwIGNsZWFuT25FeGl0IEVYSVQgSFVQIFFVSVQgQUJSVCBURVJNCgojIFZBUl9NQUlOX0ZVTkNUSU9OX1ZBUl9OQU1FPWRiUXVlcnlBbGxEYXRhYmFzZXNGYWNhZGUKCiMgQGRlc2NyaXB0aW9uIHVzZWQgdG8gZXhlY3V0ZSBnaXZlbiBxdWVyeSB3aGVuIHVzaW5nCiMgZGJTY3JpcHRBbGxEYXRhYmFzZXMKIyBAYXJnICQxIGRzbjpTdHJpbmcKIyBAYXJnICQyIGRiOlN0cmluZwojIEBlbnYgcXVlcnkgU3RyaW5nCiMgQGVudiBvcHRpb25TZXBhcmF0b3IgU3RyaW5nCiMgQHJlcXVpcmUgTGludXg6OnJlcXVpcmVFeGVjdXRlZEFzVXNlcgpEYjo6cXVlcnlPbmVEYXRhYmFzZSgpIHsKICAjIHF1ZXJ5IGFuZCBvcHRpb25TZXBhcmF0b3IgYXJlIHBhc3NlZCB2aWEgZXhwb3J0CiAgbG9jYWwgZHNuPSIkMSIKICBsb2NhbCBkYj0iJDIiCgogIGxvY2FsIC1BIGRiSW5zdGFuY2UKICBEYXRhYmFzZTo6bmV3SW5zdGFuY2UgZGJJbnN0YW5jZSAiJHtkc259IgogIERhdGFiYXNlOjpzZXRRdWVyeU9wdGlvbnMgZGJJbnN0YW5jZSAiJHtkYkluc3RhbmNlW1FVRVJZX09QVElPTlNdfSAtLWNvbm5lY3QtdGltZW91dD01IgoKICAjIGlkZW50aWZ5IGNvbHVtbnMgaGVhZGVyCiAgZWNobyAtbiAiQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAiCiAgRGF0YWJhc2U6OnNraXBDb2x1bW5OYW1lcyBkYkluc3RhbmNlIDAKCiAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTU0CiAgaWYgISBEYXRhYmFzZTo6cXVlcnkgZGJJbnN0YW5jZSAiJHtxdWVyeX0iICIke2RifSIgfCBzZWQgInMvXHQvJHtvcHRpb25TZXBhcmF0b3J9L2ciOyB0aGVuCiAgICBMb2c6OmZhdGFsICJkYXRhYmFzZSAke2RifSBlcnJvciIgMT4mMgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIExvZyBuYW1lc3BhY2UgcHJvdmlkZXMgMiBraW5kIG9mIGZ1bmN0aW9ucwojIC0gTG9nOjpkaXNwbGF5KiBhbGxvd3MgdG8gZGlzcGxheSBnaXZlbiBtZXNzYWdlIHdpdGgKIyAgIGdpdmVuIGRpc3BsYXkgbGV2ZWwKIyAtIExvZzo6bG9nKiBhbGxvd3MgdG8gbG9nIGdpdmVuIG1lc3NhZ2Ugd2l0aAojICAgZ2l2ZW4gbG9nIGxldmVsCiMgTG9nOjpkaXNwbGF5KiBmdW5jdGlvbnMgYXV0b21hdGljYWxseSBsb2cgdGhlIG1lc3NhZ2UgdG9vCiMgQHNlZSBFbnY6OnJlcXVpcmVMb2FkIHRvIGxvYWQgdGhlIGRpc3BsYXkgYW5kIGxvZyBsZXZlbCBmcm9tIC5lbnYgZmlsZQoKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIG9mZgpleHBvcnQgX19MRVZFTF9PRkY9MAojIEBkZXNjcmlwdGlvbiBsb2cgbGV2ZWwgZXJyb3IKZXhwb3J0IF9fTEVWRUxfRVJST1I9MQojIEBkZXNjcmlwdGlvbiBsb2cgbGV2ZWwgd2FybmluZwpleHBvcnQgX19MRVZFTF9XQVJOSU5HPTIKIyBAZGVzY3JpcHRpb24gbG9nIGxldmVsIGluZm8KZXhwb3J0IF9fTEVWRUxfSU5GTz0zCiMgQGRlc2NyaXB0aW9uIGxvZyBsZXZlbCBzdWNjZXNzCmV4cG9ydCBfX0xFVkVMX1NVQ0NFU1M9MwojIEBkZXNjcmlwdGlvbiBsb2cgbGV2ZWwgZGVidWcKZXhwb3J0IF9fTEVWRUxfREVCVUc9NAoKIyBAZGVzY3JpcHRpb24gdmVyYm9zZSBsZXZlbCBvZmYKZXhwb3J0IF9fVkVSQk9TRV9MRVZFTF9PRkY9MAojIEBkZXNjcmlwdGlvbiB2ZXJib3NlIGxldmVsIGluZm8KZXhwb3J0IF9fVkVSQk9TRV9MRVZFTF9JTkZPPTEKIyBAZGVzY3JpcHRpb24gdmVyYm9zZSBsZXZlbCBpbmZvCmV4cG9ydCBfX1ZFUkJPU0VfTEVWRUxfREVCVUc9MgojIEBkZXNjcmlwdGlvbiB2ZXJib3NlIGxldmVsIGluZm8KZXhwb3J0IF9fVkVSQk9TRV9MRVZFTF9UUkFDRT0zCgojIEBkZXNjcmlwdGlvbiBEaXNwbGF5IG1lc3NhZ2UgdXNpbmcgZGVidWcgY29sb3IgKGdyZXkpCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6ZGlzcGxheURlYnVnKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCA+PSBfX0xFVkVMX0RFQlVHKSk7IHRoZW4KICAgIGVjaG8gLWUgIiR7X19ERUJVR19DT0xPUn1ERUJVRyAgIC0gJHsxfSR7X19SRVNFVF9DT0xPUn0iID4mMgogIGZpCiAgTG9nOjpsb2dEZWJ1ZyAiJDEiCn0KCiMgQGRlc2NyaXB0aW9uIERpc3BsYXkgbWVzc2FnZSB1c2luZyBpbmZvIGNvbG9yIChiZyBsaWdodCBibHVlL2ZnIHdoaXRlKQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlJbmZvKCkgewogIGxvY2FsIHR5cGU9IiR7MjotSU5GT30iCiAgaWYgKChCQVNIX0ZSQU1FV09SS19ESVNQTEFZX0xFVkVMID49IF9fTEVWRUxfSU5GTykpOyB0aGVuCiAgICBlY2hvIC1lICIke19fSU5GT19DT0xPUn0ke3R5cGV9ICAgIC0gJHsxfSR7X19SRVNFVF9DT0xPUn0iID4mMgogIGZpCiAgTG9nOjpsb2dJbmZvICIkMSIgIiR7dHlwZX0iCn0KCiMgQGRlc2NyaXB0aW9uIGVuc3VyZSBDT01NQU5EX0JJTl9ESVIgZW52IHZhciBpcyBzZXQKIyBhbmQgUEFUSCBjb3JyZWN0bHkgcHJlcGFyZWQKIyBAbm9hcmdzCiMgQHNldCBDT01NQU5EX0JJTl9ESVIgc3RyaW5nIHRoZSBkaXJlY3Rvcnkgd2hlcmUgdG8gZmluZCB0aGlzIGNvbW1hbmQKIyBAc2V0IFBBVEggc3RyaW5nIGFkZCBkaXJlY3Rvcnkgd2hlcmUgdG8gZmluZCB0aGlzIGNvbW1hbmQgYmluYXJ5CkNvbXBpbGVyOjpGYWNhZGU6OnJlcXVpcmVDb21tYW5kQmluRGlyKCkgewogIENPTU1BTkRfQklOX0RJUj0iJHtDVVJSRU5UX0RJUn0iCiAgRW52OjpwYXRoUHJlcGVuZCAiJHtDT01NQU5EX0JJTl9ESVJ9Igp9CgojIEBkZXNjcmlwdGlvbiBjcmVhdGUgYSBuZXcgZGIgaW5zdGFuY2UKIyBSZXR1cm5zIGltbWVkaWF0ZWx5IGlmIHRoZSBpbnN0YW5jZSBpcyBhbHJlYWR5IGluaXRpYWxpemVkCiMKIyBAYXJnICQxIGluc3RhbmNlTmV3SW5zdGFuY2U6Jk1hcDxTdHJpbmcsU3RyaW5nPiAocGFzc2VkIGJ5IHJlZmVyZW5jZSkgZGF0YWJhc2UgaW5zdGFuY2UgdG8gdXNlCiMgQGFyZyAkMiBkc246U3RyaW5nIGRzbiBwcm9maWxlIC0gbG9hZCB0aGUgZHNuLmVudiBwcm9maWxlIGRlZHVjZWQgdXNpbmcgcnVsZXMgZGVmaW5lZCBpbiBDb25mOjpnZXRBYnNvbHV0ZUZpbGUKIwojIEBleGFtcGxlCiMgICBkZWNsYXJlIC1BZ3ggZGJJbnN0YW5jZQojICAgRGF0YWJhc2U6Om5ld0luc3RhbmNlIGRiSW5zdGFuY2UgImRlZmF1bHQubG9jYWwiCiMKIyBAZXhpdGNvZGUgMSBpZiBkbnMgZmlsZSBub3QgYWJsZSB0byBsb2FkZWQKRGF0YWJhc2U6Om5ld0luc3RhbmNlKCkgewogIGxvY2FsIC1uIGluc3RhbmNlTmV3SW5zdGFuY2U9JDEKICBsb2NhbCBkc249IiQyIgogIGxvY2FsIERTTl9GSUxFCgogIGlmIFtbIC12IGluc3RhbmNlTmV3SW5zdGFuY2VbJ0lOSVRJQUxJWkVEJ10gJiYgIiR7aW5zdGFuY2VOZXdJbnN0YW5jZVsnSU5JVElBTElaRUQnXTotMH0iID09ICIxIiBdXTsgdGhlbgogICAgcmV0dXJuCiAgZmkKCiAgIyBmaW5hbCBhdXRoIGZpbGUgZ2VuZXJhdGVkIGZyb20gZG5zIGZpbGUKICBpbnN0YW5jZU5ld0luc3RhbmNlWydBVVRIX0ZJTEUnXT0iIgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0RTTl9GSUxFJ109IiIKCiAgIyBjaGVjayBkc24gZmlsZQogIERTTl9GSUxFPSIkKENvbmY6OmdldEFic29sdXRlRmlsZSAiZHNuIiAiJHtkc259IiAiZW52IikiIHx8IHJldHVybiAxCiAgRGF0YWJhc2U6OmNoZWNrRHNuRmlsZSAiJHtEU05fRklMRX0iIHx8IHJldHVybiAxCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnRFNOX0ZJTEUnXT0iJHtEU05fRklMRX0iCgogICMgc2hlbGxjaGVjayBzb3VyY2U9L3NyYy9EYXRhYmFzZS90ZXN0c0RhdGEvZHNuX3ZhbGlkLmVudgogIHNvdXJjZSAiJHtpbnN0YW5jZU5ld0luc3RhbmNlWydEU05fRklMRSddfSIKCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnVVNFUiddPSIke1VTRVJ9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1BBU1NXT1JEJ109IiR7UEFTU1dPUkR9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0hPU1ROQU1FJ109IiR7SE9TVE5BTUV9IgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ1BPUlQnXT0iJHtQT1JUfSIKCiAgIyBnZW5lcmF0ZSBhdXRoRmlsZSBmb3IgZWFzeSBhdXRoZW50aWNhdGlvbgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0FVVEhfRklMRSddPSQobWt0ZW1wIC1wICIke1RNUERJUjotL3RtcH0iIC10ICJteXNxbC5YWFhYWFhYWFhYWFgiKQogICgKICAgIGVjaG8gIltjbGllbnRdIgogICAgZWNobyAidXNlciA9ICR7VVNFUn0iCiAgICBlY2hvICJwYXNzd29yZCA9ICR7UEFTU1dPUkR9IgogICAgZWNobyAiaG9zdCA9ICR7SE9TVE5BTUV9IgogICAgZWNobyAicG9ydCA9ICR7UE9SVH0iCiAgKSA+IiR7aW5zdGFuY2VOZXdJbnN0YW5jZVsnQVVUSF9GSUxFJ119IgoKICAjIHNvbWUgb2YgdGhvc2UgdmFsdWVzIGNhbiBiZSBvdmVycmlkZGVuIHVzaW5nIHRoZSBkc24gZmlsZQogICMgU0tJUF9DT0xVTU5fTkFNRVMgZW5hYmxlZCBieSBkZWZhdWx0CiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnU0tJUF9DT0xVTU5fTkFNRVMnXT0iJHtTS0lQX0NPTFVNTl9OQU1FUzotMX0iCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnU1NMX09QVElPTlMnXT0iJHtNWVNRTF9TU0xfT1BUSU9OUzotLS1zc2wtbW9kZT1ESVNBQkxFRH0iCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnUVVFUllfT1BUSU9OUyddPSIke01ZU1FMX1FVRVJZX09QVElPTlM6LS0tYmF0Y2ggLS1yYXcgLS1kZWZhdWx0LWNoYXJhY3Rlci1zZXQ9dXRmOH0iCiAgaW5zdGFuY2VOZXdJbnN0YW5jZVsnRFVNUF9PUFRJT05TJ109IiR7TVlTUUxfRFVNUF9PUFRJT05TOi0tLWRlZmF1bHQtY2hhcmFjdGVyLXNldD11dGY4IC0tY29tcHJlc3MgLS1oZXgtYmxvYiAtLXJvdXRpbmVzIC0tdHJpZ2dlcnMgLS1zaW5nbGUtdHJhbnNhY3Rpb24gLS1zZXQtZ3RpZC1wdXJnZWQ9T0ZGIC0tY29sdW1uLXN0YXRpc3RpY3M9MCAke2luc3RhbmNlTmV3SW5zdGFuY2VbJ1NTTF9PUFRJT05TJ119fSIKICBpbnN0YW5jZU5ld0luc3RhbmNlWydEQl9JTVBPUlRfT1BUSU9OUyddPSIke0RCX0lNUE9SVF9PUFRJT05TOi0tLWNvbm5lY3QtdGltZW91dD01IC0tYmF0Y2ggLS1yYXcgLS1kZWZhdWx0LWNoYXJhY3Rlci1zZXQ9dXRmOH0iCgogIGluc3RhbmNlTmV3SW5zdGFuY2VbJ0lOSVRJQUxJWkVEJ109MQp9CgojIEBkZXNjcmlwdGlvbiBteXNxbCBxdWVyeSBvbiBhIGdpdmVuIGRiCiMgQHdhcm5pbmcgY291bGQgdXNlIFFVRVJZX09QVElPTlMgdmFyaWFibGUgZnJvbSBkc24gaWYgZGVmaW5lZAojIEBleGFtcGxlCiMgICBjYXQgZmlsZS5zcWwgfCBEYXRhYmFzZTo6cXVlcnkgLi4uCiMgQGFyZyAkMSBpbnN0YW5jZVF1ZXJ5OiZNYXA8U3RyaW5nLFN0cmluZz4gKHBhc3NlZCBieSByZWZlcmVuY2UpIGRhdGFiYXNlIGluc3RhbmNlIHRvIHVzZQojIEBhcmcgJDIgc3FsUXVlcnk6U3RyaW5nIChvcHRpb25hbCkgc3FsIHF1ZXJ5IG9yIHNxbCBmaWxlIHRvIGV4ZWN1dGUuIGlmIG5vdCBwcm92aWRlZCBvciBlbXB0eSwgdGhlIGNvbW1hbmQgY2FuIGJlIHBpcGVkCiMgQGFyZyAkMyBkYk5hbWU6U3RyaW5nIChvcHRpb25hbCkgdGhlIGRiIG5hbWUKIwojIEBleGl0Y29kZSBteXNxbCBjb21tYW5kIHN0YXR1cyBjb2RlCkRhdGFiYXNlOjpxdWVyeSgpIHsKICBsb2NhbCAtbiBpbnN0YW5jZVF1ZXJ5PSQxCiAgbG9jYWwgLWEgbXlzcWxDb21tYW5kPSgpCiAgbG9jYWwgLWEgcXVlcnlPcHRpb25zCgogIG15c3FsQ29tbWFuZCs9KG15c3FsKQogIG15c3FsQ29tbWFuZCs9KCItLWRlZmF1bHRzLWV4dHJhLWZpbGU9JHtpbnN0YW5jZVF1ZXJ5WydBVVRIX0ZJTEUnXX0iKQogIElGUz0nICcgcmVhZCAtciAtYSBxdWVyeU9wdGlvbnMgPDw8IiR7aW5zdGFuY2VRdWVyeVsnUVVFUllfT1BUSU9OUyddfSIKICBteXNxbENvbW1hbmQrPSgiJHtxdWVyeU9wdGlvbnNbQF19IikKICBpZiBbWyAiJHtpbnN0YW5jZVF1ZXJ5WydTS0lQX0NPTFVNTl9OQU1FUyddfSIgPSAiMSIgXV07IHRoZW4KICAgIG15c3FsQ29tbWFuZCs9KCItcyIgIi0tc2tpcC1jb2x1bW4tbmFtZXMiKQogIGZpCiAgIyBhZGQgb3B0aW9uYWwgZGIgbmFtZQogIGlmIFtbIC1uICIkezMreH0iIF1dOyB0aGVuCiAgICBteXNxbENvbW1hbmQrPSgiJDMiKQogIGZpCiAgIyBhZGQgb3B0aW9uYWwgc3FsIHF1ZXJ5CiAgaWYgW1sgLW4gIiR7Mit4fSIgJiYgLW4gIiQyIiAmJiAhIC1mICIkMiIgXV07IHRoZW4KICAgIG15c3FsQ29tbWFuZCs9KCItZSIpCiAgICBteXNxbENvbW1hbmQrPSgiJDIiKQogIGZpCiAgTG9nOjpkaXNwbGF5RGVidWcgIiQocHJpbnRmICJleGVjdXRlIGNvbW1hbmQ6ICclcyciICIke215c3FsQ29tbWFuZFsqXX0iKSIKCiAgaWYgW1sgLWYgIiQyIiBdXTsgdGhlbgogICAgIiR7bXlzcWxDb21tYW5kW0BdfSIgPCIkMiIKICBlbHNlCiAgICAiJHtteXNxbENvbW1hbmRbQF19IgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIHNldCB0aGUgZ2VuZXJhbCBvcHRpb25zIHRvIHVzZSBvbiBteXNxbCBjb21tYW5kIHRvIHF1ZXJ5IHRoZSBkYXRhYmFzZQojIERpZmZlcnMgdGhhbiBzZXRPcHRpb25zIGluIHRoZSB3YXkgdGhhdCB0aGVzZSBvcHRpb25zIGNvdWxkIGNoYW5nZSBlYWNoIHRpbWUKIwojIEBhcmcgJDEgaW5zdGFuY2VTZXRRdWVyeU9wdGlvbnM6Jk1hcDxTdHJpbmcsU3RyaW5nPiAocGFzc2VkIGJ5IHJlZmVyZW5jZSkgZGF0YWJhc2UgaW5zdGFuY2UgdG8gdXNlCiMgQGFyZyAkMiBvcHRpb25zTGlzdDpTdHJpbmcgcXVlcnkgb3B0aW9ucyBsaXN0CkRhdGFiYXNlOjpzZXRRdWVyeU9wdGlvbnMoKSB7CiAgbG9jYWwgLW4gaW5zdGFuY2VTZXRRdWVyeU9wdGlvbnM9JDEKICAjIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIwMzQKICBpbnN0YW5jZVNldFF1ZXJ5T3B0aW9uc1snUVVFUllfT1BUSU9OUyddPSIkMiIKfQoKIyBAZGVzY3JpcHRpb24gYnkgZGVmYXVsdCB3ZSBza2lwIHRoZSBjb2x1bW4gbmFtZXMKIyBidXQgc29tZXRpbWVzIHdlIG5lZWQgY29sdW1uIG5hbWVzIHRvIGRpc3BsYXkgc29tZSByZXN1bHRzCiMgZGlzYWJsZSB0aGlzIG9wdGlvbiB0ZW1wb3JhcmlseSBhbmQgdGhlbiByZXN0b3JlIGl0IHRvIHRydWUKIwojIEBhcmcgJDEgaW5zdGFuY2VTZXRRdWVyeU9wdGlvbnM6Jk1hcDxTdHJpbmcsU3RyaW5nPiAocGFzc2VkIGJ5IHJlZmVyZW5jZSkgZGF0YWJhc2UgaW5zdGFuY2UgdG8gdXNlCiMgQGFyZyAkMiBza2lwQ29sdW1uTmFtZXM6Qm9vbGVhbiAwIHRvIGRpc2FibGUsIDEgdG8gZW5hYmxlIChoaWRlIGNvbHVtbiBuYW1lcykKRGF0YWJhc2U6OnNraXBDb2x1bW5OYW1lcygpIHsKICBsb2NhbCAtbiBpbnN0YW5jZVNraXBDb2x1bW5OYW1lcz0kMQogICMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNAogIGluc3RhbmNlU2tpcENvbHVtbk5hbWVzWydTS0lQX0NPTFVNTl9OQU1FUyddPSIkMiIKfQoKIyBAZGVzY3JpcHRpb24gcHJlcGVuZCBkaXJlY3RvcmllcyB0byB0aGUgUEFUSCBlbnZpcm9ubWVudCB2YXJpYWJsZQojIEBhcmcgJEAgYXJnczpTdHJpbmdbXSBsaXN0IG9mIGRpcmVjdG9yaWVzIHRvIHByZXBlbmQKIyBAc2V0IFBBVEggdXBkYXRlIFBBVEggd2l0aCB0aGUgZGlyZWN0b3JpZXMgcHJlcGVuZGVkCkVudjo6cGF0aFByZXBlbmQoKSB7CiAgbG9jYWwgYXJnCiAgZm9yIGFyZyBpbiAiJEAiOyBkbwogICAgaWYgW1sgLWQgIiR7YXJnfSIgJiYgIjoke1BBVEh9OiIgIT0gKiI6JHthcmd9OiIqIF1dOyB0aGVuCiAgICAgIFBBVEg9IiQocmVhbHBhdGggIiR7YXJnfSIpOiR7UEFUSH0iCiAgICBmaQogIGRvbmUKfQoKIyBAZGVzY3JpcHRpb24gRGlzcGxheSBtZXNzYWdlIHVzaW5nIGVycm9yIGNvbG9yIChyZWQpIGFuZCBleGl0IGltbWVkaWF0ZWx5IHdpdGggZXJyb3Igc3RhdHVzIDEKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpmYXRhbCgpIHsKICBlY2hvIC1lICIke19fRVJST1JfQ09MT1J9RkFUQUwgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBMb2c6OmxvZ0ZhdGFsICIkMSIKICBleGl0IDEKfQoKIyBAZGVzY3JpcHRpb24gbG9nIG1lc3NhZ2UgdG8gZmlsZQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmxvZ0RlYnVnKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMID49IF9fTEVWRUxfREVCVUcpKTsgdGhlbgogICAgTG9nOjpsb2dNZXNzYWdlICIkezI6LURFQlVHfSIgIiQxIgogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGxvZyBtZXNzYWdlIHRvIGZpbGUKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpsb2dJbmZvKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMID49IF9fTEVWRUxfSU5GTykpOyB0aGVuCiAgICBMb2c6OmxvZ01lc3NhZ2UgIiR7MjotSU5GT30iICIkMSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBlbnN1cmUgcnVubmluZyB1c2VyIGlzIG5vdCByb290CiMgQGV4aXRjb2RlIDEgaWYgY3VycmVudCB1c2VyIGlzIHJvb3QKIyBAc3RkZXJyIGRpYWdub3N0aWNzIGluZm9ybWF0aW9uIGlzIGRpc3BsYXllZApMaW51eDo6cmVxdWlyZUV4ZWN1dGVkQXNVc2VyKCkgewogIGlmIFtbICIkKGlkIC11KSIgPSAiMCIgXV07IHRoZW4KICAgIExvZzo6ZmF0YWwgInRoaXMgc2NyaXB0IHNob3VsZCBiZSBleGVjdXRlZCBhcyBub3JtYWwgdXNlciIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBnZXQgYWJzb2x1dGUgY29uZiBmaWxlIGZyb20gc3BlY2lmaWVkIGNvbmYgZm9sZGVyIGRlZHVjZWQgdXNpbmcgdGhlc2UgcnVsZXMKIyAgICogZnJvbSBhYnNvbHV0ZSBmaWxlIChpZ25vcmVzIDxjb25mRm9sZGVyPiBhbmQgPGV4dGVuc2lvbj4pCiMgICAqIHJlbGF0aXZlIHRvIHdoZXJlIHNjcmlwdCBpcyBleGVjdXRlZCAoaWdub3JlcyA8Y29uZkZvbGRlcj4gYW5kIDxleHRlbnNpb24+KQojICAgKiBmcm9tIGhvbWUvLmJhc2gtdG9vbHMvPGNvbmZGb2xkZXI+CiMgICAqIGZyb20gZnJhbWV3b3JrIGNvbmYvPGNvbmZGb2xkZXI+CiMKIyBAYXJnICQxIGNvbmZGb2xkZXI6U3RyaW5nIHRoZSBkaXJlY3RvcnkgbmFtZSAobm90IHRoZSBwYXRoKSB0byBsaXN0CiMgQGFyZyAkMiBjb25mOlN0cmluZyBmaWxlIHRvIHVzZSB3aXRob3V0IGV4dGVuc2lvbgojIEBhcmcgJDMgZXh0ZW5zaW9uOlN0cmluZyB0aGUgZXh0ZW5zaW9uICguc2ggYnkgZGVmYXVsdCkKIwojIEBzdGRvdXQgYWJzb2x1dGUgY29uZiBmaWxlbmFtZQojIEBleGl0Y29kZSAxIGlmIGZpbGUgaXMgbm90IGZvdW5kIGluIGFueSBsb2NhdGlvbgpDb25mOjpnZXRBYnNvbHV0ZUZpbGUoKSB7CiAgbG9jYWwgY29uZkZvbGRlcj0iJDEiCiAgbG9jYWwgY29uZj0iJDIiCiAgbG9jYWwgZXh0ZW5zaW9uPSIkezMtLnNofSIKICBpZiBbWyAtbiAiJHtleHRlbnNpb259IiAmJiAiJHtleHRlbnNpb246MDoxfSIgIT0gIi4iIF1dOyB0aGVuCiAgICBleHRlbnNpb249Ii4ke2V4dGVuc2lvbn0iCiAgZmkKCiAgdGVzdEFicygpIHsKICAgIGxvY2FsIHJlc3VsdAogICAgcmVzdWx0PSIkKHJlYWxwYXRoIC1lICIkMSIgMj4vZGV2L251bGwpIgogICAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTgxCiAgICBpZiBbWyAiJD8iID0gIjAiICYmIC1mICIke3Jlc3VsdH0iIF1dOyB0aGVuCiAgICAgIGVjaG8gIiR7cmVzdWx0fSIKICAgICAgcmV0dXJuIDAKICAgIGZpCiAgICByZXR1cm4gMQogIH0KCiAgIyBjb25mIGlzIGFic29sdXRlIGZpbGUgKGluY2x1ZGluZyBleHRlbnNpb24pCiAgdGVzdEFicyAiJHtjb25mRm9sZGVyfSR7ZXh0ZW5zaW9ufSIgJiYgcmV0dXJuIDAKICAjIGNvbmYgaXMgYWJzb2x1dGUgZmlsZQogIHRlc3RBYnMgIiR7Y29uZkZvbGRlcn0iICYmIHJldHVybiAwCiAgIyBjb25mIGlzIGFic29sdXRlIGZpbGUgKGluY2x1ZGluZyBleHRlbnNpb24pCiAgdGVzdEFicyAiJHtjb25mfSR7ZXh0ZW5zaW9ufSIgJiYgcmV0dXJuIDAKICAjIGNvbmYgaXMgYWJzb2x1dGUgZmlsZQogIHRlc3RBYnMgIiR7Y29uZn0iICYmIHJldHVybiAwCgogICMgcmVsYXRpdmUgdG8gd2hlcmUgc2NyaXB0IGlzIGV4ZWN1dGVkIChpbmNsdWRpbmcgZXh0ZW5zaW9uKQogIGlmIFtbIC1uICIke0NVUlJFTlRfRElSK3h4eH0iIF1dOyB0aGVuCiAgICB0ZXN0QWJzICIkKEZpbGU6OmNvbmNhdGVuYXRlUGF0aCAiJHtDVVJSRU5UX0RJUn0iICIke2NvbmZGb2xkZXJ9IikvJHtjb25mfSR7ZXh0ZW5zaW9ufSIgJiYgcmV0dXJuIDAKICBmaQogICMgZnJvbSBob21lLy5iYXNoLXRvb2xzLzxjb25mRm9sZGVyPgogIHRlc3RBYnMgIiQoRmlsZTo6Y29uY2F0ZW5hdGVQYXRoICIke0hPTUV9Ly5iYXNoLXRvb2xzIiAiJHtjb25mRm9sZGVyfSIpLyR7Y29uZn0ke2V4dGVuc2lvbn0iICYmIHJldHVybiAwCgogIGlmIFtbIC1uICIke0ZSQU1FV09SS19ST09UX0RJUit4eHh9IiBdXTsgdGhlbgogICAgIyBmcm9tIGZyYW1ld29yayBjb25mLzxjb25mRm9sZGVyPiAoaW5jbHVkaW5nIGV4dGVuc2lvbikKICAgIHRlc3RBYnMgIiQoRmlsZTo6Y29uY2F0ZW5hdGVQYXRoICIke0ZSQU1FV09SS19ST09UX0RJUn0vY29uZiIgIiR7Y29uZkZvbGRlcn0iKS8ke2NvbmZ9JHtleHRlbnNpb259IiAmJiByZXR1cm4gMAoKICAgICMgZnJvbSBmcmFtZXdvcmsgY29uZi88Y29uZkZvbGRlcj4KICAgIHRlc3RBYnMgIiQoRmlsZTo6Y29uY2F0ZW5hdGVQYXRoICIke0ZSQU1FV09SS19ST09UX0RJUn0vY29uZiIgIiR7Y29uZkZvbGRlcn0iKS8ke2NvbmZ9IiAmJiByZXR1cm4gMAogIGZpCgogICMgZmlsZSBub3QgZm91bmQKICBMb2c6OmRpc3BsYXlFcnJvciAiY29uZiBmaWxlICcke2NvbmZ9JyBub3QgZm91bmQiCgogIHJldHVybiAxCn0KCiMgQGRlc2NyaXB0aW9uIGNoZWNrIGlmIGRzbiBmaWxlIGhhcyBhbGwgdGhlIG1hbmRhdG9yeSB2YXJpYWJsZXMgc2V0CiMgTWFuZGF0b3J5IHZhcmlhYmxlcyBhcmU6IEhPU1ROQU1FLCBVU0VSLCBQQVNTV09SRCwgUE9SVAojCiMgQGFyZyAkMSBkc25GaWxlTmFtZTpTdHJpbmcgZHNuIGFic29sdXRlIGZpbGVuYW1lCiMgQHNldCBIT1NUTkFNRSBsb2FkZWQgZnJvbSBkc24gZmlsZQojIEBzZXQgUE9SVCBsb2FkZWQgZnJvbSBkc24gZmlsZQojIEBzZXQgVVNFUiBsb2FkZWQgZnJvbSBkc24gZmlsZQojIEBzZXQgUEFTU1dPUkQgbG9hZGVkIGZyb20gZHNuIGZpbGUKIyBAZXhpdGNvZGUgMCBvbiB2YWxpZCBmaWxlCiMgQGV4aXRjb2RlIDEgaWYgb25lIG9mIHRoZSBwcm9wZXJ0aWVzIG9mIHRoZSBjb25mIGZpbGUgaXMgaW52YWxpZCBvciBpZiBmaWxlIG5vdCBmb3VuZAojIEBzdGRlcnIgbG9nIG91dHB1dCBpZiBlcnJvciBmb3VuZCBpbiBjb25mIGZpbGUKRGF0YWJhc2U6OmNoZWNrRHNuRmlsZSgpIHsKICBsb2NhbCBkc25GaWxlTmFtZT0iJDEiCiAgaWYgW1sgISAtZiAiJHtkc25GaWxlTmFtZX0iIF1dOyB0aGVuCiAgICBMb2c6OmRpc3BsYXlFcnJvciAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gbm90IGZvdW5kIgogICAgcmV0dXJuIDEKICBmaQoKICAoCiAgICB1bnNldCBIT1NUTkFNRSBQT1JUIFBBU1NXT1JEIFVTRVIKICAgICMgc2hlbGxjaGVjayBzb3VyY2U9L3NyYy9EYXRhYmFzZS90ZXN0c0RhdGEvZHNuX3ZhbGlkLmVudgogICAgc291cmNlICIke2RzbkZpbGVOYW1lfSIKICAgIGlmIFtbIC16ICR7SE9TVE5BTUUreH0gXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogSE9TVE5BTUUgbm90IHByb3ZpZGVkIgogICAgICByZXR1cm4gMQogICAgZmkKICAgIGlmIFtbIC16ICIke0hPU1ROQU1FfSIgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5V2FybmluZyAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gOiBIT1NUTkFNRSB2YWx1ZSBub3QgcHJvdmlkZWQiCiAgICBmaQogICAgaWYgW1sgIiR7SE9TVE5BTUV9IiA9ICJsb2NhbGhvc3QiIF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheVdhcm5pbmcgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogY2hlY2sgdGhhdCBIT1NUTkFNRSBzaG91bGQgbm90IGJlIDEyNy4wLjAuMSBpbnN0ZWFkIG9mIGxvY2FsaG9zdCIKICAgIGZpCiAgICBpZiBbWyAteiAiJHtQT1JUK3h9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlFcnJvciAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gOiBQT1JUIG5vdCBwcm92aWRlZCIKICAgICAgcmV0dXJuIDEKICAgIGZpCiAgICBpZiAhIFtbICR7UE9SVH0gPX4gXlswLTldKyQgXV07IHRoZW4KICAgICAgTG9nOjpkaXNwbGF5RXJyb3IgImRzbiBmaWxlICR7ZHNuRmlsZU5hbWV9IDogUE9SVCBpbnZhbGlkIgogICAgICByZXR1cm4gMQogICAgZmkKICAgIGlmIFtbIC16ICIke1VTRVIreH0iIF1dOyB0aGVuCiAgICAgIExvZzo6ZGlzcGxheUVycm9yICJkc24gZmlsZSAke2RzbkZpbGVOYW1lfSA6IFVTRVIgbm90IHByb3ZpZGVkIgogICAgICByZXR1cm4gMQogICAgZmkKICAgIGlmIFtbIC16ICIke1BBU1NXT1JEK3h9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlFcnJvciAiZHNuIGZpbGUgJHtkc25GaWxlTmFtZX0gOiBQQVNTV09SRCBub3QgcHJvdmlkZWQiCiAgICAgIHJldHVybiAxCiAgICBmaQogICkKfQoKIyBAZGVzY3JpcHRpb24gbG9nIG1lc3NhZ2UgdG8gZmlsZQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmxvZ0ZhdGFsKCkgewogIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1GQVRBTH0iICIkMSIKfQoKIyBAZGVzY3JpcHRpb24gSW50ZXJuYWw6IGNvbW1vbiBsb2cgbWVzc2FnZQojIEBleGFtcGxlIHRleHQKIyAgIFtkYXRlXXxbbGV2ZWxNc2ddfG1lc3NhZ2UKIwojIEBleGFtcGxlIHRleHQKIyAgIDIwMjAtMDEtMTkgMTk6MjA6MjF8RVJST1IgIHxsb2cgZXJyb3IKIyAgIDIwMjAtMDEtMTkgMTk6MjA6MjF8U0tJUFBFRHxsb2cgc2tpcHBlZAojCiMgQGFyZyAkMSBsZXZlbE1zZzpTdHJpbmcgbWVzc2FnZSdzIGxldmVsIGRlc2NyaXB0aW9uIChlZzogU1RBVFVTLCBFUlJPUiwgLi4uKQojIEBhcmcgJDIgbXNnOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CiMgQGVudiBCQVNIX0ZSQU1FV09SS19MT0dfRklMRSBTdHJpbmcgbG9nIGZpbGUgdG8gdXNlLCBkbyBub3RoaW5nIGlmIGVtcHR5CiMgQGVudiBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgaW50IGxvZyBsZXZlbCBsb2cgb25seSBpZiA+IE9GRiBvciBmYXRhbCBtZXNzYWdlcwojIEBzdGRlcnIgZGlhZ25vc3RpY3MgaW5mb3JtYXRpb24gaXMgZGlzcGxheWVkCiMgQHJlcXVpcmUgRW52OjpyZXF1aXJlTG9hZAojIEByZXF1aXJlIExvZzo6cmVxdWlyZUxvYWQKTG9nOjpsb2dNZXNzYWdlKCkgewogIGxvY2FsIGxldmVsTXNnPSIkMSIKICBsb2NhbCBtc2c9IiQyIgogIGxvY2FsIGRhdGUKCiAgaWYgW1sgLW4gIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IiBdXSAmJiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+IF9fTEVWRUxfT0ZGKSk7IHRoZW4KICAgIGRhdGU9IiQoZGF0ZSAnKyVZLSVtLSVkICVIOiVNOiVTJykiCiAgICB0b3VjaCAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iCiAgICBwcmludGYgIiVzfCU3c3wlc1xuIiAiJHtkYXRlfSIgIiR7bGV2ZWxNc2d9IiAiJHttc2d9IiA+PiIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBjb25jYXRlbmF0ZSAyIHBhdGhzIGFuZCBlbnN1cmUgdGhlIHBhdGggaXMgY29ycmVjdCB1c2luZyByZWFscGF0aCAtbQojIEBhcmcgJDEgYmFzZVBhdGg6U3RyaW5nCiMgQGFyZyAkMiBzdWJQYXRoOlN0cmluZwojIEByZXF1aXJlIExpbnV4OjpyZXF1aXJlUmVhbHBhdGhDb21tYW5kCkZpbGU6OmNvbmNhdGVuYXRlUGF0aCgpIHsKICBsb2NhbCBiYXNlUGF0aD0iJDEiCiAgbG9jYWwgc3ViUGF0aD0iJDIiCiAgbG9jYWwgZnVsbFBhdGg9IiR7YmFzZVBhdGg6KyR7YmFzZVBhdGh9L30ke3N1YlBhdGh9IgoKICByZWFscGF0aCAtbSAiJHtmdWxsUGF0aH0iIDI+L2Rldi9udWxsCn0KCiMgQGRlc2NyaXB0aW9uIERpc3BsYXkgbWVzc2FnZSB1c2luZyBlcnJvciBjb2xvciAocmVkKQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlFcnJvcigpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUwgPj0gX19MRVZFTF9FUlJPUikpOyB0aGVuCiAgICBlY2hvIC1lICIke19fRVJST1JfQ09MT1J9RVJST1IgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBmaQogIExvZzo6bG9nRXJyb3IgIiQxIgp9CgojIEBkZXNjcmlwdGlvbiBEaXNwbGF5IG1lc3NhZ2UgdXNpbmcgd2FybmluZyBjb2xvciAoeWVsbG93KQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmRpc3BsYXlXYXJuaW5nKCkgewogIGlmICgoQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCA+PSBfX0xFVkVMX1dBUk5JTkcpKTsgdGhlbgogICAgZWNobyAtZSAiJHtfX1dBUk5JTkdfQ09MT1J9V0FSTiAgICAtICR7MX0ke19fUkVTRVRfQ09MT1J9IiA+JjIKICBmaQogIExvZzo6bG9nV2FybmluZyAiJDEiCn0KCiMgQGRlc2NyaXB0aW9uIGVuc3VyZSBlbnYgZmlsZXMgYXJlIGxvYWRlZAojIEBhcmcgJEAgbGlzdCBvZiBkZWZhdWx0IGZpbGVzIHRvIGxvYWQgYXQgdGhlIGVuZAojIEBleGl0Y29kZSAxIGlmIG9uZSBvZiBlbnYgZmlsZXMgZmFpbHMgdG8gbG9hZAojIEBzdGRlcnIgZGlhZ25vc3RpY3MgaW5mb3JtYXRpb24gaXMgZGlzcGxheWVkCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjEyMApFbnY6OnJlcXVpcmVMb2FkKCkgewogIGxvY2FsIC1hIGRlZmF1bHRGaWxlcz0oIiRAIikKICAjIGdldCBsaXN0IG9mIHBvc3NpYmxlIGNvbmZpZyBmaWxlcwogIGxvY2FsIC1hIGNvbmZpZ0ZpbGVzPSgpCiAgaWYgW1sgLW4gIiR7QkFTSF9GUkFNRVdPUktfRU5WX0ZJTEVTWzBdKzF9IiBdXTsgdGhlbgogICAgIyBCQVNIX0ZSQU1FV09SS19FTlZfRklMRVMgaXMgYW4gYXJyYXkKICAgIGNvbmZpZ0ZpbGVzKz0oIiR7QkFTSF9GUkFNRVdPUktfRU5WX0ZJTEVTW0BdfSIpCiAgZmkKICBpZiBbWyAtZiAiJChwd2QpLy5mcmFtZXdvcmstY29uZmlnIiBdXTsgdGhlbgogICAgY29uZmlnRmlsZXMrPSgiJChwd2QpLy5mcmFtZXdvcmstY29uZmlnIikKICBmaQogIGlmIFtbIC1mICIke0ZSQU1FV09SS19ST09UX0RJUn0vLmZyYW1ld29yay1jb25maWciIF1dOyB0aGVuCiAgICBjb25maWdGaWxlcys9KCIke0ZSQU1FV09SS19ST09UX0RJUn0vLmZyYW1ld29yay1jb25maWciKQogIGZpCiAgaWYgW1sgLW4gIiR7b3B0aW9uQmFzaEZyYW1ld29ya0NvbmZpZ30iICYmIC1mICIke29wdGlvbkJhc2hGcmFtZXdvcmtDb25maWd9IiBdXTsgdGhlbgogICAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMDM0CiAgICBjb25maWdGaWxlcys9KCIke29wdGlvbkJhc2hGcmFtZXdvcmtDb25maWd9IikKICBmaQogIGNvbmZpZ0ZpbGVzKz0oIiR7b3B0aW9uRW52RmlsZXNbQF19IikKICBjb25maWdGaWxlcys9KCIke2RlZmF1bHRGaWxlc1tAXX0iKQoKICBmb3IgZmlsZSBpbiAiJHtjb25maWdGaWxlc1tAXX0iOyBkbwogICAgIyBzaGVsbGNoZWNrIHNvdXJjZT0vLmZyYW1ld29yay1jb25maWcKICAgIHNvdXJjZSAiJHtmaWxlfSIgfHwgewogICAgICBMb2c6OmRpc3BsYXlFcnJvciAid2hpbGUgbG9hZGluZyBjb25maWcgZmlsZTogJHtmaWxlfSIKICAgICAgcmV0dXJuIDEKICAgIH0KICBkb25lCn0KCiMgQGRlc2NyaXB0aW9uIGFjdGl2YXRlIG9yIG5vdCBMb2c6OmRpc3BsYXkqIGFuZCBMb2c6OmxvZyogZnVuY3Rpb25zCiMgYmFzZWQgb24gQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCBhbmQgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMCiMgZW52aXJvbm1lbnQgdmFyaWFibGVzIGxvYWRlZCBieSBFbnY6OnJlcXVpcmVMb2FkCiMgdHJ5IHRvIGNyZWF0ZSBsb2cgZmlsZSBhbmQgcm90YXRlIGl0IGlmIG5lY2Vzc2FyeQojIEBub2FyZ3MKIyBAc2V0IEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCBpbnQgdG8gT0ZGIGxldmVsIGlmIEJBU0hfRlJBTUVXT1JLX0xPR19GSUxFIGlzIGVtcHR5IG9yIG5vdCB3cml0YWJsZQojIEBlbnYgQkFTSF9GUkFNRVdPUktfRElTUExBWV9MRVZFTCBpbnQKIyBAZW52IEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCBpbnQKIyBAZW52IEJBU0hfRlJBTUVXT1JLX0xPR19GSUxFIFN0cmluZwojIEBlbnYgQkFTSF9GUkFNRVdPUktfTE9HX0ZJTEVfTUFYX1JPVEFUSU9OIGludCBkbyBsb2cgcm90YXRpb24gaWYgPiAwCiMgQGV4aXRjb2RlIDAgYWx3YXlzIHN1Y2Nlc3NmdWwKIyBAc3RkZXJyIGRpYWdub3N0aWNzIGluZm9ybWF0aW9uIGFib3V0IGxvZyBmaWxlIGlzIGRpc3BsYXllZAojIEByZXF1aXJlIEVudjo6cmVxdWlyZUxvYWQKIyBAcmVxdWlyZSBVSTo6cmVxdWlyZVRoZW1lCkxvZzo6cmVxdWlyZUxvYWQoKSB7CiAgaWYgW1sgLXogIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEU6LX0iIF1dOyB0aGVuCiAgICBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUw9JHtfX0xFVkVMX09GRn0KICAgIGV4cG9ydCBCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwKICBmaQoKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+IF9fTEVWRUxfT0ZGKSk7IHRoZW4KICAgIGlmIFtbICEgLWYgIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IiBdXTsgdGhlbgogICAgICBpZgogICAgICAgICEgbWtkaXIgLXAgIiQoZGlybmFtZSAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iKSIgMj4vZGV2L251bGwgfHwKICAgICAgICAgICEgdG91Y2ggLS1uby1jcmVhdGUgIiR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IiAyPi9kZXYvbnVsbAogICAgICB0aGVuCiAgICAgICAgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMPSR7X19MRVZFTF9PRkZ9CiAgICAgICAgZWNobyAtZSAiJHtfX0VSUk9SX0NPTE9SfUVSUk9SICAgLSBGaWxlICR7QkFTSF9GUkFNRVdPUktfTE9HX0ZJTEV9IGlzIG5vdCB3cml0YWJsZSR7X19SRVNFVF9DT0xPUn0iID4mMgogICAgICBmaQogICAgZWxpZiBbWyAhIC13ICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSIgXV07IHRoZW4KICAgICAgQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMPSR7X19MRVZFTF9PRkZ9CiAgICAgIGVjaG8gLWUgIiR7X19FUlJPUl9DT0xPUn1FUlJPUiAgIC0gRmlsZSAke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSBpcyBub3Qgd3JpdGFibGUke19fUkVTRVRfQ09MT1J9IiA+JjIKICAgIGZpCgogIGZpCgogIGlmICgoQkFTSF9GUkFNRVdPUktfTE9HX0xFVkVMID4gX19MRVZFTF9PRkYpKTsgdGhlbgogICAgIyB3aWxsIGFsd2F5cyBiZSBjcmVhdGVkIGV2ZW4gaWYgbm90IGluIGluZm8gbGV2ZWwKICAgIExvZzo6bG9nTWVzc2FnZSAiSU5GTyIgIkxvZ2dpbmcgdG8gZmlsZSAke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFfSAtIExvZyBsZXZlbCAke0JBU0hfRlJBTUVXT1JLX0xPR19MRVZFTH0iCiAgICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19GSUxFX01BWF9ST1RBVElPTiA+IDApKTsgdGhlbgogICAgICBMb2c6OnJvdGF0ZSAiJHtCQVNIX0ZSQU1FV09SS19MT0dfRklMRX0iICIke0JBU0hfRlJBTUVXT1JLX0xPR19GSUxFX01BWF9ST1RBVElPTn0iCiAgICBmaQogIGZpCn0KCiMgQGRlc2NyaXB0aW9uIGxvZyBtZXNzYWdlIHRvIGZpbGUKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpsb2dFcnJvcigpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+PSBfX0xFVkVMX0VSUk9SKSk7IHRoZW4KICAgIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1FUlJPUn0iICIkMSIKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBsb2cgbWVzc2FnZSB0byBmaWxlCiMgQGFyZyAkMSBtZXNzYWdlOlN0cmluZyB0aGUgbWVzc2FnZSB0byBkaXNwbGF5CkxvZzo6bG9nV2FybmluZygpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0xPR19MRVZFTCA+PSBfX0xFVkVMX1dBUk5JTkcpKTsgdGhlbgogICAgTG9nOjpsb2dNZXNzYWdlICIkezI6LVdBUk5JTkd9IiAiJDEiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gVG8gYmUgY2FsbGVkIGJlZm9yZSBsb2dnaW5nIGluIHRoZSBsb2cgZmlsZQojIEBhcmcgJDEgZmlsZTpzdHJpbmcgbG9nIGZpbGUgbmFtZQojIEBhcmcgJDIgbWF4TG9nRmlsZXNDb3VudDppbnQgbWF4aW11bSBudW1iZXIgb2YgbG9nIGZpbGVzCkxvZzo6cm90YXRlKCkgewogIGxvY2FsIGZpbGU9IiQxIgogIGxvY2FsIG1heExvZ0ZpbGVzQ291bnQ9IiR7MjotNX0iCgogIGlmIFtbICEgLWYgIiR7ZmlsZX0iIF1dOyB0aGVuCiAgICBMb2c6OmRpc3BsYXlTa2lwcGVkICJMb2cgZmlsZSAke2ZpbGV9IGRvZXNuJ3QgZXhpc3QgeWV0IgogICAgcmV0dXJuIDAKICBmaQogIGZvciBpIGluICQoc2VxICQoKG1heExvZ0ZpbGVzQ291bnQgLSAxKSkgLTEgMSk7IGRvCiAgICBMb2c6OmRpc3BsYXlJbmZvICJMb2cgcm90YXRpb24gJHtmaWxlfS4ke2l9IHRvICR7ZmlsZX0uJCgoaSArIDEpKSIKICAgIG12ICIke2ZpbGV9LiJ7IiR7aX0iLCIkKChpICsgMSkpIn0gJj4vZGV2L251bGwgfHwgdHJ1ZQogIGRvbmUKICBpZiBjcCAiJHtmaWxlfSIgIiR7ZmlsZX0uMSIgJj4vZGV2L251bGw7IHRoZW4KICAgIGVjaG8gPiIke2ZpbGV9IiAjIHJlc2V0IGxvZyBmaWxlCiAgICBMb2c6OmRpc3BsYXlJbmZvICJMb2cgcm90YXRpb24gJHtmaWxlfSB0byAke2ZpbGV9LjEiCiAgZmkKfQoKIyBAZGVzY3JpcHRpb24gZW5zdXJlIGNvbW1hbmQgcmVhbHBhdGggaXMgYXZhaWxhYmxlCiMgQGV4aXRjb2RlIDEgaWYgcmVhbHBhdGggY29tbWFuZCBub3QgYXZhaWxhYmxlCiMgQHN0ZGVyciBkaWFnbm9zdGljcyBpbmZvcm1hdGlvbiBpcyBkaXNwbGF5ZWQKTGludXg6OnJlcXVpcmVSZWFscGF0aENvbW1hbmQoKSB7CiAgQXNzZXJ0Ojpjb21tYW5kRXhpc3RzIHJlYWxwYXRoCn0KCiMgQGRlc2NyaXB0aW9uIGxvYWQgY29sb3IgdGhlbWUKIyBAbm9hcmdzCiMgQGVudiBCQVNIX0ZSQU1FV09SS19USEVNRSBTdHJpbmcgdGhlbWUgdG8gdXNlCiMgQGV4aXRjb2RlIDAgYWx3YXlzIHN1Y2Nlc3NmdWwKVUk6OnJlcXVpcmVUaGVtZSgpIHsKICBVSTo6dGhlbWUgIiR7QkFTSF9GUkFNRVdPUktfVEhFTUUtZGVmYXVsdH0iCn0KCiMgQGRlc2NyaXB0aW9uIGNoZWNrIGlmIGNvbW1hbmQgc3BlY2lmaWVkIGV4aXN0cyBvciByZXR1cm4gMQojIHdpdGggZXJyb3IgYW5kIG1lc3NhZ2UgaWYgbm90CiMKIyBAYXJnICQxIGNvbW1hbmROYW1lOlN0cmluZyBvbiB3aGljaCBleGlzdGVuY2UgbXVzdCBiZSBjaGVja2VkCiMgQGFyZyAkMiBoZWxwSWZOb3RFeGlzdHM6U3RyaW5nIGEgaGVscCBjb21tYW5kIHRvIGRpc3BsYXkgaWYgdGhlIGNvbW1hbmQgZG9lcyBub3QgZXhpc3QKIwojIEBleGl0Y29kZSAxIGlmIHRoZSBjb21tYW5kIHNwZWNpZmllZCBkb2VzIG5vdCBleGlzdAojIEBzdGRlcnIgZGlhZ25vc3RpYyBpbmZvcm1hdGlvbiArIGhlbHAgaWYgc2Vjb25kIGFyZ3VtZW50IGlzIHByb3ZpZGVkCkFzc2VydDo6Y29tbWFuZEV4aXN0cygpIHsKICBsb2NhbCBjb21tYW5kTmFtZT0iJDEiCiAgbG9jYWwgaGVscElmTm90RXhpc3RzPSIkMiIKCiAgIiR7QkFTSF9GUkFNRVdPUktfQ09NTUFORDotY29tbWFuZH0iIC12ICIke2NvbW1hbmROYW1lfSIgPi9kZXYvbnVsbCAyPi9kZXYvbnVsbCB8fCB7CiAgICBMb2c6OmRpc3BsYXlFcnJvciAiJHtjb21tYW5kTmFtZX0gaXMgbm90IGluc3RhbGxlZCwgcGxlYXNlIGluc3RhbGwgaXQiCiAgICBpZiBbWyAtbiAiJHtoZWxwSWZOb3RFeGlzdHN9IiBdXTsgdGhlbgogICAgICBMb2c6OmRpc3BsYXlJbmZvICIke2hlbHBJZk5vdEV4aXN0c30iCiAgICBmaQogICAgcmV0dXJuIDEKICB9CiAgcmV0dXJuIDAKfQoKIyBAZGVzY3JpcHRpb24gRGlzcGxheSBtZXNzYWdlIHVzaW5nIHNraXAgY29sb3IgKHllbGxvdykKIyBAYXJnICQxIG1lc3NhZ2U6U3RyaW5nIHRoZSBtZXNzYWdlIHRvIGRpc3BsYXkKTG9nOjpkaXNwbGF5U2tpcHBlZCgpIHsKICBpZiAoKEJBU0hfRlJBTUVXT1JLX0RJU1BMQVlfTEVWRUwgPj0gX19MRVZFTF9JTkZPKSk7IHRoZW4KICAgIGVjaG8gLWUgIiR7X19TS0lQUEVEX0NPTE9SfVNLSVBQRUQgLSAkezF9JHtfX1JFU0VUX0NPTE9SfSIgPiYyCiAgZmkKICBMb2c6OmxvZ1NraXBwZWQgIiQxIgp9CgojIEBkZXNjcmlwdGlvbiBsb2FkIGNvbG9ycyB0aGVtZSBjb25zdGFudHMKIyBAd2FybmluZyBpZiB0dHkgbm90IG9wZW5lZCwgbm9Db2xvciB0aGVtZSB3aWxsIGJlIGNob3NlbgojIEBhcmcgJDEgdGhlbWU6U3RyaW5nIHRoZSB0aGVtZSB0byB1c2UgKGRlZmF1bHQsIG5vQ29sb3IpCiMgQGFyZyAkQCBhcmdzOlN0cmluZ1tdCiMgQHNldCBfX0VSUk9SX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSBlcnJvciBzdGF0dXMKIyBAc2V0IF9fSU5GT19DT0xPUiBTdHJpbmcgaW5kaWNhdGUgaW5mbyBzdGF0dXMKIyBAc2V0IF9fU1VDQ0VTU19DT0xPUiBTdHJpbmcgaW5kaWNhdGUgc3VjY2VzcyBzdGF0dXMKIyBAc2V0IF9fV0FSTklOR19DT0xPUiBTdHJpbmcgaW5kaWNhdGUgd2FybmluZyBzdGF0dXMKIyBAc2V0IF9fU0tJUFBFRF9DT0xPUiBTdHJpbmcgaW5kaWNhdGUgc2tpcHBlZCBzdGF0dXMKIyBAc2V0IF9fREVCVUdfQ09MT1IgU3RyaW5nIGluZGljYXRlIGRlYnVnIHN0YXR1cwojIEBzZXQgX19IRUxQX0NPTE9SIFN0cmluZyBpbmRpY2F0ZSBoZWxwIHN0YXR1cwojIEBzZXQgX19URVNUX0NPTE9SIFN0cmluZyBub3QgdXNlZAojIEBzZXQgX19URVNUX0VSUk9SX0NPTE9SIFN0cmluZyBub3QgdXNlZAojIEBzZXQgX19IRUxQX1RJVExFX0NPTE9SIFN0cmluZyB1c2VkIHRvIGRpc3BsYXkgaGVscCB0aXRsZSBpbiBoZWxwIHN0cmluZ3MKIyBAc2V0IF9fSEVMUF9PUFRJT05fQ09MT1IgU3RyaW5nIHVzZWQgdG8gZGlzcGxheSBoaWdobGlnaHQgb3B0aW9ucyBpbiBoZWxwIHN0cmluZ3MKIwojIEBzZXQgX19SRVNFVF9DT0xPUiBTdHJpbmcgcmVzZXQgZGVmYXVsdCBjb2xvcgojCiMgQHNldCBfX0hFTFBfRVhBTVBMRSBTdHJpbmcgdG8gcmVtb3ZlCiMgQHNldCBfX0hFTFBfVElUTEUgU3RyaW5nIHRvIHJlbW92ZQojIEBzZXQgX19IRUxQX05PUk1BTCBTdHJpbmcgdG8gcmVtb3ZlCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMjAzNApVSTo6dGhlbWUoKSB7CiAgbG9jYWwgdGhlbWU9IiR7MS1kZWZhdWx0fSIKICBpZiBbWyAhICIke3RoZW1lfSIgPX4gLWZvcmNlJCBdXSAmJiAhIEFzc2VydDo6dHR5OyB0aGVuCiAgICB0aGVtZT0ibm9Db2xvciIKICBmaQogIGNhc2UgIiR7dGhlbWV9IiBpbgogICAgZGVmYXVsdCB8IGRlZmF1bHQtZm9yY2UpCiAgICAgIHRoZW1lPSJkZWZhdWx0IgogICAgICA7OwogICAgbm9Db2xvcikgOzsKICAgICopCiAgICAgIExvZzo6ZmF0YWwgImludmFsaWQgdGhlbWUgcHJvdmlkZWQiCiAgICAgIDs7CiAgZXNhYwogIGlmIFtbICIke3RoZW1lfSIgPSAiZGVmYXVsdCIgXV07IHRoZW4KICAgIEJBU0hfRlJBTUVXT1JLX1RIRU1FPSJkZWZhdWx0IgogICAgIyBjaGVjayBjb2xvcnMgYXBwbGljYWJsZSBodHRwczovL21pc2MuZmxvZ2lzb2Z0LmNvbS9iYXNoL3RpcF9jb2xvcnNfYW5kX2Zvcm1hdHRpbmcKICAgIF9fRVJST1JfQ09MT1I9J1xlWzMxbScgICAgICAgICAjIFJlZAogICAgX19JTkZPX0NPTE9SPSdcZVs0NG0nICAgICAgICAgICMgd2hpdGUgb24gbGlnaHRCbHVlCiAgICBfX1NVQ0NFU1NfQ09MT1I9J1xlWzMybScgICAgICAgIyBHcmVlbgogICAgX19XQVJOSU5HX0NPTE9SPSdcZVszM20nICAgICAgICMgWWVsbG93CiAgICBfX1NLSVBQRURfQ09MT1I9J1xlWzMzbScgICAgICAgIyBZZWxsb3cKICAgIF9fREVCVUdfQ09MT1I9J1xlWzM3bScgICAgICAgICAjIEdyZXkKICAgIF9fSEVMUF9DT0xPUj0nXGVbNzs0OTszM20nICAgICAjIEJsYWNrIG9uIEdvbGQKICAgIF9fVEVTVF9DT0xPUj0nXGVbMTAwbScgICAgICAgICAjIExpZ2h0IG1hZ2VudGEKICAgIF9fVEVTVF9FUlJPUl9DT0xPUj0nXGVbNDFtJyAgICAjIHdoaXRlIG9uIHJlZAogICAgX19IRUxQX1RJVExFX0NPTE9SPSJcZVsxOzM3bSIgICMgQm9sZAogICAgX19IRUxQX09QVElPTl9DT0xPUj0iXGVbMTszNG0iICMgQmx1ZQogICAgIyBJbnRlcm5hbDogcmVzZXQgY29sb3IKICAgIF9fUkVTRVRfQ09MT1I9J1xlWzBtJyAjIFJlc2V0IENvbG9yCiAgICAjIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIxNTUsU0MyMDM0CiAgICBfX0hFTFBfRVhBTVBMRT0iJChlY2hvIC1lICJcZVsyOzk3bSIpIgogICAgIyBzaGVsbGNoZWNrIGRpc2FibGU9U0MyMTU1LFNDMjAzNAogICAgX19IRUxQX1RJVExFPSIkKGVjaG8gLWUgIlxlWzE7MzdtIikiCiAgICAjIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIxNTUsU0MyMDM0CiAgICBfX0hFTFBfTk9STUFMPSIkKGVjaG8gLWUgIlwwMzNbMG0iKSIKICBlbHNlCiAgICBCQVNIX0ZSQU1FV09SS19USEVNRT0ibm9Db2xvciIKICAgICMgY2hlY2sgY29sb3JzIGFwcGxpY2FibGUgaHR0cHM6Ly9taXNjLmZsb2dpc29mdC5jb20vYmFzaC90aXBfY29sb3JzX2FuZF9mb3JtYXR0aW5nCiAgICBfX0VSUk9SX0NPTE9SPScnCiAgICBfX0lORk9fQ09MT1I9JycKICAgIF9fU1VDQ0VTU19DT0xPUj0nJwogICAgX19XQVJOSU5HX0NPTE9SPScnCiAgICBfX1NLSVBQRURfQ09MT1I9JycKICAgIF9fREVCVUdfQ09MT1I9JycKICAgIF9fSEVMUF9DT0xPUj0nJwogICAgX19URVNUX0NPTE9SPScnCiAgICBfX1RFU1RfRVJST1JfQ09MT1I9JycKICAgIF9fSEVMUF9USVRMRV9DT0xPUj0nJwogICAgX19IRUxQX09QVElPTl9DT0xPUj0nJwogICAgIyBJbnRlcm5hbDogcmVzZXQgY29sb3IKICAgIF9fUkVTRVRfQ09MT1I9JycKICAgIF9fSEVMUF9FWEFNUExFPScnCiAgICBfX0hFTFBfVElUTEU9JycKICAgIF9fSEVMUF9OT1JNQUw9JycKICBmaQp9CgojIEBkZXNjcmlwdGlvbiBjaGVjayBpZiB0dHkgKGludGVyYWN0aXZlIG1vZGUpIGlzIGFjdGl2ZQojIEBub2FyZ3MKIyBAZXhpdGNvZGUgMSBpZiB0dHkgbm90IGFjdGl2ZQojIEBlbnYgTk9OX0lOVEVSQUNUSVZFIGlmIDEgY29uc2lkZXIgYXMgbm90IGludGVyYWN0aXZlIGV2ZW4gaWYgZW52aXJvbm1lbnQgaXMgaW50ZXJhY3RpdmUKIyBAZW52IElOVEVSQUNUSVZFIGlmIDEgY29uc2lkZXIgYXMgaW50ZXJhY3RpdmUgZXZlbiBpZiBlbnZpcm9ubWVudCBpcyBub3QgaW50ZXJhY3RpdmUKIyBAc3RkZXJyIGRpYWdub3N0aWMgaW5mb3JtYXRpb24gKyBoZWxwIGlmIHNlY29uZCBhcmd1bWVudCBpcyBwcm92aWRlZApBc3NlcnQ6OnR0eSgpIHsKICBpZiBbWyAiJHtOT05fSU5URVJBQ1RJVkU6LTB9IiA9ICIxIiBdXTsgdGhlbgogICAgcmV0dXJuIDEKICBmaQogIGlmIFtbICIke0lOVEVSQUNUSVZFOi0wfSIgPSAiMSIgXV07IHRoZW4KICAgIHJldHVybiAwCiAgZmkKICBbWyAtdCAxIHx8IC10IDIgXV0KfQoKIyBAZGVzY3JpcHRpb24gbG9nIG1lc3NhZ2UgdG8gZmlsZQojIEBhcmcgJDEgbWVzc2FnZTpTdHJpbmcgdGhlIG1lc3NhZ2UgdG8gZGlzcGxheQpMb2c6OmxvZ1NraXBwZWQoKSB7CiAgaWYgKChCQVNIX0ZSQU1FV09SS19MT0dfTEVWRUwgPj0gX19MRVZFTF9JTkZPKSk7IHRoZW4KICAgIExvZzo6bG9nTWVzc2FnZSAiJHsyOi1TS0lQUEVEfSIgIiQxIgogIGZpCn0KCiMgRlVOQ1RJT05TCgpmYWNhZGVfbWFpbl9lbWJlZEZyYW1ld29ya0Z1bmN0aW9uYmluRmlsZXRwbCgpIHsKIyBSRVFVSVJFUwpMaW51eDo6cmVxdWlyZUV4ZWN1dGVkQXNVc2VyCkVudjo6cmVxdWlyZUxvYWQKTG9nOjpyZXF1aXJlTG9hZApMaW51eDo6cmVxdWlyZVJlYWxwYXRoQ29tbWFuZApVSTo6cmVxdWlyZVRoZW1lCkNvbXBpbGVyOjpGYWNhZGU6OnJlcXVpcmVDb21tYW5kQmluRGlyCgojIEByZXF1aXJlIENvbXBpbGVyOjpGYWNhZGU6OnJlcXVpcmVDb21tYW5kQmluRGlyCgojIHNoZWxsY2hlY2sgZGlzYWJsZT1TQzIxNTQsU0MyMDE2CmZ1bmN0aW9uVG9DYWxsPSdEYjo6cXVlcnlPbmVEYXRhYmFzZScKIiR7ZnVuY3Rpb25Ub0NhbGx9IiAiJEAiCgp9CgpmYWNhZGVfbWFpbl9lbWJlZEZyYW1ld29ya0Z1bmN0aW9uYmluRmlsZXRwbCAiJEAiCg==" Compiler::Embed::extractFileFromBase64 \ "${embed_function_DbQueryOneDatabase}" \ @@ -1319,6 +1114,7 @@ optionVersionCallback() { # shellcheck disable=SC2317 # if function is overridden optionEnvFileCallback() { local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" exit 1 @@ -1453,54 +1249,47 @@ defaultFrameworkConfig="$( # copied from src/_includes/.framework-config.default # shellcheck disable=SC2034 +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")/../.." && pwd -P)}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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)" +NON_FRAMEWORK_FILES_REGEXP="${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)" +BATS_FILE_NOT_NEEDED_REGEXP="${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$" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|\.tpl$|/testsData/|^manualTests/|\.bats$}" # Source directories -FRAMEWORK_SRC_DIRS=( - "${FRAMEWORK_ROOT_DIR}/src" -) +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi # export here all the variables that will be used in your templates -export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/$(basename "$0").log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" 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 default template framework config + # shellcheck disable=SC2034 + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" > "${defaultEnvFile}" - # 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 + Env::requireLoad "${defaultEnvFile}" + Log::requireLoad if [[ "${optionConfig}" = "1" ]]; then displayConfig @@ -2027,7 +1816,7 @@ dbScriptAllDatabasesCommand() { echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file) + helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray diff --git a/bin/doc b/bin/doc index fcf0212d..d965aed4 100755 --- a/bin/doc +++ b/bin/doc @@ -212,42 +212,38 @@ Array::wrap() { #Array::wrap ":" 40 0 "Lorem ipsum dolor sit amet," "consectetur adipiscing elit." "Curabitur ac elit id massa" "condimentum finibus." # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") + fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } -} - -# @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 "$@" + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description Log namespace provides 2 kind of functions @@ -574,122 +570,6 @@ Assert::tty() { [[ -t 1 || -t 2 ]] } -# @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" -# -# @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 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 -} - -# @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 -} - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -738,15 +618,6 @@ Filters::removeAnsiCodes() { # cspell:enable } -# @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" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -849,67 +720,6 @@ 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 -# -# @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 [[ -f "${fromPath}" ]]; then - fromPath="$(dirname "${fromPath}")" - fi - while true; do - if [[ -f "${fromPath}/${fileName}" ]]; then - echo "${fromPath}/${fileName}" - return 0 - fi - if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then - return 1 - fi - fromPath="$(readlink -f "${fromPath}"/..)" - done - return 1 -} - -# @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 -} - # @description create a temp file using default TMPDIR variable # initialized in _includes/_commonHeader.sh # @env TMPDIR String (default value /tmp) @@ -918,6 +728,15 @@ Framework::createTempFile() { mktemp -p "${TMPDIR:-/tmp}" -t "${1:-}.XXXXXXXXXXXX" } +# @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" +} + # @description log message to file # @arg $1 message:String the message to display Log::logSkipped() { @@ -926,22 +745,6 @@ Log::logSkipped() { fi } -# @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 facade_main_docsh() { @@ -1024,6 +827,7 @@ optionVersionCallback() { # shellcheck disable=SC2317 # if function is overridden optionEnvFileCallback() { local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" exit 1 @@ -1158,54 +962,47 @@ defaultFrameworkConfig="$( # copied from src/_includes/.framework-config.default # shellcheck disable=SC2034 +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")/../.." && pwd -P)}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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)" +NON_FRAMEWORK_FILES_REGEXP="${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)" +BATS_FILE_NOT_NEEDED_REGEXP="${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$" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|\.tpl$|/testsData/|^manualTests/|\.bats$}" # Source directories -FRAMEWORK_SRC_DIRS=( - "${FRAMEWORK_ROOT_DIR}/src" -) +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi # export here all the variables that will be used in your templates -export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/$(basename "$0").log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" 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 default template framework config + # shellcheck disable=SC2034 + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" > "${defaultEnvFile}" - # 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 + Env::requireLoad "${defaultEnvFile}" + Log::requireLoad if [[ "${optionConfig}" = "1" ]]; then displayConfig @@ -1556,7 +1353,7 @@ docCommand() { echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file) + helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray @@ -1645,7 +1442,7 @@ docCommand parse "${BASH_FRAMEWORK_ARGV[@]}" run() { if [[ "${IN_BASH_DOCKER:-}" != "You're in docker" ]]; then DOCKER_RUN_OPTIONS=$"-e ORIGINAL_DOC_DIR=${DOC_DIR}" \ - "${COMMAND_BIN_DIR}/runBuildContainer" "/bash/bin/doc" \ + "${FRAMEWORK_BIN_DIR}/runBuildContainer" "/bash/bin/doc" \ "${RUN_CONTAINER_ARGV_FILTERED[@]}" return $? fi diff --git a/bin/gitIsAncestorOf b/bin/gitIsAncestorOf index 92bd0884..98f532ec 100755 --- a/bin/gitIsAncestorOf +++ b/bin/gitIsAncestorOf @@ -212,42 +212,38 @@ Array::wrap() { #Array::wrap ":" 40 0 "Lorem ipsum dolor sit amet," "consectetur adipiscing elit." "Curabitur ac elit id massa" "condimentum finibus." # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") + fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } -} - -# @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 "$@" + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description Log namespace provides 2 kind of functions @@ -492,122 +488,6 @@ Assert::tty() { [[ -t 1 || -t 2 ]] } -# @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" -# -# @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 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 -} - -# @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 -} - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -633,15 +513,6 @@ Filters::removeAnsiCodes() { # cspell:enable } -# @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" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -736,73 +607,13 @@ 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 -# -# @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 [[ -f "${fromPath}" ]]; then - fromPath="$(dirname "${fromPath}")" +# @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 - while true; do - if [[ -f "${fromPath}/${fileName}" ]]; then - echo "${fromPath}/${fileName}" - return 0 - fi - if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then - return 1 - fi - fromPath="$(readlink -f "${fromPath}"/..)" - done - return 1 -} - -# @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 -} - -# @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" + Log::logSkipped "$1" } # @description log message to file @@ -813,22 +624,6 @@ Log::logSkipped() { fi } -# @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 facade_main_gitIsAncestorOfsh() { @@ -912,6 +707,7 @@ optionVersionCallback() { # shellcheck disable=SC2317 # if function is overridden optionEnvFileCallback() { local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" exit 1 @@ -1046,54 +842,47 @@ defaultFrameworkConfig="$( # copied from src/_includes/.framework-config.default # shellcheck disable=SC2034 +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")/../.." && pwd -P)}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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)" +NON_FRAMEWORK_FILES_REGEXP="${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)" +BATS_FILE_NOT_NEEDED_REGEXP="${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$" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|\.tpl$|/testsData/|^manualTests/|\.bats$}" # Source directories -FRAMEWORK_SRC_DIRS=( - "${FRAMEWORK_ROOT_DIR}/src" -) +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi # export here all the variables that will be used in your templates -export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/$(basename "$0").log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" 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 default template framework config + # shellcheck disable=SC2034 + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" > "${defaultEnvFile}" - # 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 + Env::requireLoad "${defaultEnvFile}" + Log::requireLoad if [[ "${optionConfig}" = "1" ]]; then displayConfig @@ -1465,7 +1254,7 @@ gitIsAncestorOfCommand() { echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file) + helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray diff --git a/bin/gitIsBranch b/bin/gitIsBranch index db8d3d7e..13ac130f 100755 --- a/bin/gitIsBranch +++ b/bin/gitIsBranch @@ -212,42 +212,38 @@ Array::wrap() { #Array::wrap ":" 40 0 "Lorem ipsum dolor sit amet," "consectetur adipiscing elit." "Curabitur ac elit id massa" "condimentum finibus." # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") + fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } -} - -# @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 "$@" + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description Log namespace provides 2 kind of functions @@ -492,122 +488,6 @@ Assert::tty() { [[ -t 1 || -t 2 ]] } -# @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" -# -# @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 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 -} - -# @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 -} - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -633,15 +513,6 @@ Filters::removeAnsiCodes() { # cspell:enable } -# @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" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -736,73 +607,13 @@ 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 -# -# @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 [[ -f "${fromPath}" ]]; then - fromPath="$(dirname "${fromPath}")" +# @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 - while true; do - if [[ -f "${fromPath}/${fileName}" ]]; then - echo "${fromPath}/${fileName}" - return 0 - fi - if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then - return 1 - fi - fromPath="$(readlink -f "${fromPath}"/..)" - done - return 1 -} - -# @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 -} - -# @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" + Log::logSkipped "$1" } # @description log message to file @@ -813,22 +624,6 @@ Log::logSkipped() { fi } -# @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 facade_main_gitIsBranchsh() { @@ -911,6 +706,7 @@ optionVersionCallback() { # shellcheck disable=SC2317 # if function is overridden optionEnvFileCallback() { local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" exit 1 @@ -1045,54 +841,47 @@ defaultFrameworkConfig="$( # copied from src/_includes/.framework-config.default # shellcheck disable=SC2034 +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")/../.." && pwd -P)}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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)" +NON_FRAMEWORK_FILES_REGEXP="${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)" +BATS_FILE_NOT_NEEDED_REGEXP="${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$" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|\.tpl$|/testsData/|^manualTests/|\.bats$}" # Source directories -FRAMEWORK_SRC_DIRS=( - "${FRAMEWORK_ROOT_DIR}/src" -) +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi # export here all the variables that will be used in your templates -export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/$(basename "$0").log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" 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 default template framework config + # shellcheck disable=SC2034 + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" > "${defaultEnvFile}" - # 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 + Env::requireLoad "${defaultEnvFile}" + Log::requireLoad if [[ "${optionConfig}" = "1" ]]; then displayConfig @@ -1443,7 +1232,7 @@ gitIsBranchCommand() { echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file) + helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray diff --git a/bin/gitRenameBranch b/bin/gitRenameBranch index c27ed4c2..b17adeec 100755 --- a/bin/gitRenameBranch +++ b/bin/gitRenameBranch @@ -228,42 +228,38 @@ Assert::tty() { } # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") + fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } -} - -# @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 "$@" + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description Log namespace provides 2 kind of functions @@ -514,122 +510,6 @@ Linux::requireExecutedAsUser() { fi } -# @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" -# -# @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 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 -} - -# @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 -} - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -655,15 +535,6 @@ Filters::removeAnsiCodes() { # cspell:enable } -# @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" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -758,73 +629,13 @@ 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 -# -# @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 [[ -f "${fromPath}" ]]; then - fromPath="$(dirname "${fromPath}")" +# @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 - while true; do - if [[ -f "${fromPath}/${fileName}" ]]; then - echo "${fromPath}/${fileName}" - return 0 - fi - if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then - return 1 - fi - fromPath="$(readlink -f "${fromPath}"/..)" - done - return 1 -} - -# @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 -} - -# @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" + Log::logSkipped "$1" } # @description log message to file @@ -835,22 +646,6 @@ Log::logSkipped() { fi } -# @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 facade_main_gitRenameBranchsh() { @@ -940,6 +735,7 @@ optionVersionCallback() { # shellcheck disable=SC2317 # if function is overridden optionEnvFileCallback() { local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" exit 1 @@ -1074,54 +870,47 @@ defaultFrameworkConfig="$( # copied from src/_includes/.framework-config.default # shellcheck disable=SC2034 +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")/../.." && pwd -P)}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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)" +NON_FRAMEWORK_FILES_REGEXP="${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)" +BATS_FILE_NOT_NEEDED_REGEXP="${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$" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|\.tpl$|/testsData/|^manualTests/|\.bats$}" # Source directories -FRAMEWORK_SRC_DIRS=( - "${FRAMEWORK_ROOT_DIR}/src" -) +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi # export here all the variables that will be used in your templates -export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/$(basename "$0").log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" 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 default template framework config + # shellcheck disable=SC2034 + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" > "${defaultEnvFile}" - # 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 + Env::requireLoad "${defaultEnvFile}" + Log::requireLoad if [[ "${optionConfig}" = "1" ]]; then displayConfig @@ -1549,7 +1338,7 @@ gitRenameBranchCommand() { echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file) + helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray diff --git a/bin/installRequirements b/bin/installRequirements index b5b1e355..2ab13892 100755 --- a/bin/installRequirements +++ b/bin/installRequirements @@ -212,42 +212,38 @@ Array::wrap() { #Array::wrap ":" 40 0 "Lorem ipsum dolor sit amet," "consectetur adipiscing elit." "Curabitur ac elit id massa" "condimentum finibus." # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") + fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } -} - -# @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 "$@" + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description clone the repository if not done yet, else pull it if no change in it @@ -532,122 +528,6 @@ Assert::tty() { [[ -t 1 || -t 2 ]] } -# @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" -# -# @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 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 -} - -# @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 -} - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -699,15 +579,6 @@ Git::pullIfNoChanges() { ) } -# @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" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -802,81 +673,13 @@ 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 -# -# @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 [[ -f "${fromPath}" ]]; then - fromPath="$(dirname "${fromPath}")" - fi - while true; do - if [[ -f "${fromPath}/${fileName}" ]]; then - echo "${fromPath}/${fileName}" - return 0 - fi - if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then - return 1 - fi - fromPath="$(readlink -f "${fromPath}"/..)" - done - return 1 -} - -# @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 -} - -# @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" -} - -# @description log message to file +# @description Display message using skip color (yellow) # @arg $1 message:String the message to display -Log::logSkipped() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO)); then - Log::logMessage "${2:-SKIPPED}" "$1" +Log::displaySkipped() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO)); then + echo -e "${__SKIPPED_COLOR}SKIPPED - ${1}${__RESET_COLOR}" >&2 fi + Log::logSkipped "$1" } # @description ensure command git is available @@ -886,22 +689,6 @@ Git::requireGitCommand() { Assert::commandExists git } -# @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 -} - # @description check if command specified exists or return 1 # with error and message if not # @@ -924,6 +711,14 @@ Assert::commandExists() { return 0 } +# @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 +} + # FUNCTIONS facade_main_installRequirementssh() { @@ -1007,6 +802,7 @@ optionVersionCallback() { # shellcheck disable=SC2317 # if function is overridden optionEnvFileCallback() { local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" exit 1 @@ -1141,54 +937,47 @@ defaultFrameworkConfig="$( # copied from src/_includes/.framework-config.default # shellcheck disable=SC2034 +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")/../.." && pwd -P)}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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)" +NON_FRAMEWORK_FILES_REGEXP="${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)" +BATS_FILE_NOT_NEEDED_REGEXP="${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$" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|\.tpl$|/testsData/|^manualTests/|\.bats$}" # Source directories -FRAMEWORK_SRC_DIRS=( - "${FRAMEWORK_ROOT_DIR}/src" -) +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi # export here all the variables that will be used in your templates -export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/$(basename "$0").log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" 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 default template framework config + # shellcheck disable=SC2034 + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" > "${defaultEnvFile}" - # 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 + Env::requireLoad "${defaultEnvFile}" + Log::requireLoad if [[ "${optionConfig}" = "1" ]]; then displayConfig @@ -1510,7 +1299,7 @@ installRequirementsCommand() { echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file) + helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray @@ -1556,7 +1345,7 @@ installRequirementsCommand() { INSTALLS REQUIREMENTS: - fchastanet/bash-tools-framework - and fchastanet/bash-tools-framework useful binaries: - bin/runBuildContainer, bin/test""" + bin/test""" echo echo -n -e "${__HELP_TITLE_COLOR}VERSION: ${__RESET_COLOR}" echo '1.0' @@ -1576,7 +1365,7 @@ installRequirementsCommand() { return 1 fi } -declare -a externalBinaries=([0]="bin/runBuildContainer" [1]="bin/test") +declare -a externalBinaries=([0]="bin/test") installRequirementsCommand parse "${BASH_FRAMEWORK_ARGV[@]}" diff --git a/bin/mysql2puml b/bin/mysql2puml index 4d153384..bb1517b9 100755 --- a/bin/mysql2puml +++ b/bin/mysql2puml @@ -319,28 +319,38 @@ Conf::getMergedList() { } # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description remove all empty lines @@ -367,20 +377,6 @@ Framework::createTempFile() { mktemp -p "${TMPDIR:-/tmp}" -t "${1:-}.XXXXXXXXXXXX" } -# @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 "$@" -} - # @description Log namespace provides 2 kind of functions # - Log::display* allows to display given message with # given display level @@ -649,122 +645,6 @@ Conf::list() { ) } -# @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" -# -# @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 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 -} - -# @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 -} - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -802,15 +682,6 @@ Filters::removeAnsiCodes() { # cspell:enable } -# @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" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -905,73 +776,13 @@ 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 -# -# @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 [[ -f "${fromPath}" ]]; then - fromPath="$(dirname "${fromPath}")" - fi - while true; do - if [[ -f "${fromPath}/${fileName}" ]]; then - echo "${fromPath}/${fileName}" - return 0 - fi - if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then - return 1 - fi - fromPath="$(readlink -f "${fromPath}"/..)" - done - return 1 -} - -# @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 -} - -# @description log message to file +# @description Display message using skip color (yellow) # @arg $1 message:String the message to display -Log::logSkipped() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO)); then - Log::logMessage "${2:-SKIPPED}" "$1" +Log::displaySkipped() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO)); then + echo -e "${__SKIPPED_COLOR}SKIPPED - ${1}${__RESET_COLOR}" >&2 fi + Log::logSkipped "$1" } # @description ensure command realpath is available @@ -1003,6 +814,14 @@ Assert::commandExists() { return 0 } +# @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 +} + # FUNCTIONS facade_main_mysql2pumlsh() { @@ -1086,6 +905,7 @@ optionVersionCallback() { # shellcheck disable=SC2317 # if function is overridden optionEnvFileCallback() { local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" exit 1 @@ -1220,54 +1040,47 @@ defaultFrameworkConfig="$( # copied from src/_includes/.framework-config.default # shellcheck disable=SC2034 +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")/../.." && pwd -P)}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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)" +NON_FRAMEWORK_FILES_REGEXP="${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)" +BATS_FILE_NOT_NEEDED_REGEXP="${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$" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|\.tpl$|/testsData/|^manualTests/|\.bats$}" # Source directories -FRAMEWORK_SRC_DIRS=( - "${FRAMEWORK_ROOT_DIR}/src" -) +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi # export here all the variables that will be used in your templates -export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/$(basename "$0").log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" 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 default template framework config + # shellcheck disable=SC2034 + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" > "${defaultEnvFile}" - # 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 + Env::requireLoad "${defaultEnvFile}" + Log::requireLoad if [[ "${optionConfig}" = "1" ]]; then displayConfig @@ -1643,7 +1456,7 @@ mysql2pumlCommand() { echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file) + helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray diff --git a/bin/upgradeGithubRelease b/bin/upgradeGithubRelease index 530eb068..4f7e60e6 100755 --- a/bin/upgradeGithubRelease +++ b/bin/upgradeGithubRelease @@ -254,42 +254,38 @@ Assert::validPath() { } # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") + fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } -} - -# @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 "$@" + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description intermediate callback that is used by Github::upgradeRelease @@ -665,122 +661,6 @@ Assert::tty() { [[ -t 1 || -t 2 ]] } -# @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" -# -# @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 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 -} - -# @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 -} - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -849,15 +729,6 @@ Github::getLatestRelease() { rm -f "${resultFile}" } -# @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" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -968,81 +839,13 @@ 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 -# -# @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 [[ -f "${fromPath}" ]]; then - fromPath="$(dirname "${fromPath}")" - fi - while true; do - if [[ -f "${fromPath}/${fileName}" ]]; then - echo "${fromPath}/${fileName}" - return 0 - fi - if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then - return 1 - fi - fromPath="$(readlink -f "${fromPath}"/..)" - done - return 1 -} - -# @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 -} - -# @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" -} - -# @description log message to file +# @description Display message using skip color (yellow) # @arg $1 message:String the message to display -Log::logSkipped() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO)); then - Log::logMessage "${2:-SKIPPED}" "$1" +Log::displaySkipped() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO)); then + echo -e "${__SKIPPED_COLOR}SKIPPED - ${1}${__RESET_COLOR}" >&2 fi + Log::logSkipped "$1" } # @description Retry a command several times depending on parameters @@ -1092,20 +895,12 @@ Version::githubApiExtractVersion() { jq -r ".tag_name" | Version::parse } -# @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 +# @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 } # @description ensure command jq is available @@ -1230,6 +1025,7 @@ optionVersionCallback() { # shellcheck disable=SC2317 # if function is overridden optionEnvFileCallback() { local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" exit 1 @@ -1364,54 +1160,47 @@ defaultFrameworkConfig="$( # copied from src/_includes/.framework-config.default # shellcheck disable=SC2034 +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")/../.." && pwd -P)}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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)" +NON_FRAMEWORK_FILES_REGEXP="${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)" +BATS_FILE_NOT_NEEDED_REGEXP="${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$" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|\.tpl$|/testsData/|^manualTests/|\.bats$}" # Source directories -FRAMEWORK_SRC_DIRS=( - "${FRAMEWORK_ROOT_DIR}/src" -) +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi # export here all the variables that will be used in your templates -export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/$(basename "$0").log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" 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 default template framework config + # shellcheck disable=SC2034 + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" > "${defaultEnvFile}" - # 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 + Env::requireLoad "${defaultEnvFile}" + Log::requireLoad if [[ "${optionConfig}" = "1" ]]; then displayConfig @@ -1882,7 +1671,7 @@ upgradeGithubReleaseCommand() { echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file) + helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray diff --git a/bin/waitForIt b/bin/waitForIt index 99da67d8..9d12da25 100755 --- a/bin/waitForIt +++ b/bin/waitForIt @@ -250,42 +250,38 @@ Assert::commandExists() { } # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") + fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } -} - -# @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 "$@" + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description Log namespace provides 2 kind of functions @@ -521,122 +517,6 @@ Assert::tty() { [[ -t 1 || -t 2 ]] } -# @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" -# -# @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 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 -} - -# @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 -} - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -662,15 +542,6 @@ Filters::removeAnsiCodes() { # cspell:enable } -# @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" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -765,73 +636,13 @@ 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 -# -# @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 [[ -f "${fromPath}" ]]; then - fromPath="$(dirname "${fromPath}")" +# @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 - while true; do - if [[ -f "${fromPath}/${fileName}" ]]; then - echo "${fromPath}/${fileName}" - return 0 - fi - if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then - return 1 - fi - fromPath="$(readlink -f "${fromPath}"/..)" - done - return 1 -} - -# @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 -} - -# @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" + Log::logSkipped "$1" } # @description log message to file @@ -938,6 +749,7 @@ optionVersionCallback() { # shellcheck disable=SC2317 # if function is overridden optionEnvFileCallback() { local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" exit 1 @@ -1072,54 +884,47 @@ defaultFrameworkConfig="$( # copied from src/_includes/.framework-config.default # shellcheck disable=SC2034 +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")/../.." && pwd -P)}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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)" +NON_FRAMEWORK_FILES_REGEXP="${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)" +BATS_FILE_NOT_NEEDED_REGEXP="${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$" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|\.tpl$|/testsData/|^manualTests/|\.bats$}" # Source directories -FRAMEWORK_SRC_DIRS=( - "${FRAMEWORK_ROOT_DIR}/src" -) +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi # export here all the variables that will be used in your templates -export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/$(basename "$0").log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" 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 default template framework config + # shellcheck disable=SC2034 + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" > "${defaultEnvFile}" - # 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 + Env::requireLoad "${defaultEnvFile}" + Log::requireLoad if [[ "${optionConfig}" = "1" ]]; then displayConfig @@ -1589,7 +1394,7 @@ waitForItCommand() { echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file) + helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray diff --git a/bin/waitForMysql b/bin/waitForMysql index 4b129253..823a4d57 100755 --- a/bin/waitForMysql +++ b/bin/waitForMysql @@ -234,42 +234,38 @@ Assert::commandExists() { } # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") + fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } -} - -# @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 "$@" + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description Log namespace provides 2 kind of functions @@ -505,122 +501,6 @@ Assert::tty() { [[ -t 1 || -t 2 ]] } -# @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" -# -# @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 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 -} - -# @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 -} - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -646,15 +526,6 @@ Filters::removeAnsiCodes() { # cspell:enable } -# @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" -} - # @description log message to file # @arg $1 message:String the message to display Log::logDebug() { @@ -749,73 +620,13 @@ 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 -# -# @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 [[ -f "${fromPath}" ]]; then - fromPath="$(dirname "${fromPath}")" +# @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 - while true; do - if [[ -f "${fromPath}/${fileName}" ]]; then - echo "${fromPath}/${fileName}" - return 0 - fi - if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then - return 1 - fi - fromPath="$(readlink -f "${fromPath}"/..)" - done - return 1 -} - -# @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 -} - -# @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" + Log::logSkipped "$1" } # @description log message to file @@ -826,22 +637,6 @@ Log::logSkipped() { fi } -# @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 facade_main_waitForMysqlsh() { @@ -931,6 +726,7 @@ optionVersionCallback() { # shellcheck disable=SC2317 # if function is overridden optionEnvFileCallback() { local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" exit 1 @@ -1065,54 +861,47 @@ defaultFrameworkConfig="$( # copied from src/_includes/.framework-config.default # shellcheck disable=SC2034 +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")/../.." && pwd -P)}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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)" +NON_FRAMEWORK_FILES_REGEXP="${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)" +BATS_FILE_NOT_NEEDED_REGEXP="${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$" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|\.tpl$|/testsData/|^manualTests/|\.bats$}" # Source directories -FRAMEWORK_SRC_DIRS=( - "${FRAMEWORK_ROOT_DIR}/src" -) +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi # export here all the variables that will be used in your templates -export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/$(basename "$0").log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" 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 default template framework config + # shellcheck disable=SC2034 + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" > "${defaultEnvFile}" - # 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 + Env::requireLoad "${defaultEnvFile}" + Log::requireLoad if [[ "${optionConfig}" = "1" ]]; then displayConfig @@ -1561,7 +1350,7 @@ waitForMysqlCommand() { echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file) + helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray diff --git a/build.sh b/build.sh deleted file mode 100755 index 58ae3ff9..00000000 --- a/build.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash - -BASH_TOOLS_ROOT_DIR=$(cd "$(readlink -e "${BASH_SOURCE[0]%/*}")" && pwd -P) -BASH_TOOLS_SRC_DIR="${BASH_TOOLS_ROOT_DIR}/src" -FRAMEWORK_ROOT_DIR="${BASH_TOOLS_ROOT_DIR}/vendor/bash-tools-framework" -FRAMEWORK_BIN_DIR="${FRAMEWORK_ROOT_DIR}/bin" -COMMAND_BIN_DIR="${BASH_TOOLS_ROOT_DIR}/bin" - -# shellcheck source=./vendor/bash-tools-framework/src/_includes/_header.sh -source "${FRAMEWORK_ROOT_DIR}/src/_includes/_header.sh" - -# srcFile : file that needs to be compiled -# templateDir : directory from which bash-tpl templates will be searched -# binDir : fallback bin directory in case BIN_FILE has not been provided -# rootDir : directory used to compute src file relative path -# srcDirs : additional directories where to find the functions -declare -a params=( - --src-dir "${BASH_TOOLS_SRC_DIR}" - --bin-dir "${COMMAND_BIN_DIR}" - --root-dir "${BASH_TOOLS_ROOT_DIR}" - --template-dir "${BASH_TOOLS_SRC_DIR}" -) -if [[ "${ARGS_VERBOSE}" = "1" ]]; then - params+=("-vvv") -fi - -( - if (($# == 0)); then - find "${BASH_TOOLS_SRC_DIR}/_binaries" -name "*.sh" | - (grep -v -E '/testsData/' || true) - else - for file in "$@"; do - realpath "${file}" - done - fi -) | xargs -L1 -P8 -I{} \ - "${FRAMEWORK_BIN_DIR}/compile" "{}" "${params[@]}" diff --git a/conf/dbScripts/extractData b/conf/dbScripts/extractData index 77c0629e..904a4489 100755 --- a/conf/dbScripts/extractData +++ b/conf/dbScripts/extractData @@ -537,28 +537,38 @@ Log::logWarning() { } # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") + fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description activate or not Log::display* and Log::log* functions @@ -606,96 +616,6 @@ Log::requireLoad() { fi } -# @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 -} - -# @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 -} - -# @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" -} - # @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 @@ -725,49 +645,13 @@ 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 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 -} - -# @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" -} - -# @description log message to file +# @description Display message using skip color (yellow) # @arg $1 message:String the message to display -Log::logSkipped() { - if ((BASH_FRAMEWORK_LOG_LEVEL >= __LEVEL_INFO)); then - Log::logMessage "${2:-SKIPPED}" "$1" +Log::displaySkipped() { + if ((BASH_FRAMEWORK_DISPLAY_LEVEL >= __LEVEL_INFO)); then + echo -e "${__SKIPPED_COLOR}SKIPPED - ${1}${__RESET_COLOR}" >&2 fi + Log::logSkipped "$1" } # @description load colors theme constants @@ -866,6 +750,14 @@ Assert::tty() { [[ -t 1 || -t 2 ]] } +# @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 +} + # FUNCTIONS facade_main_extractDatash() { diff --git a/install b/install index e4711ce5..192c3023 100755 --- a/install +++ b/install @@ -212,42 +212,38 @@ Array::wrap() { #Array::wrap ":" 40 0 "Lorem ipsum dolor sit amet," "consectetur adipiscing elit." "Curabitur ac elit id massa" "condimentum finibus." # @description ensure env files are loaded -# @noargs -# @exitcode 1 if getOrderedConfFiles fails -# @exitcode 2 if one of env files fails to load +# @arg $@ list of default files to load at the end +# @exitcode 1 if one of env files fails to load # @stderr diagnostics information is displayed +# shellcheck disable=SC2120 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 + local -a defaultFiles=("$@") + # get list of possible config files + 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 + if [[ -f "$(pwd)/.framework-config" ]]; then + configFiles+=("$(pwd)/.framework-config") + fi + if [[ -f "${FRAMEWORK_ROOT_DIR}/.framework-config" ]]; then + configFiles+=("${FRAMEWORK_ROOT_DIR}/.framework-config") + fi + if [[ -n "${optionBashFrameworkConfig}" && -f "${optionBashFrameworkConfig}" ]]; then + # shellcheck disable=SC2034 + configFiles+=("${optionBashFrameworkConfig}") + fi + configFiles+=("${optionEnvFiles[@]}") + configFiles+=("${defaultFiles[@]}") - Env::mergeConfFiles "${configFiles[@]}" || { - Log::displayError "while loading config files: ${configFiles[*]}" - return 2 - } -} - -# @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 "$@" + for file in "${configFiles[@]}"; do + # shellcheck source=/.framework-config + source "${file}" || { + Log::displayError "while loading config file: ${file}" + return 1 + } + done } # @description Log namespace provides 2 kind of functions @@ -501,122 +497,6 @@ Assert::tty() { [[ -t 1 || -t 2 ]] } -# @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" -# -# @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 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 -} - -# @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 -} - # @description prepend directories to the PATH environment variable # @arg $@ args:String[] list of directories to prepend # @set PATH update PATH with the directories prepended @@ -744,91 +624,6 @@ 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 -# -# @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 [[ -f "${fromPath}" ]]; then - fromPath="$(dirname "${fromPath}")" - fi - while true; do - if [[ -f "${fromPath}/${fileName}" ]]; then - echo "${fromPath}/${fileName}" - return 0 - fi - if Array::contains "${fromPath}" "${untilInclusivePath}" "$@" "/"; then - return 1 - fi - fromPath="$(readlink -f "${fromPath}"/..)" - done - return 1 -} - -# @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 -} - -# @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" -} - -# @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 facade_main_installsh() { @@ -911,6 +706,7 @@ optionVersionCallback() { # shellcheck disable=SC2317 # if function is overridden optionEnvFileCallback() { local envFile="$2" + Log::displayWarning "Command ${SCRIPT_NAME} - Option --env-file is deprecated and will be removed in the future" if [[ ! -f "${envFile}" || ! -r "${envFile}" ]]; then Log::displayError "Command ${SCRIPT_NAME} - Option --env-file - File '${envFile}' doesn't exist" exit 1 @@ -1045,54 +841,47 @@ defaultFrameworkConfig="$( # copied from src/_includes/.framework-config.default # shellcheck disable=SC2034 +REAL_SCRIPT_FILE="${REAL_SCRIPT_FILE:-$(readlink -e "$(realpath "${BASH_SOURCE[0]}")")}" +FRAMEWORK_ROOT_DIR="${FRAMEWORK_ROOT_DIR:-$(cd "$(readlink -e "${REAL_SCRIPT_FILE%/*}")/../.." && pwd -P)}" +FRAMEWORK_SRC_DIR="${FRAMEWORK_SRC_DIR:-${FRAMEWORK_ROOT_DIR}/src}" +FRAMEWORK_BIN_DIR="${FRAMEWORK_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/bin}" +FRAMEWORK_VENDOR_DIR="${FRAMEWORK_VENDOR_DIR:-${FRAMEWORK_ROOT_DIR}/vendor}" +FRAMEWORK_VENDOR_BIN_DIR="${FRAMEWORK_VENDOR_BIN_DIR:-${FRAMEWORK_ROOT_DIR}/vendor/bin}" + # describe the functions that will be skipped from being imported -FRAMEWORK_FUNCTIONS_IGNORE_REGEXP='^(Namespace::functions|Functions::myFunction|Namespace::requireSomething|Acquire::ForceIPv4)$' +FRAMEWORK_FUNCTIONS_IGNORE_REGEXP="${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)" +NON_FRAMEWORK_FILES_REGEXP="${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)" +BATS_FILE_NOT_NEEDED_REGEXP="${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$" +FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP="${FRAMEWORK_FILES_FUNCTION_MATCHING_IGNORE_REGEXP:-^bin/|^\.framework-config$|\.tpl$|/testsData/|^manualTests/|\.bats$}" # Source directories -FRAMEWORK_SRC_DIRS=( - "${FRAMEWORK_ROOT_DIR}/src" -) +if [[ ! -v FRAMEWORK_SRC_DIRS ]]; then + FRAMEWORK_SRC_DIRS=( + "${FRAMEWORK_ROOT_DIR}/src" + ) +fi # export here all the variables that will be used in your templates -export REPOSITORY_URL="https://github.com/fchastanet/bash-tools-framework" +export REPOSITORY_URL="${REPOSITORY_URL:-https://github.com/fchastanet/bash-tools-framework}" + +BASH_FRAMEWORK_THEME="${BASH_FRAMEWORK_THEME:-default}" +BASH_FRAMEWORK_LOG_LEVEL="${BASH_FRAMEWORK_LOG_LEVEL:-0}" +BASH_FRAMEWORK_DISPLAY_LEVEL="${BASH_FRAMEWORK_DISPLAY_LEVEL:-3}" +BASH_FRAMEWORK_LOG_FILE="${BASH_FRAMEWORK_LOG_FILE:-${FRAMEWORK_ROOT_DIR}/logs/$(basename "$0").log}" +BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION="${BASH_FRAMEWORK_LOG_FILE_MAX_ROTATION:-5}" 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 default template framework config + # shellcheck disable=SC2034 + defaultEnvFile="${PERSISTENT_TMPDIR}/.framework-config" + echo "${defaultFrameworkConfig}" > "${defaultEnvFile}" - # 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 + Env::requireLoad "${defaultEnvFile}" + Log::requireLoad if [[ "${optionConfig}" = "1" ]]; then displayConfig @@ -1416,7 +1205,7 @@ installCommand() { echo -e " ${__HELP_OPTION_COLOR}--env-file ${__HELP_NORMAL} {list} (optional)" local -a helpArray # shellcheck disable=SC2054 - helpArray=(Load\ the\ specified\ env\ file) + helpArray=(Load\ the\ specified\ env\ file\ \(deprecated\,\ please\ use\ --bash-framework-config\ option\ instead\)) echo -e " $(Array::wrap " " 76 4 "${helpArray[@]}")" echo -e " ${__HELP_OPTION_COLOR}--no-color${__HELP_NORMAL} {single}" local -a helpArray diff --git a/src/_binaries/Converters/testsData/mysql2puml.help.txt b/src/_binaries/Converters/testsData/mysql2puml.help.txt index b8ee7cce..8495ecfa 100644 --- a/src/_binaries/Converters/testsData/mysql2puml.help.txt +++ b/src/_binaries/Converters/testsData/mysql2puml.help.txt @@ -27,7 +27,8 @@ -vvv {single} trace level verbose mode (alias of --display-level TRACE) --env-file  {list} (optional) - Load the specified env file + Load the specified env file (deprecated, please use --bash-framework-config + option instead) --no-color {single} Produce monochrome output. alias of --theme noColor. --theme  {single} diff --git a/src/_binaries/DbImport/dbImport.bats b/src/_binaries/DbImport/dbImport.bats index 744554d9..87539ada 100755 --- a/src/_binaries/DbImport/dbImport.bats +++ b/src/_binaries/DbImport/dbImport.bats @@ -308,7 +308,6 @@ function Database::dbImport::import_from_aws_with_tables_filter { #@test export BASH_FRAMEWORK_ENV_FILEPATH="${BATS_TEST_DIRNAME}/testsData/.env" run "${binDir}/dbImport" --verbose --from-aws fromDb.tar.gz toDb --tables dataTable,otherTable 2>&1 - assert_output --partial "Import database duration : " assert_output --partial "ignore table emptyTable" assert_output --partial "begin insert dataTable" 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 2ec8a5fd..33f38ff5 100755 --- a/src/_binaries/DbImport/testsData/auto_default.local_fromDb_20.sh +++ b/src/_binaries/DbImport/testsData/auto_default.local_fromDb_20.sh @@ -4,5 +4,5 @@ cat | grep -v '^table1$' | # table size 29MB grep -v '^table2$' | # table size 10MB -# grep -v '^table3$' | # table size 4MB +# 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 index 3d4cfd29..d0d27835 100644 --- a/src/_binaries/DbImport/testsData/dbImport.help.txt +++ b/src/_binaries/DbImport/testsData/dbImport.help.txt @@ -56,7 +56,8 @@ -vvv {single} trace level verbose mode (alias of --display-level TRACE) --env-file  {list} (optional) - Load the specified env file + Load the specified env file (deprecated, please use --bash-framework-config + option instead) --no-color {single} Produce monochrome output. alias of --theme noColor. --theme  {single} diff --git a/src/_binaries/DbImport/testsData/dbImportProfile.help.txt b/src/_binaries/DbImport/testsData/dbImportProfile.help.txt index fe9fcc5f..eed43c23 100644 --- a/src/_binaries/DbImport/testsData/dbImportProfile.help.txt +++ b/src/_binaries/DbImport/testsData/dbImportProfile.help.txt @@ -36,7 +36,8 @@ -vvv {single} trace level verbose mode (alias of --display-level TRACE) --env-file  {list} (optional) - Load the specified env file + Load the specified env file (deprecated, please use --bash-framework-config + option instead) --no-color {single} Produce monochrome output. alias of --theme noColor. --theme  {single} diff --git a/src/_binaries/DbImport/testsData/dbImportStream.help.txt b/src/_binaries/DbImport/testsData/dbImportStream.help.txt index 87d3059d..14739ed6 100644 --- a/src/_binaries/DbImport/testsData/dbImportStream.help.txt +++ b/src/_binaries/DbImport/testsData/dbImportStream.help.txt @@ -40,7 +40,8 @@ -vvv {single} trace level verbose mode (alias of --display-level TRACE) --env-file  {list} (optional) - Load the specified env file + Load the specified env file (deprecated, please use --bash-framework-config + option instead) --no-color {single} Produce monochrome output. alias of --theme noColor. --theme  {single} diff --git a/src/_binaries/DbQueryAllDatabases/testsData/dbQueryAllDatabases.help.txt b/src/_binaries/DbQueryAllDatabases/testsData/dbQueryAllDatabases.help.txt index 8c275026..8c57c76a 100644 --- a/src/_binaries/DbQueryAllDatabases/testsData/dbQueryAllDatabases.help.txt +++ b/src/_binaries/DbQueryAllDatabases/testsData/dbQueryAllDatabases.help.txt @@ -43,7 +43,8 @@ -vvv {single} trace level verbose mode (alias of --display-level TRACE) --env-file  {list} (optional) - Load the specified env file + Load the specified env file (deprecated, please use --bash-framework-config + option instead) --no-color {single} Produce monochrome output. alias of --theme noColor. --theme  {single} diff --git a/src/_binaries/DbScriptAllDatabases/testsData/dbScriptAllDatabases.help.txt b/src/_binaries/DbScriptAllDatabases/testsData/dbScriptAllDatabases.help.txt index 884015af..4e8b2e67 100644 --- a/src/_binaries/DbScriptAllDatabases/testsData/dbScriptAllDatabases.help.txt +++ b/src/_binaries/DbScriptAllDatabases/testsData/dbScriptAllDatabases.help.txt @@ -47,7 +47,8 @@ Allows to execute a script on each database of specified mysql server -vvv {single} trace level verbose mode (alias of --display-level TRACE) --env-file  {list} (optional) - Load the specified env file + Load the specified env file (deprecated, please use --bash-framework-config + option instead) --no-color {single} Produce monochrome output. alias of --theme noColor. --theme  {single} diff --git a/src/_binaries/Docker/testsData/cli.help.txt b/src/_binaries/Docker/testsData/cli.help.txt index f9297ec2..8fd39f97 100644 --- a/src/_binaries/Docker/testsData/cli.help.txt +++ b/src/_binaries/Docker/testsData/cli.help.txt @@ -39,7 +39,8 @@ -vvv {single} trace level verbose mode (alias of --display-level TRACE) --env-file  {list} (optional) - Load the specified env file + Load the specified env file (deprecated, please use --bash-framework-config + option instead) --no-color {single} Produce monochrome output. alias of --theme noColor. --theme  {single} diff --git a/src/_binaries/Git/testsData/gitIsAncestorOf.help.txt b/src/_binaries/Git/testsData/gitIsAncestorOf.help.txt index e4db7d17..65fbf775 100644 --- a/src/_binaries/Git/testsData/gitIsAncestorOf.help.txt +++ b/src/_binaries/Git/testsData/gitIsAncestorOf.help.txt @@ -24,7 +24,8 @@ -vvv {single} trace level verbose mode (alias of --display-level TRACE) --env-file  {list} (optional) - Load the specified env file + Load the specified env file (deprecated, please use --bash-framework-config + option instead) --no-color {single} Produce monochrome output. alias of --theme noColor. --theme  {single} diff --git a/src/_binaries/Git/testsData/gitRenameBranch.help.txt b/src/_binaries/Git/testsData/gitRenameBranch.help.txt index 2b19e695..88207f95 100644 --- a/src/_binaries/Git/testsData/gitRenameBranch.help.txt +++ b/src/_binaries/Git/testsData/gitRenameBranch.help.txt @@ -35,7 +35,8 @@ -vvv {single} trace level verbose mode (alias of --display-level TRACE) --env-file  {list} (optional) - Load the specified env file + Load the specified env file (deprecated, please use --bash-framework-config + option instead) --no-color {single} Produce monochrome output. alias of --theme noColor. --theme  {single} diff --git a/src/_binaries/Git/testsData/upgradeGithubRelease.help.txt b/src/_binaries/Git/testsData/upgradeGithubRelease.help.txt index 7cc9e555..e40168a9 100644 --- a/src/_binaries/Git/testsData/upgradeGithubRelease.help.txt +++ b/src/_binaries/Git/testsData/upgradeGithubRelease.help.txt @@ -49,7 +49,8 @@ -vvv {single} trace level verbose mode (alias of --display-level TRACE) --env-file  {list} (optional) - Load the specified env file + Load the specified env file (deprecated, please use --bash-framework-config + option instead) --no-color {single} Produce monochrome output. alias of --theme noColor. --theme  {single} diff --git a/src/_binaries/Utils/testsData/waitForIt.help.txt b/src/_binaries/Utils/testsData/waitForIt.help.txt index cc8da451..1ef282b6 100644 --- a/src/_binaries/Utils/testsData/waitForIt.help.txt +++ b/src/_binaries/Utils/testsData/waitForIt.help.txt @@ -39,7 +39,8 @@ -vvv {single} trace level verbose mode (alias of --display-level TRACE) --env-file  {list} (optional) - Load the specified env file + Load the specified env file (deprecated, please use --bash-framework-config + option instead) --no-color {single} Produce monochrome output. alias of --theme noColor. --theme  {single} diff --git a/src/_binaries/Utils/testsData/waitForMysql.help.txt b/src/_binaries/Utils/testsData/waitForMysql.help.txt index 4d739c8d..373497f3 100644 --- a/src/_binaries/Utils/testsData/waitForMysql.help.txt +++ b/src/_binaries/Utils/testsData/waitForMysql.help.txt @@ -33,7 +33,8 @@ -vvv {single} trace level verbose mode (alias of --display-level TRACE) --env-file  {list} (optional) - Load the specified env file + Load the specified env file (deprecated, please use --bash-framework-config + option instead) --no-color {single} Produce monochrome output. alias of --theme noColor. --theme  {single} diff --git a/src/_binaries/build/doc.sh b/src/_binaries/build/doc.sh index bcbc7a6a..14890978 100755 --- a/src/_binaries/build/doc.sh +++ b/src/_binaries/build/doc.sh @@ -13,7 +13,7 @@ declare -a RUN_CONTAINER_ARGV_FILTERED=() run() { if [[ "${IN_BASH_DOCKER:-}" != "You're in docker" ]]; then DOCKER_RUN_OPTIONS=$"-e ORIGINAL_DOC_DIR=${DOC_DIR}" \ - "${COMMAND_BIN_DIR}/runBuildContainer" "/bash/bin/doc" \ + "${FRAMEWORK_BIN_DIR}/runBuildContainer" "/bash/bin/doc" \ "${RUN_CONTAINER_ARGV_FILTERED[@]}" return $? fi diff --git a/src/_binaries/build/installRequirements.options.tpl b/src/_binaries/build/installRequirements.options.tpl index 02df895f..61532f84 100644 --- a/src/_binaries/build/installRequirements.options.tpl +++ b/src/_binaries/build/installRequirements.options.tpl @@ -1,6 +1,5 @@ % declare -a externalBinaries=( - bin/runBuildContainer bin/test ) declare versionNumber="1.0"