|
1 | 1 | # EncodingFixTool |
2 | | -encoding fix tool tailored to delphi pas files |
| 2 | + |
| 3 | +A fast, parallel, command-line fixer for Delphi source file encodings. |
| 4 | +It scans `.pas`, `.dpr`, …; detects UTF-8/ASCII vs. legacy single-byte encodings; repairs mixed lines (Windows-1250/1252/ANSI), and writes clean UTF-8 with an optional BOM — without breaking your line endings or folder structure. |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +## Why? |
| 9 | + |
| 10 | +* Old codebases often mix ANSI and UTF-8 (sometimes within a single file). |
| 11 | +* Delphi 12+ works best when sources are in UTF-8 (usually with BOM). |
| 12 | +* Manual converting is slow and risky. This tool automates it safely and fast. |
| 13 | + |
| 14 | +--- |
| 15 | + |
| 16 | +## Quick start |
| 17 | + |
| 18 | +``` |
| 19 | +EncodingFixTool [params] |
| 20 | +``` |
| 21 | + |
| 22 | +Examples: |
| 23 | + |
| 24 | +```bat |
| 25 | +:: Dry run, current folder, defaults (.pas,.dpr, recursive, BOM=yes) |
| 26 | +EncodingFixTool dry |
| 27 | +
|
| 28 | +:: Fix a project tree verbosely and back it up before changes |
| 29 | +EncodingFixTool path=C:\Projs\MyApp v bkp-dir=C:\backup\myapp |
| 30 | +
|
| 31 | +:: Only scan .pas files in ./src (not recursive), remove BOM if present |
| 32 | +EncodingFixTool path=.\src recursive=n ext=pas utf8-bom=n |
| 33 | +
|
| 34 | +:: Multiple ext forms are OK; quoted lists work |
| 35 | +EncodingFixTool ext="*.pas,*.dpr, .dfm" |
| 36 | +``` |
| 37 | + |
| 38 | +> Tip: Parameters accept `key=value` or `key:value`. You can prefix flags with `-` (and `/` on Windows), e.g. `-v`, `-dry`. |
| 39 | +
|
| 40 | +--- |
| 41 | + |
| 42 | +## Parameters |
| 43 | + |
| 44 | +| Param | Aliases | Values | Default | Meaning | |
| 45 | +| ------------- | ------- | -------------------------- | ----------- | ---------------------------------------------------------------------------------------------- | |
| 46 | +| `help` | `-h` | — | — | Prints extended help and exits. | |
| 47 | +| `dry` | — | — | off | Dry run: analyze and report what **would** change; no writes. | |
| 48 | +| `s`/`silent` | — | — | off | No console output. (Overrides `verbose`.) | |
| 49 | +| `v`/`verbose` | — | — | off | More output: “OK” lines etc. (Ignored if `silent`.) | |
| 50 | +| `path` | — | dir | current dir | Directory to scan. | |
| 51 | +| `recursive` | — | `y`/`n`/`yes`/`no`/`1`/`0` | `y` | Recurse into subfolders. | |
| 52 | +| `ext` | — | CSV list | `pas,dpr` | File extensions to include. Smart parsing: accepts `pas`, `.pas`, `*.pas`. Quoted lists OK. | |
| 53 | +| `utf8-bom` | — | `y`/`n` | `y` | Whether to **save with** UTF-8 BOM. **Pure US-ASCII files are always left without a BOM**. | |
| 54 | +| `bkp-dir` | — | dir | empty | If set, backs up every file **before** overwriting, preserving the relative path below `path`. | |
| 55 | + |
| 56 | +### Backup path example |
| 57 | + |
| 58 | +If `path=C:\tmp\` and `bkp-dir=C:\bkp` and a processed file is |
| 59 | +`C:\tmp\src\foo\bar\Main.pas` → backup is written to: |
| 60 | + |
| 61 | +``` |
| 62 | +C:\bkp\src\foo\bar\Main.pas |
| 63 | +``` |
| 64 | + |
| 65 | +--- |
| 66 | + |
| 67 | +## What it does (algorithm) |
| 68 | + |
| 69 | +1. **Gather files** |
| 70 | + Walk the `path` (recursively by default), matching the configured extensions. |
| 71 | + |
| 72 | +2. **Process in parallel** |
| 73 | + Uses `TParallel.For` to utilize multiple cores. Console output is synchronized, and the “files changed” counter is atomic. |
| 74 | + |
| 75 | +3. **Detect encoding per file** (fast pre-check) |
| 76 | + |
| 77 | + * Uses `System.WideStrUtils.DetectUTF8Encoding` on the raw bytes. |
| 78 | + * If **US-ASCII** → file is left as-is (never add a BOM). |
| 79 | + * If **UTF-8** → ensure BOM matches `utf8-bom` option; adjust if needed (optionally backing up first). |
| 80 | + * Else (treated as **ANSI / unknown**) → go to step 4. |
| 81 | + |
| 82 | +4. **Repair mixed/legacy encodings per line** |
| 83 | + |
| 84 | + * Split the original bytes by raw CR/LF/CRLF (no decoding yet). |
| 85 | + * For each line, try: |
| 86 | + |
| 87 | + * **UTF-8 (strict)** — if it round-trips, use it. |
| 88 | + * **Windows-1250** (Central Europe/PL), **Windows-1252** (Western/DE), and **ANSI** — decode and **score**: |
| 89 | + |
| 90 | + * +2 for valid Polish/German diacritics, |
| 91 | + * +1 for typical source characters (letters/digits/whitespace/common punctuation), |
| 92 | + * −2 for control chars (except tab), |
| 93 | + * −1 for U+FFFD replacements. |
| 94 | + * Pick the **highest-scoring** decode for that line. |
| 95 | + * Reassemble the file: |
| 96 | + |
| 97 | + * Preserve the **dominant original EOL style** (CRLF/LF/CR). |
| 98 | + * Preserve whether the file ended **with a trailing EOL**. |
| 99 | + * Save as **UTF-8** (BOM per `utf8-bom`), optionally to backup first. |
| 100 | + |
| 101 | +5. **Summary** |
| 102 | + At the end prints elapsed time and number of files changed. |
| 103 | + |
| 104 | +--- |
| 105 | + |
| 106 | +## Output examples |
| 107 | + |
| 108 | +Dry run: |
| 109 | + |
| 110 | +``` |
| 111 | +Would fix: src\Utils\StrTools.pas (mixed bytes; would save UTF-8 (BOM=Y, EOL=CRLF) (dry-run)) |
| 112 | +OK : src\Main.dpr (UTF-8 OK) |
| 113 | +Done in 00:12.384. Files changed: 0 |
| 114 | +``` |
| 115 | + |
| 116 | +Actual run: |
| 117 | + |
| 118 | +``` |
| 119 | +Fixed: src\Utils\StrTools.pas (detected Windows-1250; saved UTF-8 (BOM=Y, EOL=CRLF)) |
| 120 | +Fixed: src\Forms\About.pas (detected UTF-8; Added UTF-8 BOM) |
| 121 | +Done in 00:08.972. Files changed: 2 |
| 122 | +``` |
| 123 | + |
| 124 | +> When `silent` is enabled, only the summary is suppressed too. In dry-run mode, change messages are phrased as “Would fix”. |
| 125 | +
|
| 126 | +--- |
| 127 | + |
| 128 | +## Behavior details |
| 129 | + |
| 130 | +* **BOM policy** |
| 131 | + |
| 132 | + * Default is **BOM = yes** (Delphi IDE/compiler are happiest this way). |
| 133 | + * **US-ASCII** files are **never** given a BOM, even if `utf8-bom=y`. |
| 134 | + * Existing UTF-8 files are re-saved only if the BOM policy differs. |
| 135 | + |
| 136 | +* **Line endings** |
| 137 | + Dominant EOL (CRLF/LF/CR) is detected from raw bytes and **preserved**; trailing newline presence is preserved. |
| 138 | + |
| 139 | +* **Relative reporting** |
| 140 | + Paths in logs are shown **relative to** the scanned `path`, for readability. |
| 141 | + |
| 142 | +--- |
| 143 | + |
| 144 | +## Exit codes |
| 145 | + |
| 146 | +* `0` – completed (even if some files failed; failures are printed). |
| 147 | +* `2` – the `path` argument didn’t exist. |
0 commit comments