Files
web/linter
2026-01-03 01:12:18 +08:00

433 lines
17 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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