Skip to content

Commit

Permalink
Simplified Array::wrap2 algorithm and performance improvement
Browse files Browse the repository at this point in the history
performance improvement using:
- echo instead of string concatenation
- string substitution instead of calling sed on each element
- echo -e removed the need to do a loop on each character
  to parse ansi code and the need of Filters::removeAnsiCodes
  • Loading branch information
fchastanet committed Jan 13, 2024
1 parent 30f45db commit bb58e72
Show file tree
Hide file tree
Showing 33 changed files with 1,409 additions and 1,616 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/lint-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ on: # yamllint disable-line rule:truthy
jobs:
build:
runs-on: ubuntu-22.04
permissions:
# needed by ouzi-dev/commit-status-updater@v2
statuses: write
strategy:
fail-fast: true
matrix:
Expand Down Expand Up @@ -111,7 +114,7 @@ jobs:
- name: Archive results
if: matrix.runPrecommitTests && always()
continue-on-error: true
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: linter-reports
path: |
Expand Down Expand Up @@ -151,7 +154,7 @@ jobs:
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Test Results ${{matrix.vendor}} ${{matrix.bashTarVersion}}
path: logs/report.xml
Expand Down
5 changes: 4 additions & 1 deletion .markdown-link-check.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
"pattern": "^http://localhost:3000/"
},
{
"pattern": "^https://www.cyberciti.biz/.*$"
"pattern": "^https://www.cyberciti.biz/"
},
{
"pattern": "^https://tinyurl.com/"
},
{
"pattern": "^#.*"
Expand Down
3 changes: 2 additions & 1 deletion .mega-linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ EDITORCONFIG_EDITORCONFIG_CHECKER_FILTER_REGEX_EXCLUDE: |
bin/bash-tpl|
^doc/guides/Options/generate.*\.md$|
^pages/Commands.md
^.*-megalinter_file_names_cspell.txt
^.*-megalinter_file_names_cspell.txt|
testsData/.*\.result$
)
GIT_GIT_DIFF_PRE_COMMANDS:
Expand Down
9 changes: 7 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ repos:
exclude: |
(?x)(
^doc/images/.*\.svg$|
^doc/guides/Options/generate.*.md$
^doc/guides/Options/generate.*.md$|
testsData/.*\.result$
)
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
Expand Down Expand Up @@ -49,7 +50,11 @@ repos:
rev: v4.0.0-alpha.8
hooks:
- id: prettier
exclude: ^doc/guides/Options/generate.*.md$
exclude: |
(?x)(
^doc/guides/Options/generate.*.md$|
^\.vscode/.*\.code-snippets$
)
- repo: https://github.com/fchastanet/bash-tools-framework
rev: 2.1.0
Expand Down
2 changes: 1 addition & 1 deletion .vscode/bats.code-snippets
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
"[[ \"\\${status}\" = \"0\" ]]",
"run cat \"\\${BATS_TEST_TMPDIR}/result\""
],
"description": "bats get status and output"
"description": "bats get status and output",
}
}
2 changes: 1 addition & 1 deletion .vscode/shdoc.code-snippets
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@
"# @require Git::requireGitCommand",
"# @feature Retry::default, sudo"
],
"description": "bash function generic shdoc"
"description": "bash function generic shdoc",
}
}
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,10 +389,18 @@ bin/compile src/_binaries/testsData/bin/embed.sh --template-dir src --bin-dir bi
echo $? # prints 127

# try to get more logs
KEEP_TEMP_FILES=1 BASH_FRAMEWORK_DISPLAY_LEVEL=4 bin/compile src/_binaries/testsData/bin/embed.sh --template-dir src --bin-dir bin --root-dir $PWD --src-dir src/_binaries/testsData/src
KEEP_TEMP_FILES=1 BASH_FRAMEWORK_DISPLAY_LEVEL=4 bin/compile \
src/_binaries/testsData/bin/embed.sh \
--template-dir src \
--bin-dir bin \
--root-dir "${PWD}" \
--src-dir src/_binaries/testsData/src

