diff --git a/bin/clang-format-fix.ps1 b/bin/clang-format-fix.ps1 new file mode 100644 index 00000000..9eae1715 --- /dev/null +++ b/bin/clang-format-fix.ps1 @@ -0,0 +1,154 @@ +<# +.SYNOPSIS + Runs clang-format -i on project *.cpp and *.h files. + +.DESCRIPTION + Formats all C/C++ source and header files in the repository, excluding + generated, vendored, and build directories (open-x4-sdk, builtinFonts, + hyphenation tries, uzlib, .pio, *.generated.h). + + The clang-format binary path is resolved once and cached in + bin/clang-format-fix.local. On first run it checks a default path, + then PATH, then common install locations. Edit the .local file to + override manually. + +.PARAMETER g + Format only git-modified files (git diff --name-only HEAD) instead of + the full tree. + +.PARAMETER h + Show this help text. + +.EXAMPLE + .\clang-format-fix.ps1 + Format all files. + +.EXAMPLE + .\clang-format-fix.ps1 -g + Format only git-modified files. +#> + +param( + [switch]$g, + [switch]$h +) + +if ($h) { + Get-Help $PSCommandPath -Detailed + return +} + +$repoRoot = (Resolve-Path "$PSScriptRoot\..").Path +$configFile = Join-Path $PSScriptRoot 'clang-format-fix.local' +$defaultPath = 'C:\Program Files\LLVM\bin\clang-format.exe' + +$candidatePaths = @( + 'C:\Program Files\LLVM\bin\clang-format.exe' + 'C:\Program Files (x86)\LLVM\bin\clang-format.exe' + 'C:\msys64\ucrt64\bin\clang-format.exe' + 'C:\msys64\mingw64\bin\clang-format.exe' + "$env:LOCALAPPDATA\LLVM\bin\clang-format.exe" +) + +function Find-ClangFormat { + # Try PATH first + $inPath = Get-Command clang-format -ErrorAction SilentlyContinue + if ($inPath) { return $inPath.Source } + + # Try candidate paths + foreach ($p in $candidatePaths) { + if (Test-Path $p) { return $p } + } + return $null +} + +function Resolve-ClangFormat { + # 1. Read from config if present + if (Test-Path $configFile) { + $saved = (Get-Content $configFile -Raw).Trim() + if ($saved -and (Test-Path $saved)) { return $saved } + Write-Host "Configured path no longer valid: $saved" + } + + # 2. Check default + if (Test-Path $defaultPath) { + $defaultPath | Set-Content $configFile + Write-Host "Saved clang-format path to $configFile" + return $defaultPath + } + + # 3. Search PATH and candidate locations + $found = Find-ClangFormat + if ($found) { + $found | Set-Content $configFile + Write-Host "Found clang-format at $found - saved to $configFile" + return $found + } + + Write-Error "clang-format not found. Install LLVM or add clang-format to PATH." + exit 1 +} + +$clangFormat = Resolve-ClangFormat + +$exclude = @( + 'open-x4-sdk' + 'lib\EpdFont\builtinFonts' + 'lib\Epub\Epub\hyphenation\generated' + 'lib\uzlib' + '.pio' +) + +function Test-Excluded($fullPath) { + foreach ($ex in $exclude) { + if ($fullPath -like "*\$ex\*") { return $true } + } + if ($fullPath -like '*.generated.h') { return $true } + return $false +} + +if ($g) { + # Only git-modified *.cpp / *.h files + # Covers both staged and unstaged changes + $files = @(git -C $repoRoot diff --name-only HEAD) + + @(git -C $repoRoot diff --name-only --cached) | + Sort-Object -Unique | + Where-Object { $_ -match '\.(cpp|h)$' } | + ForEach-Object { Get-Item (Join-Path $repoRoot $_) -ErrorAction SilentlyContinue } | + Where-Object { $_ -and -not (Test-Excluded $_.FullName) } +} else { + $files = Get-ChildItem -Path $repoRoot -Recurse -Include *.cpp, *.h -File | + Where-Object { -not (Test-Excluded $_.FullName) } +} + +$files = @($files) + +if ($files.Count -eq 0) { + Write-Host 'No files to format.' + return +} + +Write-Host "Formatting $($files.Count) files..." +$i = 0 +$changed = 0 +$failures = 0 +foreach ($f in $files) { + $i++ + $rel = $f.FullName.Substring($repoRoot.Length + 1) + $hashBefore = (Get-FileHash $f.FullName -Algorithm MD5).Hash + & $clangFormat -i $f.FullName + if ($LASTEXITCODE -ne 0) { + $failures++ + Write-Host " [$i/$($files.Count)] $rel (FAILED, exit code $LASTEXITCODE)" + continue + } + $hashAfter = (Get-FileHash $f.FullName -Algorithm MD5).Hash + if ($hashBefore -ne $hashAfter) { + $changed++ + Write-Host " [$i/$($files.Count)] $rel (changed)" + } else { + Write-Host " [$i/$($files.Count)] $rel" + } +} +Write-Host "Done. $changed/$($files.Count) files changed, $failures failed." +if ($failures -gt 0) { exit 1 }