433 lines
17 KiB
Bash
Executable File
433 lines
17 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
#
|
||
# Copyright (c) 2025, Антон Аксенов
|
||
# This file is part of m3u.su project
|
||
# MIT License: https://git.axenov.dev/IPTV/web/src/branch/master/LICENSE
|
||
#
|
||
|
||
# shellcheck disable=SC2015
|
||
|
||
########################################################
|
||
# Служебные исходные переменные
|
||
########################################################
|
||
|
||
# имя контейнера
|
||
CONTAINER="iptv-web"
|
||
|
||
# команда для запуска
|
||
COMMAND="$1"; shift
|
||
|
||
# имена всех новых, скопированных, изменённых и перемещённых php-файлов относительно корня репозитория
|
||
FILES=($(git diff --name-only --diff-filter=ACMR HEAD 2>/dev/null | grep -e '.php$'))
|
||
|
||
# признак запуска гитом (хук)
|
||
IS_FROM_GIT="$(env | grep -c "GIT_EDITOR=:")"
|
||
|
||
# признак запуска изнутри контейнера
|
||
[[ -f /.dockerenv ]] && IS_FROM_CONTAINER=1 || IS_FROM_CONTAINER=0
|
||
|
||
# признак режима отладки
|
||
[[ $LINTER_DEBUG -gt 1 ]] && set -x
|
||
|
||
########################################################
|
||
# Ввод/вывод
|
||
########################################################
|
||
|
||
which tput > /dev/null 2>&1 && [ "$(tput -T"$TERM" colors)" -gt 8 ] && CAN_USE_COLORS=1 || CAN_USE_COLORS=0
|
||
LINTER_COLORS=${LINTER_COLORS:-$CAN_USE_COLORS}
|
||
[[ "$LINTER_COLORS" == 1 ]] && FRESET="$(tput sgr0)" || FRESET=''
|
||
[[ "$LINTER_COLORS" == 1 ]] && FBOLD="$(tput bold)" || FBOLD=''
|
||
[[ "$LINTER_COLORS" == 1 ]] && FDIM="$(tput dim)" || FDIM=''
|
||
[[ "$LINTER_COLORS" == 1 ]] && FRED="$(tput setaf 1)" || FRED=''
|
||
[[ "$LINTER_COLORS" == 1 ]] && FWHITE="$(tput setaf 7)" || FWHITE=''
|
||
[[ "$LINTER_COLORS" == 1 ]] && FGREEN="$(tput setaf 2)" || FGREEN=''
|
||
[[ "$LINTER_COLORS" == 1 ]] && FBRED="$(tput setab 1)" || FBRED=''
|
||
[[ "$LINTER_COLORS" == 1 ]] && FBYELLOW="$(tput setab 3)" || FBYELLOW=''
|
||
|
||
print() {
|
||
echo -e "$*${FRESET}"
|
||
}
|
||
|
||
debug() {
|
||
[[ "$LINTER_DEBUG" != 1 ]] && return
|
||
|
||
if [ "$2" ]; then
|
||
print "${FDIM}${FBOLD}${FRESET}${FDIM} ${FUNCNAME[1]:-?}():${BASH_LINENO:-?}\t$1 " >&2
|
||
else
|
||
print "${FDIM}${FBOLD}${FRESET}${FDIM} $*" >&2
|
||
fi
|
||
}
|
||
|
||
var_dump() {
|
||
debug "$1 = ${!1}"
|
||
}
|
||
|
||
title() {
|
||
print "${FBOLD}${FWHITE}$*${FRESET}"
|
||
}
|
||
|
||
success() {
|
||
print "${FBOLD}${FGREEN}$*${FRESET}"
|
||
}
|
||
|
||
warn() {
|
||
print "${FBOLD}${FBYELLOW}${FRED} Внимание! ${FRESET} $*${FRESET}"
|
||
}
|
||
|
||
error() {
|
||
print "${FBOLD}${FBRED}${FWHITE} Ошибка: ${FRESET}${FBOLD}${FRED} $*${FRESET}" >&2
|
||
}
|
||
|
||
########################################################
|
||
# Базовые хелперы
|
||
########################################################
|
||
|
||
# Проверяет существование функции с именем $1
|
||
is_function() {
|
||
declare -F "$1" > /dev/null
|
||
}
|
||
|
||
# Выполняет команду в контейнере
|
||
docker.exec() {
|
||
if [[ "$IS_FROM_GIT" == 1 ]]; then
|
||
cmd="docker exec -i $CONTAINER $*"
|
||
else
|
||
cmd="docker exec -it $CONTAINER $*"
|
||
fi
|
||
debug "Команда: $cmd"
|
||
$cmd
|
||
}
|
||
|
||
########################################################
|
||
# Хелперы для обработки команд
|
||
########################################################
|
||
|
||
# Выводит некоторую отладочную информацию
|
||
status() {
|
||
[[ "$LINTER_DEBUG" == 0 ]] && return
|
||
debug "* Внутри контейнера: ${IS_FROM_CONTAINER}"
|
||
debug "* Через git: ${IS_FROM_GIT}"
|
||
debug "* Изменённых файлов: ${#FILES[@]}"
|
||
}
|
||
|
||
# Запускает phpcs в контейнере
|
||
exec_phpcsniffer_pre() {
|
||
docker.exec vendor/bin/phpcs -p "$@"
|
||
}
|
||
|
||
# Запускает phpcbf в контейнере
|
||
exec_phpcsniffer_fix() {
|
||
docker.exec vendor/bin/phpcbf "$@"
|
||
}
|
||
|
||
# Запускает php-cs-fixer в контейнере
|
||
exec_phpcsfixer_pre() {
|
||
docker.exec vendor/bin/php-cs-fixer fix -vv --config=./.php-cs-fixer.php --using-cache=no --format=@auto --diff --dry-run "$@"
|
||
}
|
||
|
||
# Запускает php-cs-fixer в контейнере
|
||
exec_phpcsfixer_fix() {
|
||
docker.exec vendor/bin/php-cs-fixer fix -vv --config=./.php-cs-fixer.php --using-cache=no --format=@auto "$@"
|
||
}
|
||
|
||
# Запускает phpstan в контейнере
|
||
exec_phpstan() {
|
||
docker.exec vendor/bin/phpstan -v --memory-limit=1G analyse "$@"
|
||
}
|
||
|
||
# Запускает phpunit в контейнере
|
||
exec_phpunit() {
|
||
docker.exec vendor/bin/phpunit "$@"
|
||
}
|
||
|
||
########################################################
|
||
# Главные функции обработки команд
|
||
########################################################
|
||
|
||
# Устанавливает pre-commit git хук
|
||
install() {
|
||
status
|
||
[[ -d ./.git/hooks ]] || {
|
||
print "Не найден репозиторий '$(pwd)', пропускаю"
|
||
exit
|
||
}
|
||
cp -f "$0" ./.git/hooks/pre-commit && \
|
||
success "Pre-commit hook установлен" || \
|
||
error "Pre-commit хук НЕ установлен"
|
||
}
|
||
|
||
# Удаляет pre-commit git хук
|
||
remove() {
|
||
status
|
||
[[ -d ./.git/hooks ]] || {
|
||
print "Не найден репозиторий '$(pwd)', пропускаю"
|
||
exit
|
||
}
|
||
rm -f ./.git/hooks/pre-commit && \
|
||
success "Pre-commit hook удалён" || \
|
||
error "Pre-commit хук НЕ удалён"
|
||
}
|
||
|
||
# Запускает проверку код-стайла по всему проекту или только изменённым файлам
|
||
style() {
|
||
title "[php-cs-fixer] Запущена проверка код-стайла"
|
||
status
|
||
if [[ $# -gt 0 ]]; then
|
||
exec_phpcsfixer_pre "$@" || {
|
||
error "[php-cs-fixer] Проверка код-стайла завершена с ошибками!"
|
||
exit 1
|
||
}
|
||
else
|
||
exec_phpcsfixer_pre "${FILES[@]}" || {
|
||
error "[php-cs-fixer] Проверка код-стайла завершена с ошибками!"
|
||
exit 1
|
||
}
|
||
fi
|
||
success "[php-cs-fixer] Проверка код-стайла завершена успешно!"
|
||
}
|
||
|
||
# Запускает исправление код-стайла по всему проекту или только изменённым файлам
|
||
fix() {
|
||
title "[php-cs-fixer] Запущено исправление код-стайла"
|
||
status
|
||
if [[ $# -gt 0 ]]; then
|
||
exec_phpcsfixer_fix "$@" || {
|
||
error "[php-cs-fixer] Исправление код-стайла завершено с ошибками!"
|
||
exit 2
|
||
}
|
||
else
|
||
exec_phpcsfixer_fix "${FILES[@]}" || {
|
||
error "[php-cs-fixer] Исправление код-стайла завершено с ошибками!"
|
||
exit 2
|
||
}
|
||
fi
|
||
success "[php-cs-fixer] Исправление код-стайла завершено успешно!"
|
||
}
|
||
|
||
phpcs() {
|
||
title "[phpcs] Запущена проверка код-стайла"
|
||
status
|
||
if [[ $# -gt 0 ]]; then
|
||
exec_phpcsniffer_pre "$@" || {
|
||
error "[phpcs] Проверка код-стайла завершена с ошибками!"
|
||
exit 3
|
||
}
|
||
else
|
||
exec_phpcsniffer_pre "${FILES[@]}" || {
|
||
error "[phpcs] Проверка код-стайла завершена с ошибками!"
|
||
exit 3
|
||
}
|
||
fi
|
||
success "[phpcs] Проверка код-стайла завершена успешно!"
|
||
}
|
||
|
||
phpcbf() {
|
||
title "[phpcs] Запущено исправление код-стайла"
|
||
status
|
||
if [[ $# -gt 0 ]]; then
|
||
exec_phpcsniffer_fix "$@" || {
|
||
error "[phpcs] Исправление код-стайла завершено с ошибками!"
|
||
exit 4
|
||
}
|
||
else
|
||
exec_phpcsniffer_fix "${FILES[@]}"|| {
|
||
error "[phpcs] Исправление код-стайла завершено с ошибками!"
|
||
exit 4
|
||
}
|
||
fi
|
||
success "[phpcs] Исправление код-стайла завершено успешно!"
|
||
}
|
||
|
||
# Запускает статический анализ по всему проекту или только изменённым файлам
|
||
stan() {
|
||
title "[phpstan] Запуск статического анализа"
|
||
status
|
||
if [[ $# -gt 0 ]]; then
|
||
exec_phpstan "$@" || {
|
||
error "[phpstan] Статический анализ завершён с ошибками!"
|
||
exit 5
|
||
}
|
||
else
|
||
exec_phpstan "${FILES[@]}" || {
|
||
error "[phpstan] Статический анализ завершён с ошибками!"
|
||
exit 5
|
||
}
|
||
fi
|
||
success "[phpstan] Статический анализ завершён успешно!"
|
||
}
|
||
|
||
# Запускает выполнение тестов
|
||
tests() {
|
||
title "[phpunit] Запуск тестирования"
|
||
exec_phpunit "$@" || {
|
||
error "[phpunit] Тестирование завершено с ошибками!"
|
||
exit 6
|
||
}
|
||
success "[phpunit] Тестирование завершено успешно!"
|
||
}
|
||
|
||
lint() {
|
||
style
|
||
phpcs
|
||
stan
|
||
}
|
||
|
||
########################################################
|
||
# Команды справки
|
||
########################################################
|
||
|
||
help() {
|
||
print "${FBOLD}PHP-линтер"
|
||
is_function "help.$1" && help."$1" && exit
|
||
print "Использование:"
|
||
print " ./linter КОМАНДА [АРГУМЕНТЫ]"
|
||
print
|
||
print "Доступные КОМАНДЫ:"
|
||
print " h|help - вывести это сообщение"
|
||
print " i|install - установить как pre-commit git хук"
|
||
print " s|style - запустить проверку код-стайла (php-cs-fixer)"
|
||
print " f|fix - запустить исправление код-стайла (php-cs-fixer)"
|
||
print " p|phpcs - запустить проверку код-стайла (phpcs)"
|
||
print " c|phpcbf - запустить исправление код-стайла (phpcbf)"
|
||
print " a|stan - запустить статический анализ (phpstan)"
|
||
print " l|lint - запустить style + phpcs + stan"
|
||
print " t|tests - протестировать (phpunit)"
|
||
print
|
||
print "КОМАНДЫ 's', 'f', 'p', 'c' и 'a' могут принимать собственные АРГУМЕНТЫ php-cs-fixer, phpcs, phpcbf и phpstan соответственно."
|
||
print
|
||
print "Переменные окружения:"
|
||
print " LINTER_COLORS - принудительно включить (1) или выключить (0, по умолчанию) форматированный вывод"
|
||
print " LINTER_DEBUG - включить простую отладку (1), построчную (2) или выключить (0, по умолчанию)"
|
||
}
|
||
|
||
help.help() {
|
||
print "КОМАНДА: help"
|
||
print "Отображает справку по скрипту и его командам."
|
||
print
|
||
print "Использование:"
|
||
print " ./linter help [КОМАНДА]"
|
||
print
|
||
print "Если КОМАНДА не указана, будет отображена справка по скрипту со списком допустимых КОМАНД."
|
||
}
|
||
|
||
help.install() {
|
||
print "КОМАНДА: install"
|
||
print "Устанавливает linter в качестве pre-commit git-хука."
|
||
print
|
||
print "Использование:"
|
||
print " ./linter install"
|
||
print
|
||
print "Если в директории проекта нет репозитория, то установка будет пропущена без ошибки."
|
||
print "Перед коммитом будут выполнены команды lint и tests."
|
||
}
|
||
|
||
help.style() {
|
||
print "КОМАНДА: style"
|
||
print "Запускает php-cs-fixer для проверки код-стайла."
|
||
print "Поддерживает передачу собственных аргументов."
|
||
print
|
||
print "Использование:"
|
||
print " ./linter style [АРГУМЕНТЫ]"
|
||
print
|
||
print "Допустимые АРГУМЕНТЫ: https://cs.symfony.com/doc/usage.html"
|
||
print "Если АРГУМЕНТЫ не указаны, будут анализированы только изменённые и новые php-файлы."
|
||
print "Если рабочее дерево git чистое, то инструмент отработает согласно своего конфига."
|
||
}
|
||
|
||
help.fix() {
|
||
print "КОМАНДА: fix"
|
||
print "Запускает php-cs-fixer для исправления код-стайла."
|
||
print "Поддерживает передачу собственных аргументов."
|
||
print
|
||
print "Использование:"
|
||
print " ./linter fix [АРГУМЕНТЫ]"
|
||
print
|
||
print "Допустимые АРГУМЕНТЫ: https://cs.symfony.com/doc/usage.html"
|
||
print "Если АРГУМЕНТЫ не указаны, будут анализированы только изменённые и новые php-файлы."
|
||
print "Если рабочее дерево git чистое, то инструмент отработает согласно своего конфига."
|
||
}
|
||
|
||
help.phpcs() {
|
||
print "КОМАНДА: phpcs"
|
||
print "Запускает phpcs для проверки код-стайла."
|
||
print "Поддерживает передачу собственных аргументов."
|
||
print
|
||
print "Использование:"
|
||
print " ./linter phpcs [АРГУМЕНТЫ]"
|
||
print
|
||
print "Допустимые АРГУМЕНТЫ: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Usage"
|
||
print "Если АРГУМЕНТЫ не указаны, будут анализированы только изменённые и новые php-файлы."
|
||
print "Если рабочее дерево git чистое, то инструмент отработает согласно своего конфига."
|
||
}
|
||
|
||
help.phpcbf() {
|
||
print "КОМАНДА: phpcbf"
|
||
print "Запускает phpcbf для исправления код-стайла."
|
||
print "Поддерживает передачу собственных аргументов."
|
||
print
|
||
print "Использование:"
|
||
print " ./linter phpcbf [АРГУМЕНТЫ]"
|
||
print
|
||
print "Допустимые АРГУМЕНТЫ: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Fixing-Errors-Automatically#using-the-php-code-beautifier-and-fixer"
|
||
print "Если АРГУМЕНТЫ не указаны, будут анализированы только изменённые и новые php-файлы."
|
||
print "Если рабочее дерево git чистое, то инструмент отработает согласно своего конфига."
|
||
}
|
||
|
||
help.stan() {
|
||
print "КОМАНДА: stan"
|
||
print "Запускает phpstan для статического анализа."
|
||
print
|
||
print "Использование:"
|
||
print " ./linter stan [АРГУМЕНТЫ]"
|
||
print
|
||
print "Допустимые АРГУМЕНТЫ: https://phpstan.org/user-guide/command-line-usage"
|
||
print "Если АРГУМЕНТЫ не указаны, будут анализированы только изменённые и новые php-файлы."
|
||
print "Если рабочее дерево git чистое, то инструмент отработает согласно своего конфига."
|
||
}
|
||
|
||
help.lint() {
|
||
print "КОМАНДА: lint"
|
||
print "Последовательно запускает команды 'style', 'phpcs' и 'stan'."
|
||
print "Не поддерживает передачу аргументов."
|
||
print
|
||
print "Использование:"
|
||
print " ./linter lint"
|
||
print
|
||
print "Для дополнительной информации см. './linter help' для соответствующей команды."
|
||
}
|
||
|
||
help.tests() {
|
||
print "КОМАНДА: tests"
|
||
print "Последовательно запускает phpunit для статического анализа."
|
||
print
|
||
print "Использование:"
|
||
print " ./linter tests [АРГУМЕНТЫ]"
|
||
print
|
||
print "Допустимые АРГУМЕНТЫ: https://docs.phpunit.de/en/10.5/textui.html#command-line-options"
|
||
}
|
||
|
||
########################################################
|
||
# Точка входа
|
||
########################################################
|
||
|
||
if [[ "$IS_FROM_GIT" -gt 0 ]]; then
|
||
status && lint && tests
|
||
success "Коммит разрешён!"
|
||
exit 0
|
||
fi
|
||
|
||
case "$COMMAND" in
|
||
h|help ) help "$1" ;;
|
||
i|install ) install ;;
|
||
r|remove ) remove ;;
|
||
s|style ) style "$@" ;;
|
||
f|fix ) fix "$@" ;;
|
||
p|phpcs ) phpcs "$@" ;;
|
||
c|phpcbf ) phpcbf "$@" ;;
|
||
a|stan ) stan "$@" ;;
|
||
l|lint ) lint ;;
|
||
t|tests ) tests "$@" ;;
|
||
* ) warn "неизвестная команда, вызываю help"; help ;;
|
||
esac
|
||
|