# try to use strace
docker run --rm -it -w /bash -v "$(pwd):/bash" --entrypoint="" build:bash-tools-alpine-4.4-user bash
docker run --rm -it \
-w /bash -v "$(pwd):/bash" \
--entrypoint="" \
build:bash-tools-alpine-4.4-user bash
apk update
apk add strace
```
Expand Down
194 changes: 84 additions & 110 deletions bin/awkLint
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,12 @@ trap cleanOnExit EXIT HUP QUIT ABRT TERM
# @description concat each element of an array with a separator
# but wrapping text when line length is more than provided argument
# The algorithm will try not to cut the array element if it can.
# if an arg can be placed on current line it will be,
# - if an arg can be placed on current line it will be,
# otherwise current line is printed and arg is added to the new
# current line
# empty arg is interpreted as a new line.
# - Empty arg is interpreted as a new line.
# - Add \r to arg in order to force break line and avoid following
# arg to be concatenated with current arg.
#
# @arg $1 glue:String
# @arg $2 maxLineLength:int
Expand All @@ -126,117 +128,102 @@ Array::wrap2() {
return 0
fi

eatNextSpaces() {
if [[ "${currentChar}" != [[:space:]] ]]; then
((i--)) || true
return 0
fi
for (( ; i < textLength; i++)); do
if [[ "${text:${i}:1}" != [[:space:]] ]]; then
((i--)) || true
break
fi
done
}
printCurrentLine() {
echo -e "${currentLine}" | sed -E -e 's/[[:blank:]]*$//'
if ((isNewline == 0)) || ((previousLineEmpty == 1)); then
echo
fi
((isNewline = 1))
currentLine="${indentStr}"
echo -en "${indentStr}"
((currentLineLength = indentNextLine)) || true
}
nextLine() {
printCurrentLine
eatNextSpaces
}
local currentLine currentChar ansiCode
local -i isAnsiCode currentLineLength=0 isNewline=1 textLength=0
local text=""
local arg=""
while (($# > 0)); do
arg="$1"
shift || true
if ((${#arg} == 0)) || [[ "${arg}" = $'\n' ]]; then
if ((isNewline == 0)); then
# isNewline = 0 means currentLine is not empty
printCurrentLine
fi
echo
continue
fi
local textFirstLine="${arg%%$'\n'*}"
text="$(echo "${arg}" | sed -E '1d')"
((textLength = ${#text})) || true
local textFirstLineNoAnsi
textFirstLineNoAnsi="$(echo "${textFirstLine}" | Filters::removeAnsiCodes)"
local -i textFirstLineNoAnsiLength=0
((textFirstLineNoAnsiLength = ${#textFirstLineNoAnsi})) || true

if ((isNewline == 0)); then
glueLength="${#glue}"
appendToCurrentLine() {
local text="$1"
local -i length=$2
((currentLineLength += length)) || true
((isNewline = 0)) || true
if [[ "${text: -1}" = $'\r' ]]; then
text="${text:0:-1}"
echo -en "${text%%+([[:blank:]])}"
printCurrentLine
else
glueLength="0"
echo -en "${text%%+([[:blank:]])}"
fi
if ((currentLineLength + textFirstLineNoAnsiLength + glueLength > maxLineLength)); then
if ((isNewline == 0)); then
# isNewline = 0 means currentLine is not empty
}

(
local currentLine
local -i currentLineLength=0 isNewline=1 argLength=0
local -a additionalLines
local -i previousLineEmpty=0
local arg=""

while (($# > 0)); do
arg="$1"
shift || true

# replace tab by 2 spaces
arg="${arg//$'\t'/ }"
# remove trailing spaces
arg="${arg%[[:blank:]]}"
if [[ "${arg}" = $'\n' || -z "${arg}" ]]; then
printCurrentLine
((previousLineEmpty = 1))
continue
else
if ((previousLineEmpty == 1)); then
printCurrentLine
fi
((previousLineEmpty = 0)) || true
fi
# restore current arg without considering first line
text="${arg}"
((textLength = ${#text})) || true
else
if ((isNewline == 0)); then
# isNewline = 0 means currentLine is not empty
currentLine+="${glue}"
((currentLineLength += glueLength)) || true
# convert eol to args
mapfile -t additionalLines <<<"${arg}"
if ((${#additionalLines[@]} > 1)); then
set -- "${additionalLines[@]}" "$@"
continue
fi
currentLine+="${textFirstLine}"
isNewline="0"
((currentLineLength += ${#textFirstLine})) || true
fi

for ((i = 0; i < textLength; i++)); do
currentChar="${text:${i}:1}"

if [[ "${currentChar}" = $'\r' ]]; then
# ignore
true
elif [[ "${currentChar}" = "\x1b" ]]; then
isAnsiCode=1
ansiCode+="${currentChar}"
elif ((isAnsiCode == 1)); then
ansiCode+="${currentChar}"
if [[ "${currentChar}" =~ [mGKHF] ]]; then
isAnsiCode=0
echo -e "${ansiCode}"
elif [[ "${currentChar}" = $'\n' ]]; then
# invalid ansi code, ignore it
isAnsiCode=0
ansiCode=""
((argLength = ${#arg})) || true

# empty arg
if ((argLength == 0)); then
if ((isNewline == 0)); then
# isNewline = 0 means currentLine is not empty
printCurrentLine
fi
continue
fi

if ((isNewline == 0)); then
glueLength="${#glue}"
else
# non ansi code
if [[ "${currentChar}" = $'\n' ]] || ((currentLineLength == maxLineLength)); then
nextLine
elif [[ "${currentChar}" = "\t" ]]; then
if ((currentLineLength + 2 <= maxLineLength)); then
currentLine+=" "
((isNewline = 0)) || true
((currentLineLength = currentLineLength + 2))
else
nextLine
fi
glueLength="0"
fi
if ((currentLineLength + argLength + glueLength > maxLineLength)); then
if ((argLength + glueLength > maxLineLength)); then
# arg is too long to even fit on one line
# we have to split the arg on current and next line
local -i remainingLineLength
((remainingLineLength = maxLineLength - currentLineLength - glueLength))
appendToCurrentLine "${glue:0:${glueLength}}${arg:0:${remainingLineLength}}" "$((glueLength + remainingLineLength))"
printCurrentLine
arg="${arg:${remainingLineLength}}"
# remove leading spaces
arg="${arg##[[:blank:]]}"

set -- "${arg}" "$@"
else
currentLine+="${currentChar}"
((isNewline = 0)) || true
((++currentLineLength))
# the arg can fit on next line
printCurrentLine
appendToCurrentLine "${arg}" "${argLength}"
fi
else
appendToCurrentLine "${glue:0:${glueLength}}${arg}" "$((glueLength + argLength))"
fi
done
done
if [[ "${currentLine}" != "" ]] && [[ ! "${currentLine}" =~ ^[\ \t]+$ ]]; then
printCurrentLine
fi
if [[ "${currentLine}" != "" ]] && [[ ! "${currentLine}" =~ ^[\ \t]+$ ]]; then
printCurrentLine
fi
) | sed -E -e 's/[[:blank:]]+$//'
}

# @description ensure env files are loaded
Expand Down Expand Up @@ -523,19 +510,6 @@ Env::pathPrepend() {
done
}

# @description remove ansi codes from input or files given as argument
# @arg $@ files:String[] the files to filter
# @exitcode * if one of the filter command fails
# @stdin you can use stdin as alternative to files argument
# @stdout the filtered content
# @see https://en.wikipedia.org/wiki/ANSI_escape_code
# shellcheck disable=SC2120
Filters::removeAnsiCodes() {
# cspell:disable
sed -E 's/\x1b\[[0-9;]*[mGKHF]//g' "$@"
# cspell:enable
}

# @description log message to file
# @arg $1 message:String the message to display
Log::logDebug() {
Expand Down
Loading

0 comments on commit bb58e72

Please sign in to comment.