diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100644 index 0000000..88ba850 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,217 @@ +#!/usr/bin/env bash +# +# .githooks/pre-commit +# +# Runs clang-format and clang-tidy against staged C/C++ files before each +# commit — no Python, no pre-commit framework, just bash and (optionally) +# the clang tools themselves. +# +# Neither tool is required to have this hook installed: if a binary isn't +# on PATH, this prints a warning and skips that check instead of blocking +# the commit. Only *actual formatting/lint issues* from a tool that IS +# installed will block a commit (unless --fix resolves them, see below). +# +# Platforms: +# - Linux / macOS: runs under the system bash (or Homebrew bash on macOS) +# with no extra setup. +# - Windows: runs automatically through Git for Windows' bundled Git Bash — +# git.exe recognizes the "#!/usr/bin/env bash" line above and executes +# hooks through it directly, so no WSL, Cygwin, or separate bash install +# is needed. Everything this script uses (mktemp, diff, grep, printf, +# command -v) ships with Git for Windows out of the box. +# +# One-time setup per clone (same command on every OS): +# chmod +x .githooks/pre-commit (no-op on Windows, harmless either way) +# git config core.hooksPath .githooks +# +# Usage: +# .githooks/pre-commit # hook mode: checks staged files, blocks on issues +# .githooks/pre-commit --all # checks every tracked C/C++ file, ignoring staging +# .githooks/pre-commit --fix # applies fixes to staged files, then re-stages them +# .githooks/pre-commit --all --fix # applies fixes across the whole tree (flags combine) +# +# What --fix actually does: +# clang-format -i always safe — pure whitespace/layout, never changes behavior. +# clang-tidy --fix NOT always safe — some checks (e.g. identifier renames) can +# occasionally produce code that doesn't compile if the fix is +# context-sensitive. Review the diff after running --fix before +# you trust it blindly, especially the first few times. +# +# In staged (non --all) mode, --fix re-stages (git add) any file it modifies, so the +# fixed version — not the original — is what actually gets committed. In --all mode +# nothing is staged automatically; you review and commit the changes yourself. +set -uo pipefail + +# Colors only when writing to an interactive terminal — avoids raw escape +# codes in CI logs, redirected output, or terminals that don't support ANSI. +if [ -t 1 ]; then + RED=$'\033[0;31m' + YELLOW=$'\033[0;33m' + GREEN=$'\033[0;32m' + RESET=$'\033[0m' +else + RED='' + YELLOW='' + GREEN='' + RESET='' +fi + +exit_code=0 + +# --- Flag parsing (order-independent) --- +# --all: check every tracked file matching the patterns, ignoring staging +# state entirely (git ls-files, not git diff --cached). +# --fix: apply fixes in place instead of just reporting them. +all_mode=0 +fix_mode=0 +for arg in "$@"; do + case "$arg" in + --all) all_mode=1 ;; + --fix) fix_mode=1 ;; + *) + echo "${RED}unknown argument: $arg${RESET}" + echo "usage: $0 [--all] [--fix]" + exit 2 + ;; + esac +done + +# --- Collect C/C++ files (added/copied/modified, or all tracked in --all mode) --- +# ':!vendor/*' excludes third-party code vendored under vendor/ (see +# cmake/Dependencies.cmake in the parent project) -- it isn't this +# project's code, so it isn't held to this style guide. Not part of the +# toolkit as shipped; added for this project's vendor/ convention. +files=() +if [ "$all_mode" -eq 1 ]; then + echo "mode: --all (checking every tracked C/C++ file via 'git ls-files')" + while IFS= read -r line; do + [ -n "$line" ] && files+=("$line") + done < <(git ls-files -- \ + '*.c' '*.h' '*.cpp' '*.hpp' '*.cc' '*.cxx' '*.tpp' ':!vendor/*') +else + echo "mode: staged (checking files via 'git diff --cached')" + while IFS= read -r line; do + [ -n "$line" ] && files+=("$line") + done < <(git diff --cached --name-only --diff-filter=ACM -- \ + '*.c' '*.h' '*.cpp' '*.hpp' '*.cc' '*.cxx' '*.tpp' ':!vendor/*') +fi + +if [ "$fix_mode" -eq 1 ]; then + echo "fix: enabled (issues will be corrected in place, not just reported)" +fi + +if [ ${#files[@]} -eq 0 ]; then + echo "${YELLOW}no matching C/C++ files found — nothing to check.${RESET}" + exit 0 +fi + +echo "found ${#files[@]} file(s) to check:" +printf ' %s\n' "${files[@]}" + +# --- clang-format: check (or fix) formatting --- +if command -v clang-format >/dev/null 2>&1; then + if [ "$fix_mode" -eq 1 ]; then + echo "running clang-format --fix (in-place, $(command -v clang-format))..." + fixed=() + for f in "${files[@]}"; do + [ -f "$f" ] || continue + if ! diff -q <(clang-format "$f") "$f" >/dev/null 2>&1; then + clang-format -i "$f" + fixed+=("$f") + fi + done + if [ ${#fixed[@]} -gt 0 ]; then + echo "${GREEN}clang-format: reformatted ${#fixed[@]} file(s):${RESET}" + printf ' %s\n' "${fixed[@]}" + if [ "$all_mode" -eq 0 ]; then + git add -- "${fixed[@]}" + echo "re-staged the fixed file(s) so they're included in this commit." + fi + fi + else + echo "running clang-format check ($(command -v clang-format))..." + unformatted=() + for f in "${files[@]}"; do + [ -f "$f" ] || continue + if ! diff -q <(clang-format "$f") "$f" >/dev/null 2>&1; then + unformatted+=("$f") + fi + done + if [ ${#unformatted[@]} -gt 0 ]; then + echo "${RED}clang-format: these files aren't formatted:${RESET}" + printf ' %s\n' "${unformatted[@]}" + echo "Fix with: clang-format -i ${unformatted[*]}" + echo "(or rerun this hook with --fix to apply automatically)" + exit_code=1 + fi + fi +else + echo "${YELLOW}warning: clang-format not found on PATH — skipping format check.${RESET}" +fi + +# --- clang-tidy: static analysis (or fix), needs a compile database --- +if command -v clang-tidy >/dev/null 2>&1; then + if [ ! -f compile_commands.json ]; then + echo "${YELLOW}warning: compile_commands.json not found — skipping clang-tidy.${RESET}" + echo "${YELLOW}Generate one with: cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ...${RESET}" + else + tidy_output=$(mktemp) + tidy_had_issues=0 + if [ "$fix_mode" -eq 1 ]; then + echo "running clang-tidy --fix (in-place, $(command -v clang-tidy))..." + echo "${YELLOW}note: some clang-tidy fixes are context-sensitive and can occasionally${RESET}" + echo "${YELLOW}produce code that doesn't compile — review the diff before trusting it.${RESET}" + for f in "${files[@]}"; do + [ -f "$f" ] || continue + if ! clang-tidy --quiet --fix --fix-errors -p . "$f" >>"$tidy_output" 2>&1; then + tidy_had_issues=1 + fi + done + if [ "$all_mode" -eq 0 ]; then + changed=() + while IFS= read -r line; do + [ -n "$line" ] && changed+=("$line") + done < <(git diff --name-only -- "${files[@]}") + if [ ${#changed[@]} -gt 0 ]; then + git add -- "${changed[@]}" + echo "${GREEN}clang-tidy: modified and re-staged ${#changed[@]} file(s):${RESET}" + printf ' %s\n' "${changed[@]}" + fi + fi + else + echo "running clang-tidy check ($(command -v clang-tidy))..." + for f in "${files[@]}"; do + [ -f "$f" ] || continue + if ! clang-tidy --quiet "$f" >>"$tidy_output" 2>&1; then + tidy_had_issues=1 + fi + done + fi + + if grep -q "warning:\|error:" "$tidy_output" 2>/dev/null; then + if [ "$fix_mode" -eq 1 ]; then + echo "${YELLOW}clang-tidy: issues remaining after fix attempt (not all checks can auto-fix):${RESET}" + else + echo "${RED}clang-tidy found issues:${RESET}" + echo "(rerun this hook with --fix to auto-correct what clang-tidy can fix)" + fi + cat "$tidy_output" + exit_code=1 + elif [ "$tidy_had_issues" -eq 1 ]; then + # Non-zero exit with no warning/error text usually means clang-tidy + # itself couldn't run (e.g. missing headers) — surface it, but + # don't block the commit on a tooling problem rather than a style one. + echo "${YELLOW}warning: clang-tidy exited with an error unrelated to style:${RESET}" + cat "$tidy_output" + fi + rm -f "$tidy_output" + fi +else + echo "${YELLOW}warning: clang-tidy not found on PATH — skipping static analysis.${RESET}" +fi + +if [ $exit_code -eq 0 ]; then + echo "${GREEN}pre-commit checks passed.${RESET}" +fi + +exit $exit_code diff --git a/source/CLIForge/include/CLIForge/Engine.hpp b/source/CLIForge/include/CLIForge/Engine.hpp index 417f858..40af18b 100644 --- a/source/CLIForge/include/CLIForge/Engine.hpp +++ b/source/CLIForge/include/CLIForge/Engine.hpp @@ -20,8 +20,7 @@ namespace cliforge { - class Engine - { + class Engine { public: explicit Engine(std::string programName = "");