From a839d7e5ffd73097524e3d92202e4b33c37d9965 Mon Sep 17 00:00:00 2001 From: XuehaiPan Date: Wed, 15 Sep 2021 17:00:50 +0800 Subject: [PATCH] utils/shfmt.sh: add function prototypes --- Library/Homebrew/utils/shfmt.sh | 196 ++++++++++++++++++++++++++++++-- 1 file changed, 189 insertions(+), 7 deletions(-) diff --git a/Library/Homebrew/utils/shfmt.sh b/Library/Homebrew/utils/shfmt.sh index 96891970eb..1ffecf5dcf 100755 --- a/Library/Homebrew/utils/shfmt.sh +++ b/Library/Homebrew/utils/shfmt.sh @@ -1,12 +1,19 @@ #!/bin/bash -# HOMEBREW_LIBRARY is set by bin/brew +onoe() { + echo "$*" >&2 +} + +odie() { + onoe "$@" + exit 1 +} + # HOMEBREW_PREFIX is set by extend/ENV/super.rb # shellcheck disable=SC2154 -if [[ -z "${HOMEBREW_LIBRARY}" || -z "${HOMEBREW_PREFIX}" ]] +if [[ -z "${HOMEBREW_PREFIX}" ]] then - echo "${0##*/}: This program is internal and must be run via brew." >&2 - exit 1 + odie "${0##*/}: This program is internal and must be run via brew." fi # HOMEBREW_PREFIX is set by extend/ENV/super.rb @@ -15,8 +22,12 @@ SHFMT="${HOMEBREW_PREFIX}/opt/shfmt/bin/shfmt" if [[ ! -x "${SHFMT}" ]] then - echo "${0##*/}: Please install shfmt by running \`brew install shfmt\`." >&2 - exit 1 + odie "${0##*/}: Please install shfmt by running \`brew install shfmt\`." +fi + +if [[ ! -x "$(command -v diff)" ]] +then + odie "${0##*/}: Please install diff by running \`brew install diffutils\`." fi SHFMT_ARGS=() @@ -56,4 +67,175 @@ then exit fi -"${SHFMT}" "${SHFMT_ARGS[@]}" "${FILES[@]}" +check_read_and_write() { + [[ -f "$1" && -w "$1" ]] +} + +### +### Custom shell script styling +### + +# Check pattern: +# '^\t+' +# +# Replace tabs with 2 spaces instead +# +no_tabs() { + local file="$1" + + # TODO: use bash built-in regex match syntax instead + if grep -qE '^\t+' "${file}" + then + # TODO: add line number + onoe "Indent by tab detected." + return 1 + fi +} + +# Check pattern: +# for var in ... \ +# ...; do +# +# Use the followings instead (keep for statements only one line): +# ARRAY=( +# ... +# ... +# ) +# for var in "${ARRAY[@]}" +# do +# +no_multiline_for_statements() { + local file="$1" + check_read_and_write "${file}" || return 1 + + # TODO: use bash built-in regex match syntax instead + if grep -qE '^\s*for .*\\\(#.*\)\?$' "${file}" + then + # TODO: add line number + onoe "Multi-line for statement detected." + return 1 + fi +} + +# Check pattern: +# IFS=$'\n' +# +# Use the followings instead: +# while IFS='' read -r line +# do +# ... +# done < <(command) +# +no_IFS_newline() { + local file="$1" + check_read_and_write "${file}" || return 1 + + + # TODO: use bash built-in regex match syntax instead + if grep -qE "^[^#]*IFS=\\\$'\\\\n'" "${file}" + then + # TODO: add line number + onoe "Pattern \`IFS=\$'\\\\n'\` detected." + return 1 + fi +} + +# TODO: Wrap `then` to a separated line +# before: after: +# if [[ ... ]]; then if [[ ... ]] +# then +# +# before: after: +# if [[ ... ]] || if [[ ... ]] || +# [[ ... ]]; then [[ ... ]] +# then +# +wrap_then() { + local file="$1" + check_read_and_write "${file}" || return 1 + + true +} + +# Probably merge into the above function +# TODO: Wrap `do` to a separated line +# before: after: +# for var in ...; do for var in ...do +# do +# +wrap_do() { + local file="$1" + check_read_and_write "${file}" || return 1 + + true +} + +# TODO: Align multiline if condition (indent with 3 spaces or 6 spaces (start with "-")) +# before: after: +# if [[ ... ]] || if [[ ... ]] || +# [[ ... ]] [[ ... ]] +# then then +# +# before: after: +# if [[ -n ... || \ if [[ -n ... || \ +# -n ... ]] -n ... ]] +# then then +# +align_multiline_if_condition() { + local file="$1" + check_read_and_write "${file}" || return 1 + + true +} + +# TODO: It's hard to align multiline switch cases +align_multiline_switch_cases() { + true +} + +format() { + local file="$1" + # shellcheck disable=SC2155 + local tempfile="$(dirname "${file}")/.${file##*/}.temp" + local retcode=0 + + cp -af "${file}" "${tempfile}" + + # Format with `shfmt` first + "${SHFMT}" -w "${SHFMT_ARGS[@]}" "${tempfile}" + + # Fail fast when forbidden patterns detected + if ! no_tabs "${tempfile}" || + ! no_multiline_for_statements "${tempfile}" || + ! no_IFS_newline "${tempfile}" + then + rm -f "${tempfile}" 2>/dev/null + return 1 + fi + + # Tweak it with custom shell script styles + wrap_then "${tempfile}" + wrap_do "${tempfile}" + align_multiline_if_condition "${tempfile}" + + if ! diff -q "${file}" "${tempfile}" + then + # Show differences + diff -d -C 1 --color=auto "${file}" "${tempfile}" + if [[ -n "${INPLACE}" ]]; then + cp -af "${tempfile}" "${file}" + fi + retcode=1 + else + # File is identical between code formations (good styling) + retcode=0 + fi + rm -f "${tempfile}" 2>/dev/null + return "${retcode}" +} + +for file in "${FILES[@]}" +do + # TODO: catch return values + format "${file}" +done