#!/usr/bin/env bash # docker-finance | modern accounting for the power-user # # Copyright (C) 2021-2025 Aaron Fiore (Founder, Evergreen Crypto LLC) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . [ -z "$DOCKER_FINANCE_CLIENT_REPO" ] && exit 1 # # "Libraries" # source "${DOCKER_FINANCE_CLIENT_REPO}/client/src/docker/lib/internal/lib_docker.bash" || exit 1 source "${DOCKER_FINANCE_CLIENT_REPO}/container/src/finance/lib/internal/lib_utils.bash" || exit 1 # # Implementation # function lib_linter::__parse_args() { [ -z "$global_usage" ] && lib_utils::die_fatal [ -z "$global_arg_delim_2" ] && lib_utils::die_fatal [ -z "$global_arg_delim_3" ] && lib_utils::die_fatal local -r _usage=" \e[32mDescription:\e[0m Lint docker-finance source files \e[32mUsage:\e[0m $ $global_usage type${global_arg_delim_2} [file${global_arg_delim_2}] \e[32mArguments:\e[0m File type: type${global_arg_delim_2} File path: file${global_arg_delim_2} \e[32mNotes:\e[0m Cannot use multiple types with file argument present \e[32mExamples:\e[0m \e[37;2m# Lint only PHP files\e[0m $ $global_usage type${global_arg_delim_2}php \e[37;2m# Lint *all* development files\e[0m $ $global_usage type${global_arg_delim_2}bash${global_arg_delim_3}php${global_arg_delim_3}c++ \e[37;2m# Lint given C++ files\e[0m $ $global_usage type${global_arg_delim_2}c++ file${global_arg_delim_2}path/file_1.hh${global_arg_delim_3}path/file_2.hh " # # Ensure supported arguments # [ $# -eq 0 ] && lib_utils::die_usage "$_usage" for _arg in "$@"; do [[ ! "$_arg" =~ ^type[s]?${global_arg_delim_2} ]] \ && [[ ! "$_arg" =~ ^file[s]?${global_arg_delim_2} ]] \ && lib_utils::die_usage "$_usage" done # # Parse arguments before testing # # Parse key for value for _arg in "$@"; do local _key="${_arg%${global_arg_delim_2}*}" local _len="$((${#_key} + 1))" if [[ "$_key" =~ ^type[s]?$ ]]; then local _arg_type="${_arg:${_len}}" [ -z "$_arg_type" ] && lib_utils::die_usage "$_usage" fi if [[ "$_key" =~ ^file[s]?$ ]]; then local _arg_file="${_arg:${_len}}" [ -z "$_arg_file" ] && lib_utils::die_usage "$_usage" fi done # # Test argument values, set globals # IFS="$global_arg_delim_3" # Arg: type if [ ! -z "$_arg_type" ]; then read -ra _read <<<"$_arg_type" for _type in "${_read[@]}"; do if [[ ! "$_type" =~ ^bash$|^shell$|^c\+\+$|^cpp$|^php$ ]]; then lib_utils::die_usage "$_usage" fi done declare -gr global_arg_type=("${_read[@]}") fi # Arg: file if [ ! -z "$_arg_file" ]; then read -ra _read <<<"$_arg_file" if [ "${#global_arg_type[@]}" -gt 1 ]; then # Multiple types not supported if file is given lib_utils::die_usage "$_usage" fi declare -gr global_arg_file=("${_read[@]}") fi } function lib_linter::__lint_bash() { local -r _path=("$@") local -r _exts=("bash" "bash.in") local -r _shfmt="shfmt -ln bash -i 2 -ci -bn -fn -w" local -r _shellcheck="shellcheck --color=always --norc --enable=all --shell=bash --severity=warning" if [ -z "${_path[*]}" ]; then # Do all for _ext in "${_exts[@]}"; do lib_docker::__docker_compose exec -it docker-finance /bin/bash -i -c \ "find ${DOCKER_FINANCE_CLIENT_REPO}/ ${DOCKER_FINANCE_CLIENT_PLUGINS}/ -type f -name \*.${_ext} \ | while read _file do echo Linting \'\${_file}\' $_shfmt \$_file \$_file && $_shellcheck \${_file} done" done else # Do only given file(s) for _p in "${_path[@]}"; do local _file _file="$(dirs +1)/${_p}" lib_utils::print_normal "Linting '${_file}'" lib_docker::__docker_compose exec -it docker-finance /bin/bash -i -c \ "$_shfmt $_file $_file && $_shellcheck $_file" done fi } function lib_linter::__lint_cpp() { local -r _path=("$@") local -r _exts=("hh" "cc" "cpp" "C") local _clang_file _clang_file="$(find ${DOCKER_FINANCE_CLIENT_REPO} -name .clang-format)" [ -z "$_clang_file" ] && lib_utils::die_fatal ".clang-format not found" declare -r _clang_file local -r _clang_format="clang-format -i --style=file:${_clang_file}" local -r _cpplint="cpplint --root=${DOCKER_FINANCE_CLIENT_REPO} --filter=-whitespace/braces,-whitespace/newline,-whitespace/line_length,-build/c++11 --headers=hh --extensions=hh,cc,cpp,C" local -r _cppcheck="cppcheck --enable=warning,style,performance,portability --inline-suppr --std=c++17" if [ -z "${_path[*]}" ]; then # Do all for _ext in "${_exts[@]}"; do lib_docker::__docker_compose exec -it docker-finance /bin/bash -i -c \ "find ${DOCKER_FINANCE_CLIENT_REPO}/ ${DOCKER_FINANCE_CLIENT_PLUGINS}/ -type f -name \*.${_ext} \ | while read _file do echo Linting \'\${_file}\' $_clang_format \$_file && $_cpplint \$_file && $_cppcheck \$_file done" done else # Do only given file(s) for _p in "${_path[@]}"; do local _file _file="$(dirs +1)/${_p}" lib_utils::print_normal "Linting '${_file}'" lib_docker::__docker_compose exec -it docker-finance /bin/bash -i -c \ "$_clang_format $_file && $_cpplint $_file && $_cppcheck $_file" done fi } function lib_linter::__lint_php() { local -r _path=("$@") local -r _ext="php" local -r _php_cs_fixer="time php-cs-fixer fix --rules=@PSR12 --verbose" local -r _phpstan="time phpstan analyse --debug --autoload-file /usr/local/lib/php/vendor/autoload.php --level 6" # TODO: incrementally increase to 9 if [ -z "${_path[*]}" ]; then # Do all lib_docker::__docker_compose exec -it docker-finance /bin/bash -i -c \ "$_php_cs_fixer $DOCKER_FINANCE_CLIENT_REPO && $_php_cs_fixer $DOCKER_FINANCE_CLIENT_PLUGINS \ && $_phpstan $DOCKER_FINANCE_CLIENT_REPO $DOCKER_FINANCE_CLIENT_PLUGINS" else # Do only given file(s) for _p in "${_path[@]}"; do local _file _file="$(dirs +1)/${_p}" lib_utils::print_normal "Linting '${_file}'" lib_docker::__docker_compose exec -it docker-finance /bin/bash -i -c \ "$_php_cs_fixer $_file && $_phpstan $_file" done fi } function lib_linter::__linter() { [[ -z "$DOCKER_FINANCE_CLIENT_REPO" || -z "$DOCKER_FINANCE_CLIENT_PLUGINS" ]] && lib_utils::die_fatal [[ ! "$PWD" =~ ^$DOCKER_FINANCE_CLIENT_REPO && ! -z "${global_arg_file[*]}" ]] \ && lib_utils::die_fatal "Sorry, you must work (and lint) from within parent directory '${DOCKER_FINANCE_CLIENT_REPO}'" lib_utils::print_debug "Bringing up network and container" [ -z "$global_network" ] && lib_utils::die_fatal local -r _no_funny_business="--internal=true" docker network create "$_no_funny_business" "$global_network" 2>/dev/null lib_docker::__docker_compose up -d docker-finance || lib_utils::die_fatal # Transparent bind-mount: although it's the container repo, its path is identical to client repo pushd "$DOCKER_FINANCE_CLIENT_REPO" 1>/dev/null || lib_utils::die_fatal for _type in "${global_arg_type[@]}"; do case "$_type" in bash | shell) lib_linter::__lint_bash "${global_arg_file[@]}" ;; c++ | cpp) lib_linter::__lint_cpp "${global_arg_file[@]}" ;; php) lib_linter::__lint_php "${global_arg_file[@]}" ;; esac lib_utils::print_custom "\n" done popd 1>/dev/null || lib_utils::die_fatal lib_docker::__docker_compose down local -r _ret=$? docker network rm "$global_network" 2>/dev/null # Don't force, if in use return $_ret } function lib_linter::linter() { lib_linter::__parse_args "$@" lib_linter::__linter && lib_utils::print_info "Linting completed successfully!" } # vim: sw=2 sts=2 si ai et