2021-09-15 14:58:09 +08:00
|
|
|
#!/bin/bash
|
|
|
|
|
2021-09-15 17:00:50 +08:00
|
|
|
onoe() {
|
|
|
|
echo "$*" >&2
|
|
|
|
}
|
|
|
|
|
|
|
|
odie() {
|
|
|
|
onoe "$@"
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
|
2021-09-15 14:58:09 +08:00
|
|
|
# HOMEBREW_PREFIX is set by extend/ENV/super.rb
|
|
|
|
# shellcheck disable=SC2154
|
2021-09-15 17:00:50 +08:00
|
|
|
if [[ -z "${HOMEBREW_PREFIX}" ]]
|
2021-09-15 14:58:09 +08:00
|
|
|
then
|
2021-09-15 17:00:50 +08:00
|
|
|
odie "${0##*/}: This program is internal and must be run via brew."
|
2021-09-15 14:58:09 +08:00
|
|
|
fi
|
|
|
|
|
|
|
|
# HOMEBREW_PREFIX is set by extend/ENV/super.rb
|
|
|
|
# shellcheck disable=SC2154
|
|
|
|
SHFMT="${HOMEBREW_PREFIX}/opt/shfmt/bin/shfmt"
|
|
|
|
|
|
|
|
if [[ ! -x "${SHFMT}" ]]
|
|
|
|
then
|
2021-09-15 17:00:50 +08:00
|
|
|
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\`."
|
2021-09-15 14:58:09 +08:00
|
|
|
fi
|
|
|
|
|
|
|
|
SHFMT_ARGS=()
|
|
|
|
INPLACE=''
|
|
|
|
while [[ $# -gt 0 ]]
|
|
|
|
do
|
|
|
|
arg="$1"
|
|
|
|
if [[ "${arg}" == "--" ]]
|
|
|
|
then
|
|
|
|
shift
|
|
|
|
break
|
|
|
|
fi
|
|
|
|
if [[ "${arg}" == "-w" ]]
|
|
|
|
then
|
|
|
|
shift
|
|
|
|
INPLACE=1
|
|
|
|
continue
|
|
|
|
fi
|
|
|
|
SHFMT_ARGS+=("${arg}")
|
|
|
|
shift
|
|
|
|
done
|
|
|
|
|
|
|
|
FILES=()
|
|
|
|
for file in "$@"
|
|
|
|
do
|
|
|
|
if [[ -f "${file}" ]]
|
|
|
|
then
|
|
|
|
FILES+=("${file}")
|
|
|
|
else
|
|
|
|
echo "${0##*/}: File \"${file}\" does not exist." >&2
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
|
|
|
|
if [[ "${#FILES[@]}" == 0 ]]
|
|
|
|
then
|
|
|
|
exit
|
|
|
|
fi
|
|
|
|
|
2021-09-15 17:00:50 +08:00
|
|
|
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
|