Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 339ad0a4bc | |||
| 57c4a63d06 | |||
| 9b6671b0ed | |||
| 7f06f3ca4c | |||
| 234f6a5e7f | |||
| 51cab198c4 | |||
| 558973f0c6 | |||
| 76ca98f0bf | |||
| a05687a38f | |||
| 16f7d75363 | |||
| f36a26a9c6 | |||
| b449e0aa2f | |||
| ffdaa88d74 | |||
| 98e1295eec | |||
| 7e4c0154f2 | |||
| d873d52aea |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -20,6 +20,8 @@
|
|||||||
#programms
|
#programms
|
||||||
!*.prg
|
!*.prg
|
||||||
!*.fpw
|
!*.fpw
|
||||||
|
!*.ps1
|
||||||
|
!*.bat
|
||||||
|
|
||||||
#header
|
#header
|
||||||
!*.h
|
!*.h
|
||||||
|
|||||||
144
CLAUDE.md
Normal file
144
CLAUDE.md
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## What this is
|
||||||
|
|
||||||
|
A local shared copy of **FoxBin2Prg** (the VFPX tool by Fernando D. Bozzo, maintained by Lutz Scheffler): a Visual FoxPro 9 program that converts VFP binary files to PRG-style text and back. Supported conversions: `PJX↔PJ2`, `SCX↔SC2`, `VCX↔VC2`, `FRX↔FR2`, `LBX↔LB2`, `DBC↔DC2`, `DBF↔DB2`, `MNX↔MN2`.
|
||||||
|
|
||||||
|
This directory is a git clone with two remotes:
|
||||||
|
|
||||||
|
- **`origin`** = `https://github.com/fdbozzo/foxbin2prg.git` (upstream, read-only). `master` tracks `origin/master` and stays a pristine mirror.
|
||||||
|
- **`romfast`** = `git@gitea.romfast.ro:romfast/foxbin2prg.git` (our fork on Gitea, SSH). Working branch **`romfast`** (default on Gitea) = upstream `master` + the ROA additions committed (`vcx2txt.ps1`, `PROMPT_cautare_vfp.md`, `CLAUDE.md`). The pre-git local state is kept on branch `local-snapshot-v1.21.04`.
|
||||||
|
|
||||||
|
Update flow (sync fork from upstream): `git fetch origin` → `git checkout master && git merge --ff-only origin/master` → `git checkout romfast && git merge master` → `git push romfast romfast master`. After any update, regenerate binaries/EXE in VFP9 with `DO ReCreate_FoxBin2Prg.prg` (an error about `TESTS\FXUResults.db2` is expected). Text caches live outside this folder (`D:\ROA\_vfp_textcache\`, see below) so `ReCreate` never touches them; compiled binaries are kept out of git via the upstream `.gitignore` plus `.git/info/exclude` (which also still ignores any stray local `_textcache*`).
|
||||||
|
|
||||||
|
It serves two purposes:
|
||||||
|
|
||||||
|
1. Upstream tool source (for occasional patches to FoxBin2Prg itself).
|
||||||
|
2. A **shared search tool for the ROA VFP projects** (`D:\ROA\ROAGEST`, `D:\ROA\ROAIMOB`, `D:\ROA\ROAACNPRO`, …) via the local additions below.
|
||||||
|
|
||||||
|
## Local additions (ROA-specific, not part of upstream)
|
||||||
|
|
||||||
|
- **`git_sync.ps1`** — the **in-tree** flow (primary for migrated ROA projects): folder-mode recursive over the whole project tree (incl. `COMUN\`), generates the `.??2` text **next to each binary** (not in an external cache) so git tracks readable text instead of binaries. Incremental, converts in a temp staging area and copies back only the text (never touches the SVN working-copy binaries), cleans up orphan `.??2`, continues past per-file failures and exits nonzero on any. Params: `-ProjectRoot`, `-DbfList` (tables converted to `.db2` with data), `-RoundtripExempt`, `-SubDir`. Run at session start and before every `git commit`; do not commit while it reports failures.
|
||||||
|
- **`vcx2txt.ps1`** — PowerShell script that converts the VFP binaries of a project (read from its `.pjx`) into text under a cache folder, so the code inside `.vcx/.scx/.frx/.mnx/.dbc` binaries becomes greppable. Incremental (skips up-to-date files), does not need the VFP IDE open (uses the compiled `FoxBin2Prg.EXE`). Search-only / non-migrated projects; migrated projects use `git_sync.ps1` instead.
|
||||||
|
- **`vfp_symbols.ps1`** — symbol index over the text cache and the project's `.prg`: for every class/method/procedure it records the **line range**, so a hit can be attributed to its owner. Answers what grep cannot in a `.vc2` (where the class header may be 13k lines above the hit and ~2/3 of the file is properties/metadata): `-Grep <regex>` labels each match with `class.method` (`-CodeOnly` drops metadata hits), `-Where 'file:line'` names the enclosing member, `-Find <name>` locates the **definition**, `-Class <name>` prints the inheritance chain plus members with their ranges. Rebuilds automatically when the cache is newer (~4s); index is a regenerable `_symbols.tsv` in the cache root.
|
||||||
|
- **`txt2vcx.ps1`** — the write-back counterpart: regenerates `.vcx/.scx` binaries from edited `.vc2/.sc2` text and copies them back into the project (see "Golden rule" below for scope/limits). `-CacheRoot` defaults to `-ProjectRoot` (in-tree flow: cache→project mapping is identity); pass a separate root only for the old external-cache flow.
|
||||||
|
- **`vfp_filemap.ps1`** — shared bin↔memo↔text extension map (`.vcx`/`.vct`/`.vc2`, `.scx`/`.sct`/`.sc2`, `.pjx`/`.pjt`/`.pj2`, `.dbf`/`.fpt`/`.db2`, …), dot-sourced by `git_sync.ps1`, `vcx2txt.ps1` and `txt2vcx.ps1` so the mapping stays in one place. Also hosts `Invoke-FoxBin2PrgSafe` (timeout + `taskkill`).
|
||||||
|
- **`test_roundtrip.ps1`** — standalone fidelity/regression test for the bin→text→bin→text roundtrip and for `txt2vcx.ps1`'s guard rails (staleness, COMUN, missing binary, negative/corrupt input, multi-file runs); parametrized per project. See examples below.
|
||||||
|
- **`test_git_sync.ps1`** — standalone tests for `git_sync.ps1` on temp fixtures: incremental (up-to-date files skipped), orphan cleanup, nonzero exit on partial failure, and binaries byte-identical after any run.
|
||||||
|
- **Text caches** (external-cache flow, non-migrated / search-only projects) live **outside this folder**, under `D:\ROA\_vfp_textcache\` (one subfolder per project: `roacnpro`, `roagest`, `roaimob`) — per-project regenerable text caches. Migrated projects (ROACONT, COMUN, …) keep their `.??2` in-tree via `git_sync.ps1` instead of a cache here. Safe to delete; never version them. **They must stay outside `foxbin2prg`**: `ReCreate_FoxBin2Prg.prg` does a recursive `Prg2Bin` over the whole tool folder (`get_FilesFromDirectory` recurses into every subfolder, foxbin2prg.prg:6135) and would reconvert any `.??2` under it back into stray binaries.
|
||||||
|
- **`PROMPT_cautare_vfp.md`** — reusable prompt (Romanian) explaining how to set up this search flow in any VFP project. Read it before working on VFP code search.
|
||||||
|
|
||||||
|
**Golden rule:** the text cache is for searching, and — for `.vcx`/`.scx` only — for making real code edits via `txt2vcx.ps1`. Caveats:
|
||||||
|
|
||||||
|
- The generated text format is **position-sensitive**: don't reformat or reflow lines when editing; preserve the exact line layout.
|
||||||
|
- Properties/methods are emitted **alphabetically sorted** — a newly added property must be inserted in alphabetical order, not appended.
|
||||||
|
- Only `.vcx`/`.scx` support write-back. `.frx/.mnx/.lbx/.pjx/.dbc/.dbf` are **read-only text** (edit in the VFP IDE): menus still need GENMENU to produce `.mpr`, and the other formats are too fragile for round-trip write-back. `git_sync.ps1` still generates their `.??2` for readable diffs, but never writes them back.
|
||||||
|
- Six FFC/third-party classes under `COMUN\` are in `git_sync.ps1`'s `-RoundtripExempt` list (`#INCLUDE` makes the isolated roundtrip a false-negative): `accessibility.vcx`, `oinventar.vcx`, `_gdiplus.vcx`, `_reportlistener.vcx` (in `COMUN\clase\`), `foxcharts.vcx` (`COMUN\utile\foxcharts\`), `_webview.vcx` (`COMUN\utile\web\`). Their text is valid (bin→text runs) but roundtrip is not guaranteed — edit them only in the IDE.
|
||||||
|
- Targets under `COMUN\` require `-AllowComun` **and** explicit approval — a change there affects every ROA app.
|
||||||
|
- **After any VFP IDE session** (even one that only opened a class/form), re-run `vcx2txt.ps1` to refresh the cache before editing text — otherwise `txt2vcx.ps1`'s staleness check will (correctly) refuse to write back over unseen IDE changes.
|
||||||
|
- **Encoding**: `.vc2`/`.sc2` files declare `CPID="1252"` in their header, but for ROAGEST at least the Romanian-diacritics bytes are actually **cp1250** (`0xE3`=ă, `0xBA`=ş, etc.) — the header lies. Don't decode/re-encode the text with a codepage inferred from the header; treat it byte-preserving (read/write/copy as raw bytes, compare on bytes) and this doesn't matter in practice — it only bites if a script tries to interpret the text as a specific codepage string.
|
||||||
|
- **Corrupt text can trigger a modal VFP dialog, not just a nonzero exit code**: if `.vc2`/`.sc2` text is malformed, `Prg2Bin` can return `ErrorLevel 0` while producing a corrupt binary — the real error only surfaces on a subsequent bin→text pass, sometimes as a **native VFP MessageBox that blocks headlessly** (not suppressible via parameters/`.cfg`). Any automated invocation of `FoxBin2Prg.EXE` must go through the `Invoke-FoxBin2PrgSafe` wrapper in `vfp_filemap.ps1` (default 60s timeout + `taskkill` on hang) rather than calling the exe directly — and treat the **fidelity check** (bin→text→compare), not the exe's `ErrorLevel`, as the real backstop against corrupt/malformed text.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Populate/refresh a project's text cache (incremental)
|
||||||
|
& 'D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1' -Project 'D:\ROA\ROAGEST\roagest.pjx' -ProjectRoot 'D:\ROA\ROAGEST' -CacheRoot 'D:\ROA\_vfp_textcache\roagest'
|
||||||
|
|
||||||
|
# Include reports/menus too: -Types vcx,scx,frx,mnx
|
||||||
|
# One-off conversion of a file/folder not in the project: -Source 'path\to\lib.vcx'
|
||||||
|
# Other flags: -Force (reconvert all), -Clean (wipe cache first)
|
||||||
|
```
|
||||||
|
|
||||||
|
Then search the cache with Grep (e.g. `PROCEDURE do_salvare`), citing `file:line` from the cache.
|
||||||
|
|
||||||
|
Attribute hits to their owning class/method (build the index first, or let it build itself):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
& 'D:\ROA\UTIL\foxbin2prg\vfp_symbols.ps1' -CacheRoot 'D:\ROA\_vfp_textcache\roagest' -ProjectRoot 'D:\ROA\ROAGEST' -Grep 'do_salvare' -CodeOnly
|
||||||
|
|
||||||
|
# -Where 'oavize.vc2:8412' what class/method owns that line (needed before a txt2vcx edit)
|
||||||
|
# -Find 'do_salvare' where it is DEFINED (grep also returns the call sites)
|
||||||
|
# -Class frm_avize inheritance chain + members with line ranges
|
||||||
|
```
|
||||||
|
|
||||||
|
Write back an edited `.vc2`/`.sc2` (after reviewing the diff on text):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
& 'D:\ROA\UTIL\foxbin2prg\txt2vcx.ps1' -TextFile 'D:\ROA\_vfp_textcache\roagest\Clase\oavize.vc2' -ProjectRoot 'D:\ROA\ROAGEST' -CacheRoot 'D:\ROA\_vfp_textcache\roagest'
|
||||||
|
|
||||||
|
# Preview only, no files touched in the project: add -DryRun
|
||||||
|
# Target under COMUN\: add -AllowComun (needs approval)
|
||||||
|
# Skip the staleness check (IDE binary is known up to date some other way): -Force
|
||||||
|
# Skip the fidelity re-check after regen (not recommended): -NoVerify
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the regression/fidelity test suite (no Pester, standalone):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
& 'D:\ROA\UTIL\foxbin2prg\test_roundtrip.ps1' -ProjectRoot 'D:\ROA\ROAGEST' -CacheRoot 'D:\ROA\_vfp_textcache\roagest' -Phase all
|
||||||
|
|
||||||
|
# Run a single phase: -Phase fidelity|edit|negative|smoke
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
All FoxBin2Prg commands run inside VFP 9 (or via the compiled `FoxBin2Prg.EXE`).
|
||||||
|
|
||||||
|
```foxpro
|
||||||
|
* After clone/pull: regenerate all binaries from text and compile the EXEs.
|
||||||
|
* NEVER run foxbin2prg.prg directly for this — it uses a special config.
|
||||||
|
DO ReCreate_FoxBin2Prg.prg
|
||||||
|
|
||||||
|
* Convert a single file (direction inferred from extension)
|
||||||
|
DO FOXBIN2PRG.PRG WITH "<path>\file.scx" && binary -> text (.sc2)
|
||||||
|
DO FOXBIN2PRG.PRG WITH "<path>\file.sc2" && text -> binary (.scx)
|
||||||
|
|
||||||
|
* Convert a whole directory
|
||||||
|
DO FOXBIN2PRG.PRG WITH "<path>","Bin2Prg"
|
||||||
|
DO FOXBIN2PRG.PRG WITH "<path>","Prg2Bin"
|
||||||
|
|
||||||
|
* Config templates
|
||||||
|
DO FOXBIN2PRG.PRG WITH "-c","template.cfg" && default settings
|
||||||
|
DO FOXBIN2PRG.PRG WITH "-C","config.cfg" && active settings
|
||||||
|
DO FOXBIN2PRG.PRG WITH "-t","table.dbf.cfg" && per-table template
|
||||||
|
|
||||||
|
* Interactive help / option list
|
||||||
|
DO FOXBIN2PRG.PRG WITH "?"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
Unit tests live in `TESTS/` and use **FoxUnit** (path configured in `TESTS/foxunit_path.txt`). Files: `ut__foxbin2prg__<class>__<method>.prg` (unit) and `ft__foxbin2prg__<class>.prg` (functional). Run them from VFP via FoxUnit; test fixtures live in `TESTS/DATOS_READONLY/`.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Nearly everything is in one ~34,000-line file: **`foxbin2prg.prg`**. Key class hierarchy inside it:
|
||||||
|
|
||||||
|
- **`c_foxbin2prg` (As Session)** — entry point / public API. Parameter parsing, config resolution (`evaluateConfiguration`), file dispatch, progress UI, error logging. Also usable as COM-ish object (`CREATEOBJECT('c_foxbin2prg')`, `.execute(...)`), which is how the `.vbs` helpers and SCM integrations drive it.
|
||||||
|
- **`c_conversor_base`** — shared conversion logic (backup handling, timestamps, comment/property parsing, exclusion blocks).
|
||||||
|
- **`c_conversor_bin_a_prg`** and per-type subclasses (`c_conversor_vcx_a_prg`, `c_conversor_scx_a_prg`, `c_conversor_pjx_a_prg`, `c_conversor_dbf_a_prg`, …) — binary → text.
|
||||||
|
- **`c_conversor_prg_a_bin`** and per-type subclasses — text → binary.
|
||||||
|
- **`CL_*` model classes** (`CL_CLASE`, `CL_PROJECT`, `CL_DBC*`, `CL_DBF_*`, `CL_MENU*`, …) — object model the text files are parsed into / generated from.
|
||||||
|
- **`CL_CFG` / `CL_DBF_CFG`** — config file handling (`FOXBIN2PRG.CFG` with directory inheritance, per-table `.dbf.cfg`).
|
||||||
|
- **`CL_LANG`** — localization (EN/ES/FR/DE), auto-selected.
|
||||||
|
|
||||||
|
Supporting files: `foxbin2prg_keywords.dbf/.cdx` (VFP keyword table used during generation — has its own `.db2` text form), `props_*.txt` (per-control default property lists used to suppress default values in output), `Create_FoxBin2Prg.cfg` (the special self-conversion config), `Fb2P_Diff/` and `FileName_Caps/` (companion sub-projects, built by `ReCreate_FoxBin2Prg.prg`), `BuildProcess/` (VFPXDeployment build hooks), `ThorUpdater/` (Thor/VFPX release plumbing).
|
||||||
|
|
||||||
|
**Text format is position-sensitive:** the generated `.??2` files look like PRG/XML but are parsed line by line; structure and line layout must be preserved when editing them. Properties/methods are emitted alphabetically sorted.
|
||||||
|
|
||||||
|
## Upstream contribution conventions (from `.github/CONTRIBUTING.md`)
|
||||||
|
|
||||||
|
If patching FoxBin2Prg itself:
|
||||||
|
|
||||||
|
- Two version constants at the top of `foxbin2prg.prg`: bump only the minor part of `DC_FB2PRG_VERSION_REAL` (e.g. `'1.21.04'`); do **not** change `DN_FB2PRG_VERSION` (`1.21`) — it's written into every text file and forces mass recommits.
|
||||||
|
- Add the change to the history section at the top of `foxbin2prg.prg` and to `docs/ChangeLog.md`; update the version in `README.md`.
|
||||||
|
- Style: mixed-case keywords, tab indentation, spaces around operators and after list items; do not run BeautifyX with mDots insertion.
|
||||||
|
- After changes, regenerate text with `DO Create_FoxBin2Prg.prg` and compile in **VFP9 SP2**.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- `docs/FoxBin2Prg_Internals.md` — settings reference and internals (ZOrder, timestamps, class-per-file, DBF export hooks, API).
|
||||||
|
- `docs/FoxBin2Prg_Run.md` — full command-line/parameter reference.
|
||||||
|
- `docs/FoxBin2Prg_git.md`, `docs/FoxBin2Prg_SCM.md` — SCM integration.
|
||||||
|
- Spanish docs in `Documentacion/` are unmaintained.
|
||||||
@@ -32,7 +32,7 @@ _AutoIncrement = "0"
|
|||||||
|
|
||||||
|
|
||||||
*<BuildProj>
|
*<BuildProj>
|
||||||
*<.HomeDir = 'e:\se\vfpx\foxbin2prg\fb2p_diff' />
|
*<.HomeDir = 'd:\roa\util\foxbin2prg\fb2p_diff' />
|
||||||
|
|
||||||
FOR EACH loProject IN _VFP.Projects FOXOBJECT
|
FOR EACH loProject IN _VFP.Projects FOXOBJECT
|
||||||
loProject.Close()
|
loProject.Close()
|
||||||
@@ -70,9 +70,9 @@ ENDWITH
|
|||||||
|
|
||||||
WITH loProject
|
WITH loProject
|
||||||
*<ProjectProperties>
|
*<ProjectProperties>
|
||||||
.SetMain(lcCurdir + 'main_fb2p_diff.prg')
|
.SetMain("main_fb2p_diff.prg")
|
||||||
|
|
||||||
.Icon = lcCurdir + 'fb2p_diff.ico'
|
.Icon = "fb2p_diff.ico"
|
||||||
.Debug = .T.
|
.Debug = .T.
|
||||||
.Encrypted = .F.
|
.Encrypted = .F.
|
||||||
*<.CmntStyle = 1 />
|
*<.CmntStyle = 1 />
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -3,5 +3,4 @@
|
|||||||
*--------------------------------------------------------------------------------------------------------------------------------------------------------
|
*--------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
*< FOXBIN2PRG: Version="1.21" SourceFile="fb2p_diff.scx" CPID="1252" /> (Solo para binarios VFP 9 / Only for VFP 9 binaries)
|
*< FOXBIN2PRG: Version="1.21" SourceFile="fb2p_diff.scx" CPID="1252" /> (Solo para binarios VFP 9 / Only for VFP 9 binaries)
|
||||||
*
|
*
|
||||||
|
|
||||||
*
|
*
|
||||||
127
PROMPT_cautare_vfp.md
Normal file
127
PROMPT_cautare_vfp.md
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# Prompt: căutare și editare în cod VFP (foxbin2prg) — reutilizabil în orice proiect Visual FoxPro
|
||||||
|
|
||||||
|
Acest fișier conține un **prompt gata de copiat** pentru Claude Code (sau alt agent), care
|
||||||
|
configurează și folosește sistemul `foxbin2prg` ca să poată **căuta** cod-sursă aflat **în interiorul
|
||||||
|
fișierelor binare VFP** (`.vcx/.vct`, `.scx/.sct`, `.frx/.frt`, `.mnx/.mnt`, `.dbc/.dct`) într-un
|
||||||
|
proiect nou — și, pentru `.vcx`/`.scx`, să poată și **modifica** acel cod direct pe text, cu
|
||||||
|
write-back în binar (`txt2vcx.ps1`), fără trecere prin IDE-ul VFP.
|
||||||
|
|
||||||
|
Unealta e partajată și stă la `D:\ROA\UTIL\foxbin2prg\` — nu trebuie copiată per proiect. Doar
|
||||||
|
spui agentului care e proiectul curent (folderul + fișierul `.pjx`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cum se folosește
|
||||||
|
|
||||||
|
1. Deschide proiectul VFP în Claude Code.
|
||||||
|
2. Copiază blocul de mai jos în prompt.
|
||||||
|
3. Înlocuiește cele **3 valori** marcate `<<...>>` cu datele proiectului tău:
|
||||||
|
- `<<RADACINA_PROIECT>>` = folderul rădăcină al proiectului (ex. `D:\ROA\ROAGEST`)
|
||||||
|
- `<<FISIER_PJX>>` = calea către `.pjx` (ex. `D:\ROA\ROAGEST\roagest.pjx`)
|
||||||
|
- `<<NUME_PROIECT>>` = etichetă scurtă pentru folderul de cache (ex. `roagest`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ▼▼▼ PROMPT DE COPIAT ▼▼▼
|
||||||
|
|
||||||
|
```
|
||||||
|
Acest proiect este o aplicație Visual FoxPro 9. O mare parte din cod (proceduri, metode,
|
||||||
|
parametri) NU stă în fișiere .prg, ci în interiorul fișierelor binare VFP: librării de clase
|
||||||
|
(.vcx+.vct), forme (.scx+.sct), rapoarte (.frx+.frt), meniuri (.mnx+.mnt). Un grep direct pe
|
||||||
|
acestea nu e lizibil (sunt tabele DBF cu memo binar).
|
||||||
|
|
||||||
|
Ca să poți căuta în acel cod, folosește unealta partajată foxbin2prg, care convertește binarele
|
||||||
|
în reprezentarea lor TEXT (.vc2/.sc2/.fr2/.mn2/.dc2) într-un cache, și apoi caută în cache.
|
||||||
|
|
||||||
|
PARAMETRII PROIECTULUI CURENT:
|
||||||
|
- Rădăcină proiect: <<RADACINA_PROIECT>>
|
||||||
|
- Fișier proiect (.pjx): <<FISIER_PJX>>
|
||||||
|
- Cache text: D:\ROA\_vfp_textcache\<<NUME_PROIECT>>
|
||||||
|
|
||||||
|
REGULA DE AUR: cache-ul text e sursa pentru CĂUTARE (toate tipurile de binar) și, DOAR pentru
|
||||||
|
.vcx/.scx, și pentru EDITARE cu write-back (txt2vcx.ps1, vezi PAS 3). Pentru .frx/.mnx/.dbc
|
||||||
|
textul rămâne doar pentru citire — modificările la ele se fac în IDE-ul VFP, pe binar.
|
||||||
|
|
||||||
|
PAS 1 — populează cache-ul text (la începutul sesiunii, sau după ce s-a modificat ceva în IDE).
|
||||||
|
Rulează în PowerShell (e incremental — sare peste ce e deja la zi):
|
||||||
|
|
||||||
|
& 'D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1' -Project '<<FISIER_PJX>>' -ProjectRoot '<<RADACINA_PROIECT>>' -CacheRoot 'D:\ROA\_vfp_textcache\<<NUME_PROIECT>>'
|
||||||
|
|
||||||
|
(Folosește un -CacheRoot distinct per proiect ca să nu amesteci cache-urile. Implicit scriptul
|
||||||
|
convertește doar .vcx și .scx; pentru a include și rapoarte/meniuri adaugă: -Types vcx,scx,frx,mnx)
|
||||||
|
|
||||||
|
Dacă scriptul se plânge că lipsește FoxBin2Prg.EXE, trebuie compilat o singură dată: deschide
|
||||||
|
'D:\ROA\UTIL\foxbin2prg\foxbin2prg.pj2' în VFP 9 și Build > Build Executable. Anunță-mă să fac asta.
|
||||||
|
|
||||||
|
PAS 2 — caută în cache cu unealta Grep (NU în binarele originale):
|
||||||
|
|
||||||
|
Grep pe folderul 'D:\ROA\_vfp_textcache\<<NUME_PROIECT>>' pentru numele de metodă/procedură/variabilă căutat.
|
||||||
|
Ex.: caută "PROCEDURE do_salvare" sau numele tabelei/cursorului implicat.
|
||||||
|
|
||||||
|
CONVERSIE PUNCTUALĂ (un fișier sau folder care NU e în proiect, ex. o librărie din COMUN):
|
||||||
|
|
||||||
|
& 'D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1' -Source 'CALE\catre\librarie.vcx' -CacheRoot 'D:\ROA\_vfp_textcache\<<NUME_PROIECT>>'
|
||||||
|
|
||||||
|
ALTE OPȚIUNI: -Force (reconvertește tot), -Clean (șterge cache-ul întâi).
|
||||||
|
|
||||||
|
PAS 3 — modificare de cod în .vcx/.scx (flux write-back pe text). Ordinea e obligatorie:
|
||||||
|
|
||||||
|
a. Investighează pe cache, propune modificarea și AȘTEAPTĂ ACORDUL pe abordare înainte să
|
||||||
|
editezi ceva.
|
||||||
|
b. Refresh cache (PAS 1) dacă a rulat vreo sesiune de IDE de la ultimul refresh (staleness-ul
|
||||||
|
se verifică pe mtime și txt2vcx refuză corect scrierea peste modificări nevăzute). Înainte
|
||||||
|
de write-back închide COMPLET VFP IDE și exe-ul aplicației — procesele reziduale țin lock
|
||||||
|
pe binare.
|
||||||
|
c. Editează .vc2/.sc2 BYTE-SAFE: formatul e position-sensitive (nu reformata/reflow linii),
|
||||||
|
proprietățile sunt alfabetizate (una nouă se inserează la locul ei alfabetic). Headerul
|
||||||
|
CPID="1252" poate minți despre codepage-ul real al diacriticelor — citește/scrie fișierul
|
||||||
|
ca bytes (PowerShell), conținutul NOU doar ASCII; nu folosi unelte care re-encodează.
|
||||||
|
d. Write-back:
|
||||||
|
|
||||||
|
& 'D:\ROA\UTIL\foxbin2prg\txt2vcx.ps1' -TextFile 'D:\ROA\_vfp_textcache\<<NUME_PROIECT>>\Clase\lib.vc2' -ProjectRoot '<<RADACINA_PROIECT>>' -CacheRoot 'D:\ROA\_vfp_textcache\<<NUME_PROIECT>>'
|
||||||
|
|
||||||
|
Regenerează + compilează binarul în staging, verifică fidelitatea (roundtrip byte-identic
|
||||||
|
față de textul editat) și abia apoi copiază binarul în proiect; la orice eșec proiectul
|
||||||
|
rămâne neatins. Ținte sub COMUN\ cer flag-ul -AllowComun ȘI aprobare explicită — o
|
||||||
|
modificare acolo afectează toate aplicațiile ROA.
|
||||||
|
e. Diff lizibil pentru review: extrage binarul vechi din git (git show HEAD:cale > temp, atât
|
||||||
|
.vcx cât și .vct), regenerează-i textul cu vcx2txt.ps1 -Source într-un folder temporar și
|
||||||
|
compară cu cache-ul editat: git diff --no-index. COMMIT DOAR după ce omul confirmă pe diff
|
||||||
|
și testează în IDE.
|
||||||
|
|
||||||
|
EXCEPȚII: pentru .mnx (GENMENU) și .frx (fragile la round-trip) nu există write-back — scrie
|
||||||
|
propunerea de modificare într-un fișier docs/propuneri_<subiect>.md (cod + unde se inserează),
|
||||||
|
iar aplicarea se face manual în IDE-ul VFP.
|
||||||
|
|
||||||
|
FLUX TIPIC: la prima sesiune rulează PAS 1 o dată, apoi caută cu Grep în cache (PAS 2). La
|
||||||
|
sesiunile următoare rescrie doar ce s-a schimbat. Când citești codul unei metode, dă referințe
|
||||||
|
fișier:linie din cache.
|
||||||
|
```
|
||||||
|
|
||||||
|
## ▲▲▲ SFÂRȘIT PROMPT ▲▲▲
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Note pentru adaptare
|
||||||
|
|
||||||
|
- `<<NUME_PROIECT>>` îl poți pune orice etichetă scurtă (ex. `roagest`), doar ca numele
|
||||||
|
folderului de cache să fie unic per proiect.
|
||||||
|
- Ține cache-ul în `D:\ROA\_vfp_textcache\`, adică **în afara** folderului `foxbin2prg`. Motiv:
|
||||||
|
`ReCreate_FoxBin2Prg.prg` face `Prg2Bin` recursiv pe tot folderul tool-ului și ar reconverti
|
||||||
|
`.??2`-urile din orice cache aflat sub el înapoi în binare parazitare.
|
||||||
|
- Scriptul citește lista de fișiere direct din `.pjx`, deci convertește **doar** ce folosește
|
||||||
|
efectiv proiectul (rapid), nu tot `COMUN\`.
|
||||||
|
- Cache-ul (`D:\ROA\_vfp_textcache\<proiect>`) e regenerabil și e în afara arborelui de versionare
|
||||||
|
al proiectului (și în afara clonei foxbin2prg) — poate fi șters oricând.
|
||||||
|
- Conversia NU necesită IDE-ul VFP deschis (folosește `FoxBin2Prg.EXE` compilat).
|
||||||
|
- Fără backup-uri `.bak` lângă binarele din proiect — git e mecanismul de restore; `txt2vcx.ps1`
|
||||||
|
ține o copie de siguranță doar în folderul temp al rulării.
|
||||||
|
- Editările de volum se pot delega la subagenți (Sonnet, în background) — sesiunea principală
|
||||||
|
orchestrează, verifică diff/fidelity și intervine doar la deblocări și decizii.
|
||||||
|
- Detalii complete despre write-back (guard-uri, encoding, dialoguri VFP modale la text corupt,
|
||||||
|
testul de regresie `test_roundtrip.ps1`): `D:\ROA\UTIL\foxbin2prg\CLAUDE.md`.
|
||||||
|
- Exemplu funcțional, rodat pe ROAGEST: `D:\ROA\ROAGEST\docs\flux-editare-vfp-text.md` și
|
||||||
|
`D:\ROA\ROAGEST\docs\CLAUDE.md` (modul de lucru complet, cu review pe diff înainte de commit).
|
||||||
|
- Ghid complet doar-căutare (varianta ROAACNPRO, cu exemple): `D:\ROA\ROAACNPRO\docs\cautare_vcx_vct.md`.
|
||||||
|
- Tabel corespondență binar → text: `.vcx`→`.vc2`, `.scx`→`.sc2`, `.frx`→`.fr2`, `.mnx`→`.mn2`,
|
||||||
|
`.dbc`→`.dc2`, `.lbx`→`.lb2`.
|
||||||
331
git_sync.ps1
Normal file
331
git_sync.ps1
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Sincronizeaza in ARBORE (langa binare) versiunile text FoxBin2Prg ale binarelor VFP
|
||||||
|
ale unui proiect ROA, pentru ca git sa urmareasca text lizibil in loc de binare.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Folder-mode recursiv pe TOT arborele proiectului (inclusiv COMUN\), NU project-mode din .pjx:
|
||||||
|
altfel binarele nereferentiate in .pjx ar disparea din git. Pentru fiecare binar suportat
|
||||||
|
genereaza fisierul text (.vc2/.sc2/.fr2/.mn2/.lb2/.pj2/.dc2/.db2) IN ACELASI FOLDER cu binarul.
|
||||||
|
|
||||||
|
Tipuri convertite: vcx, scx, frx, mnx, lbx, pjx, dbc (recursiv) + dbf-urile dintr-o lista
|
||||||
|
explicita per proiect (-DbfList). Incremental: sare peste text-ul deja la zi (mtime binar vs text).
|
||||||
|
|
||||||
|
CONVERSIE IN STAGING TEMP (nu in arbore): FoxBin2Prg.EXE lasa artefacte (.err, _*.BAK) si
|
||||||
|
vcx2txt sterge copia binara la final - cu destinatia = arborele ar sterge binarul din working
|
||||||
|
copy-ul SVN. Aici copiem binar+memo (+.dbf.cfg) intr-un temp, convertim acolo, si copiem inapoi
|
||||||
|
DOAR textul (raw bytes, byte-preserving - encoding real cp1250 sub header CPID=1252, nu decodam).
|
||||||
|
Binarele din arbore NU sunt atinse niciodata.
|
||||||
|
|
||||||
|
Fidelitate per tip:
|
||||||
|
- vcx/scx: roundtrip complet (bin->text->bin->text, comparare pe octeti) - au write-back.
|
||||||
|
- frx/mnx/lbx/pjx/dbc/dbf: doar bin->text cu exit-code + timeout + text nevid (write-back nesuportat).
|
||||||
|
|
||||||
|
Esec partial: la esecul unui fisier continua cu restul, colecteaza esecurile, le raporteaza la
|
||||||
|
final si iese cu exit code nenul.
|
||||||
|
|
||||||
|
Curatare orfani: textele .??2 fara binar corespondent (binar sters din SVN) si .db2-urile ale
|
||||||
|
caror tabele au IESIT din lista -DbfList sunt DOAR RAPORTATE implicit; se sterg numai cu
|
||||||
|
-OrphanCleanup explicit. Textele din -OrphanExempt sunt sarite de la verificare (text fara
|
||||||
|
binar prin design).
|
||||||
|
|
||||||
|
Toate invocarile FoxBin2Prg.EXE trec prin Invoke-FoxBin2PrgSafe (timeout + taskkill).
|
||||||
|
|
||||||
|
.PARAMETER ProjectRoot
|
||||||
|
Radacina proiectului (implicit D:\ROA\ROACONT).
|
||||||
|
|
||||||
|
.PARAMETER Types
|
||||||
|
Tipurile binare (fara dbf) convertite recursiv (implicit vcx,scx,frx,mnx,lbx,pjx,dbc).
|
||||||
|
|
||||||
|
.PARAMETER DbfList
|
||||||
|
Lista de pattern-uri relative la ProjectRoot pentru dbf-urile convertite in .db2 cu DATE
|
||||||
|
(implicit lista ROACONT: Locale\locale*.dbf, Help\*.dbf, DATE\Optiuni_*.dbf).
|
||||||
|
|
||||||
|
.PARAMETER SubDir
|
||||||
|
Optional. Limiteaza rularea la un subfolder (relativ la ProjectRoot) - util pentru rulari in pasi.
|
||||||
|
Curatarea de orfani se face doar cand se ruleaza pe tot arborele (SubDir gol).
|
||||||
|
|
||||||
|
.PARAMETER TimeoutSeconds
|
||||||
|
Timeout per invocare FoxBin2Prg (implicit 60s).
|
||||||
|
|
||||||
|
.PARAMETER MaxDb2Mb
|
||||||
|
Prag: un .db2 peste aceasta dimensiune e raportat (tabela ar trebui scoasa din lista). Implicit 5.
|
||||||
|
|
||||||
|
.PARAMETER Force
|
||||||
|
Reconverteste chiar daca textul e la zi.
|
||||||
|
|
||||||
|
.PARAMETER NoOrphanCleanup
|
||||||
|
Pastrat pentru compatibilitate: forteaza doar-raportare chiar si cu -OrphanCleanup.
|
||||||
|
|
||||||
|
.PARAMETER OrphanCleanup
|
||||||
|
Sterge efectiv textele orfane. Implicit orfanii sunt DOAR raportati, nu stersi.
|
||||||
|
|
||||||
|
.PARAMETER OrphanExempt
|
||||||
|
Pattern-uri (-like, relative la ProjectRoot) de texte .??2 care NU sunt orfani chiar fara binar
|
||||||
|
langa ele: binarul e gitignored si se genereaza local din text (txt2vcx), textul e sursa.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\git_sync.ps1 -ProjectRoot D:\ROA\ROACONT
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$ProjectRoot = 'D:\ROA\ROACONT',
|
||||||
|
[string[]]$Types = @('vcx','scx','frx','mnx','lbx','pjx','dbc'),
|
||||||
|
[string[]]$DbfList = @('Locale\locale*.dbf','Help\*.dbf','DATE\Optiuni_*.dbf',
|
||||||
|
'COMUN\clase\Locale\*.dbf','COMUN\datemenu\*.dbf','COMUN\datemenu\xold\*.dbf',
|
||||||
|
'COMUN\Drepturi utilizatori\*\*.dbf','COMUN\utile\GridExtras\gridextras.dbf',
|
||||||
|
'COMUN\utile\ListProperty\lproperty.dbf','COMUN\utile\hpdf\*.dbf','COMUN\utile\hpdf\ReportOutput\*.dbf',
|
||||||
|
# acelasi set si fara prefixul COMUN\, pentru rulari cu -ProjectRoot chiar pe radacina COMUN
|
||||||
|
'clase\Locale\*.dbf','datemenu\*.dbf','datemenu\xold\*.dbf',
|
||||||
|
'Drepturi utilizatori\*\*.dbf','utile\GridExtras\gridextras.dbf',
|
||||||
|
'utile\ListProperty\lproperty.dbf','utile\hpdf\*.dbf','utile\hpdf\ReportOutput\*.dbf'),
|
||||||
|
# clase FFC/third-party cu #INCLUDE: roundtrip izolat = fals-negativ; editare doar in IDE, fara write-back
|
||||||
|
[string[]]$RoundtripExempt = @('COMUN\clase\accessibility.vcx','COMUN\clase\oinventar.vcx','COMUN\clase\_gdiplus.vcx','COMUN\clase\_reportlistener.vcx','COMUN\utile\foxcharts\foxcharts.vcx','COMUN\utile\web\_webview.vcx'),
|
||||||
|
# texte a caror sursa de adevar e textul: binarul e gitignored si se genereaza local, nu sunt orfani
|
||||||
|
[string[]]$OrphanExempt = @('*utile\Teste\achizitie_import\stub_verificare\verificare.sc2'),
|
||||||
|
[string]$SubDir = '',
|
||||||
|
[int]$TimeoutSeconds = 60,
|
||||||
|
[int]$MaxDb2Mb = 5,
|
||||||
|
[switch]$Force,
|
||||||
|
[switch]$NoOrphanCleanup,
|
||||||
|
[switch]$OrphanCleanup
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$exe = 'D:\ROA\UTIL\foxbin2prg\FoxBin2Prg.EXE'
|
||||||
|
if (-not (Test-Path $exe)) {
|
||||||
|
throw "Nu gasesc $exe. Compileaza intai FoxBin2Prg (deschide foxbin2prg.pj2 in VFP si Build)."
|
||||||
|
}
|
||||||
|
if (-not (Test-Path -LiteralPath $ProjectRoot -PathType Container)) {
|
||||||
|
throw "ProjectRoot nu exista: $ProjectRoot"
|
||||||
|
}
|
||||||
|
. (Join-Path $PSScriptRoot 'vfp_filemap.ps1')
|
||||||
|
$map = $script:VfpFileMap
|
||||||
|
$ProjectRoot = (Get-Item -LiteralPath $ProjectRoot).FullName
|
||||||
|
|
||||||
|
# extensiile text valide (pt curatare orfani), lowercase
|
||||||
|
$script:TextExts = @{}
|
||||||
|
foreach ($k in $map.Keys) { $script:TextExts[$map[$k][1]] = $k } # .vc2 -> .vcx
|
||||||
|
|
||||||
|
# set cai absolute (lowercase) ale binarelor scutite de garda roundtrip - doar bin->text valid
|
||||||
|
$script:ExemptSet = New-Object System.Collections.Generic.HashSet[string]
|
||||||
|
foreach ($rel in $RoundtripExempt) { [void]$script:ExemptSet.Add((Join-Path $ProjectRoot $rel).ToLower()) }
|
||||||
|
|
||||||
|
$runStamp = "{0}-{1}" -f $PID, (Get-Date -Format 'yyyyMMddHHmmssfff')
|
||||||
|
$stagingRoot = Join-Path $env:TEMP ("git_sync_" + (Split-Path $ProjectRoot -Leaf) + "\$runStamp")
|
||||||
|
|
||||||
|
function Invoke-Fb2p {
|
||||||
|
# toate apelurile trec prin watchdog (timeout + taskkill) - vezi vfp_filemap.ps1
|
||||||
|
param([string]$InputFile, [string]$Recompile = '')
|
||||||
|
if ($Recompile) {
|
||||||
|
$r = Invoke-FoxBin2PrgSafe -ExePath $exe -ExeArgs @($InputFile,'','','','1','0','1','',$Recompile) -TimeoutSeconds $TimeoutSeconds
|
||||||
|
} else {
|
||||||
|
$r = Invoke-FoxBin2PrgSafe -ExePath $exe -ExeArgs @($InputFile,'','','','1','0','1') -TimeoutSeconds $TimeoutSeconds
|
||||||
|
}
|
||||||
|
return $r
|
||||||
|
}
|
||||||
|
|
||||||
|
function Find-InDir([string]$dir, [string]$name) {
|
||||||
|
# gaseste un fisier dupa nume (case-insensitive) intr-un folder; $null daca lipseste
|
||||||
|
if (-not (Test-Path -LiteralPath $dir -PathType Container)) { return $null }
|
||||||
|
return (Get-ChildItem -LiteralPath $dir -Filter $name -File -ErrorAction SilentlyContinue | Select-Object -First 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-BytesEqual([byte[]]$a, [byte[]]$b) {
|
||||||
|
# normalizeaza CRLF/CR->LF si compara ca string Latin1 (byte<->char lossless) - rapid pe fisiere mari;
|
||||||
|
# bucla per-octet in PowerShell era patologic de lenta (minute/fisier) pe textele de sute de KB.
|
||||||
|
$enc = [System.Text.Encoding]::GetEncoding(28591)
|
||||||
|
$sa = $enc.GetString($a) -replace "`r`n?", "`n"
|
||||||
|
$sb = $enc.GetString($b) -replace "`r`n?", "`n"
|
||||||
|
return $sa -eq $sb
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Converteste un binar in staging si copiaza inapoi DOAR textul. Intoarce status string. ---
|
||||||
|
function Convert-One {
|
||||||
|
param([Parameter(Mandatory)][System.IO.FileInfo]$Bin)
|
||||||
|
|
||||||
|
$ext = $Bin.Extension.ToLower()
|
||||||
|
$memoX = $map[$ext][0]
|
||||||
|
$txtX = $map[$ext][1]
|
||||||
|
$dir = $Bin.DirectoryName
|
||||||
|
$base = [IO.Path]::GetFileNameWithoutExtension($Bin.Name)
|
||||||
|
$memo = Find-InDir $dir ($base + $memoX)
|
||||||
|
|
||||||
|
# text tinta existent (case-insensitive) pentru incremental + destinatie
|
||||||
|
$txtExisting = Find-InDir $dir ($base + $txtX)
|
||||||
|
if (-not $Force -and $txtExisting) {
|
||||||
|
$newestBin = $Bin.LastWriteTime
|
||||||
|
if ($memo -and $memo.LastWriteTime -gt $newestBin) { $newestBin = $memo.LastWriteTime }
|
||||||
|
if ($txtExisting.LastWriteTime -ge $newestBin) { return 'uptodate' }
|
||||||
|
}
|
||||||
|
|
||||||
|
$stageDir = Join-Path $stagingRoot ([Guid]::NewGuid().ToString('N').Substring(0,12))
|
||||||
|
New-Item -ItemType Directory -Force -Path $stageDir | Out-Null
|
||||||
|
try {
|
||||||
|
# cfg in staging (mostenit si de subfolderul rc): DontShowErrors suprima MessageBox-ul propriu
|
||||||
|
# "End of Process (with errors)"; ShowProgressbar 0 = fara fereastra de progres (rulare headless).
|
||||||
|
$cfgLines = [System.Collections.Generic.List[string]]@('DontShowErrors: 1','ShowProgressbar: 0')
|
||||||
|
if ($ext -eq '.dbf') { $cfgLines.Add('DBF_Conversion_Support: 4') } # forteaza exportul DATELOR
|
||||||
|
Set-Content -LiteralPath (Join-Path $stageDir 'foxbin2prg.cfg') -Value $cfgLines -Encoding Ascii
|
||||||
|
|
||||||
|
$stageBin = Join-Path $stageDir $Bin.Name
|
||||||
|
Copy-Item -LiteralPath $Bin.FullName -Destination $stageBin -Force
|
||||||
|
if ($memo) { Copy-Item -LiteralPath $memo.FullName -Destination (Join-Path $stageDir $memo.Name) -Force }
|
||||||
|
|
||||||
|
if ($ext -eq '.dbc') {
|
||||||
|
# dbc = tabela cu index structural .dcx; fara el FoxBin2Prg cere indexul intr-un dialog modal (hang)
|
||||||
|
$dcx = Find-InDir $dir ($base + '.dcx')
|
||||||
|
if ($dcx) { Copy-Item -LiteralPath $dcx.FullName -Destination (Join-Path $stageDir $dcx.Name) -Force }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ext -eq '.dbf') {
|
||||||
|
# index structural + (daca e tabela legata de DBC) containerul: FoxBin2Prg le cere ca sa deschida tabela
|
||||||
|
$cdx = Find-InDir $dir ($base + '.cdx')
|
||||||
|
if ($cdx) { Copy-Item -LiteralPath $cdx.FullName -Destination (Join-Path $stageDir $cdx.Name) -Force }
|
||||||
|
Get-ChildItem -LiteralPath $dir -File -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { '.dbc','.dct','.dcx' -contains $_.Extension.ToLower() } |
|
||||||
|
ForEach-Object { Copy-Item -LiteralPath $_.FullName -Destination (Join-Path $stageDir $_.Name) -Force }
|
||||||
|
# onoreaza order/condition custom din .dbf.cfg comis, daca exista
|
||||||
|
$cfg = Find-InDir $dir ($base + '.dbf.cfg')
|
||||||
|
if ($cfg) { Copy-Item -LiteralPath $cfg.FullName -Destination (Join-Path $stageDir $cfg.Name) -Force }
|
||||||
|
}
|
||||||
|
|
||||||
|
$r = Invoke-Fb2p -InputFile $stageBin
|
||||||
|
if ($r.TimedOut) { return 'fail:timeout bin->text' }
|
||||||
|
if ($r.ExitCode -ne 0) { return "fail:bin->text ErrorLevel=$($r.ExitCode)" }
|
||||||
|
|
||||||
|
$stageTxt = Find-InDir $stageDir ($base + $txtX)
|
||||||
|
if (-not $stageTxt) { return 'fail:text negenerat' }
|
||||||
|
if ($stageTxt.Length -eq 0) { return 'fail:text gol' }
|
||||||
|
|
||||||
|
# scutite (clase FFC cu #INCLUDE): roundtrip izolat = fals-negativ; doar bin->text valid, fara write-back
|
||||||
|
$exempt = $script:ExemptSet.Contains($Bin.FullName.ToLower())
|
||||||
|
|
||||||
|
# fidelitate per tip: roundtrip complet doar vcx/scx (mai putin cele scutite)
|
||||||
|
if (($ext -eq '.vcx' -or $ext -eq '.scx') -and -not $exempt) {
|
||||||
|
$rc2 = Join-Path $stageDir 'rc'
|
||||||
|
New-Item -ItemType Directory -Force -Path $rc2 | Out-Null
|
||||||
|
$txt1 = Join-Path $rc2 $stageTxt.Name
|
||||||
|
Copy-Item -LiteralPath $stageTxt.FullName -Destination $txt1 -Force
|
||||||
|
$r2 = Invoke-Fb2p -InputFile $txt1 -Recompile $ProjectRoot
|
||||||
|
if ($r2.TimedOut -or $r2.ExitCode -ne 0) { return "fail:roundtrip text->bin ErrorLevel=$($r2.ExitCode)" }
|
||||||
|
$bin2 = Find-InDir $rc2 ($base + $ext)
|
||||||
|
if (-not $bin2) { return 'fail:roundtrip bin nerecreat' }
|
||||||
|
$r3 = Invoke-Fb2p -InputFile $bin2.FullName
|
||||||
|
if ($r3.TimedOut -or $r3.ExitCode -ne 0) { return "fail:roundtrip bin->text2 ErrorLevel=$($r3.ExitCode)" }
|
||||||
|
$txt2 = Find-InDir $rc2 ($base + $txtX)
|
||||||
|
if (-not $txt2) { return 'fail:roundtrip text2 negenerat' }
|
||||||
|
if (-not (Test-BytesEqual ([IO.File]::ReadAllBytes($stageTxt.FullName)) ([IO.File]::ReadAllBytes($txt2.FullName)))) {
|
||||||
|
return 'fail:roundtrip text1 != text2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ext -eq '.dbf' -and $stageTxt.Length -gt ($MaxDb2Mb * 1MB)) {
|
||||||
|
$script:BigDb2 += ("{0} ({1:N1} MB)" -f (Get-RelPath $Bin.FullName), ($stageTxt.Length / 1MB))
|
||||||
|
}
|
||||||
|
|
||||||
|
# copiaza inapoi DOAR textul (raw bytes), pastrand numele produs de FoxBin2Prg
|
||||||
|
$destTxt = if ($txtExisting) { $txtExisting.FullName } else { Join-Path $dir $stageTxt.Name }
|
||||||
|
Copy-Item -LiteralPath $stageTxt.FullName -Destination $destTxt -Force
|
||||||
|
# mtime text >= mtime binar, altfel pare stale si se reconverteste mereu
|
||||||
|
$destItem = Get-Item -LiteralPath $destTxt
|
||||||
|
$newestBin = $Bin.LastWriteTime
|
||||||
|
if ($memo -and $memo.LastWriteTime -gt $newestBin) { $newestBin = $memo.LastWriteTime }
|
||||||
|
if ($destItem.LastWriteTime -lt $newestBin) { $destItem.LastWriteTime = $newestBin.AddSeconds(1) }
|
||||||
|
if ($exempt) { return 'ok-exempt' }
|
||||||
|
return 'ok'
|
||||||
|
} finally {
|
||||||
|
Remove-Item -Recurse -Force -LiteralPath $stageDir -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-RelPath([string]$full) {
|
||||||
|
if ($full.ToLower().StartsWith($ProjectRoot.ToLower())) { return $full.Substring($ProjectRoot.Length).TrimStart('\') }
|
||||||
|
return $full
|
||||||
|
}
|
||||||
|
|
||||||
|
# =========================== MAIN ===========================
|
||||||
|
$scanRoot = if ($SubDir) { Join-Path $ProjectRoot $SubDir } else { $ProjectRoot }
|
||||||
|
if (-not (Test-Path -LiteralPath $scanRoot)) { throw "SubDir nu exista: $scanRoot" }
|
||||||
|
|
||||||
|
Write-Host "=== git_sync: ProjectRoot=$ProjectRoot SubDir=$(if($SubDir){$SubDir}else{'(tot)'}) Types=$($Types -join ',') ==="
|
||||||
|
|
||||||
|
# --- lista dbf din patternurile per proiect (cai absolute, set pt orfani) ---
|
||||||
|
$script:DbfInList = New-Object System.Collections.Generic.HashSet[string]
|
||||||
|
foreach ($pat in $DbfList) {
|
||||||
|
$full = Join-Path $ProjectRoot $pat
|
||||||
|
Get-ChildItem -Path $full -File -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.Extension.ToLower() -eq '.dbf' } |
|
||||||
|
ForEach-Object { [void]$script:DbfInList.Add($_.FullName.ToLower()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- construieste lista de binare de convertit ---
|
||||||
|
$script:BigDb2 = @()
|
||||||
|
$binList = @()
|
||||||
|
$typeExts = $Types | ForEach-Object { '.' + $_.ToLower() }
|
||||||
|
$binList += Get-ChildItem -LiteralPath $scanRoot -Recurse -File -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $typeExts -contains $_.Extension.ToLower() }
|
||||||
|
# dbf-urile din lista aflate sub scanRoot
|
||||||
|
$binList += $script:DbfInList | ForEach-Object { Get-Item -LiteralPath $_ -ErrorAction SilentlyContinue } |
|
||||||
|
Where-Object { $_ -and $_.FullName.ToLower().StartsWith(((Get-Item -LiteralPath $scanRoot).FullName).ToLower()) }
|
||||||
|
|
||||||
|
$ok=0; $up=0; $failures=@()
|
||||||
|
foreach ($b in $binList) {
|
||||||
|
$status = Convert-One -Bin $b
|
||||||
|
$rel = Get-RelPath $b.FullName
|
||||||
|
switch -Wildcard ($status) {
|
||||||
|
'ok' { $ok++; Write-Host "OK $rel" }
|
||||||
|
'ok-exempt' { $ok++; Write-Host "OK $rel (roundtrip scutit)" }
|
||||||
|
'uptodate' { $up++ }
|
||||||
|
'fail:*' { $failures += [PSCustomObject]@{ File=$rel; Reason=$status.Substring(5) }; Write-Warning "ESEC $rel -- $($status.Substring(5))" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- curatare orfani (doar pe tot arborele) ---
|
||||||
|
$orphansDeleted = @()
|
||||||
|
if (-not $SubDir) {
|
||||||
|
$allText = Get-ChildItem -LiteralPath $ProjectRoot -Recurse -File -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $script:TextExts.ContainsKey($_.Extension.ToLower()) }
|
||||||
|
foreach ($t in $allText) {
|
||||||
|
$tRel = Get-RelPath $t.FullName
|
||||||
|
if (@($OrphanExempt | Where-Object { $tRel -like $_ }).Count -gt 0) { continue }
|
||||||
|
$tExt = $t.Extension.ToLower()
|
||||||
|
$binExt = $script:TextExts[$tExt]
|
||||||
|
$base = [IO.Path]::GetFileNameWithoutExtension($t.Name)
|
||||||
|
$bin = Find-InDir $t.DirectoryName ($base + $binExt)
|
||||||
|
$isOrphan = $false
|
||||||
|
if (-not $bin) {
|
||||||
|
$isOrphan = $true # binar sters
|
||||||
|
} elseif ($tExt -eq '.db2' -and -not $script:DbfInList.Contains($bin.FullName.ToLower())) {
|
||||||
|
$isOrphan = $true # tabela a iesit din lista
|
||||||
|
}
|
||||||
|
if ($isOrphan) {
|
||||||
|
$orphansDeleted += $tRel
|
||||||
|
if ($OrphanCleanup -and -not $NoOrphanCleanup) { Remove-Item -LiteralPath $t.FullName -Force -ErrorAction SilentlyContinue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Remove-Item -Recurse -Force -LiteralPath $stagingRoot -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# --- raport ---
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host "--- Sumar git_sync ---"
|
||||||
|
Write-Host "Convertite: $ok Sarite(la zi): $up Esuate: $($failures.Count)"
|
||||||
|
if ($orphansDeleted.Count -gt 0) {
|
||||||
|
$verb = if ($OrphanCleanup -and -not $NoOrphanCleanup) { 'sterse' } else { 'raportate (nesterse)' }
|
||||||
|
Write-Host "Orfani ${verb}: $($orphansDeleted.Count)"
|
||||||
|
$orphansDeleted | ForEach-Object { Write-Host " orfan: $_" }
|
||||||
|
}
|
||||||
|
if ($script:BigDb2.Count -gt 0) {
|
||||||
|
Write-Warning "db2 peste ${MaxDb2Mb} MB (scoate tabela din lista, ramane binara):"
|
||||||
|
$script:BigDb2 | ForEach-Object { Write-Warning " $_" }
|
||||||
|
}
|
||||||
|
if ($failures.Count -gt 0) {
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host "ESECURI (nu comite pana nu sunt explicate):"
|
||||||
|
$failures | ForEach-Object { Write-Host " $($_.File) -- $($_.Reason)" }
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-Host "OK: fara esecuri."
|
||||||
|
exit 0
|
||||||
45
roa_sync.ps1
Normal file
45
roa_sync.ps1
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
STUB de compatibilitate: roa_sync.ps1 s-a MUTAT in COMUN\scripts\roa_sync.ps1
|
||||||
|
(se distribuie automat prin svn update, ca restul COMUN).
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Deleaga la copia proiectului: <ProjectRoot>\COMUN\scripts\roa_sync.ps1. Daca lipseste
|
||||||
|
(working copy neactualizat inca), face intai svn update pe proiect ca sa o aduca.
|
||||||
|
Actualizeaza lansatoarele (roa_sync.bat) sa cheme direct COMUN\scripts\roa_sync.ps1.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$ProjectRoot = '',
|
||||||
|
[switch]$GitOnly
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
if (-not $ProjectRoot) {
|
||||||
|
$cwd = (Get-Location).Path
|
||||||
|
$hasGit = Test-Path -LiteralPath (Join-Path $cwd '.git')
|
||||||
|
$hasPjx = @(Get-ChildItem -LiteralPath $cwd -Filter '*.pjx' -File -ErrorAction SilentlyContinue).Count -gt 0
|
||||||
|
if ($hasGit -and $hasPjx) {
|
||||||
|
$ProjectRoot = $cwd
|
||||||
|
} else {
|
||||||
|
Write-Host "EROARE: -ProjectRoot nedat si directorul curent nu pare radacina de proiect ROA (lipseste .git sau .pjx): $cwd"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$target = Join-Path $ProjectRoot 'COMUN\scripts\roa_sync.ps1'
|
||||||
|
if (-not (Test-Path -LiteralPath $target)) {
|
||||||
|
Write-Host "roa_sync.ps1 s-a mutat in COMUN\scripts si lipseste inca din working copy - rulez svn update ca sa il aduc."
|
||||||
|
& svn update $ProjectRoot
|
||||||
|
if (-not (Test-Path -LiteralPath $target)) {
|
||||||
|
Write-Host "EROARE: $target lipseste si dupa svn update. Verifica externalul COMUN."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "NOTA: roa_sync.ps1 s-a mutat in COMUN\scripts\. Actualizeaza lansatorul sa cheme direct: $target"
|
||||||
|
$extra = @()
|
||||||
|
if ($GitOnly) { $extra += '-GitOnly' }
|
||||||
|
& powershell -ExecutionPolicy Bypass -NoProfile -File $target -ProjectRoot $ProjectRoot @extra
|
||||||
|
exit $LASTEXITCODE
|
||||||
72
sync_romfast.bat
Normal file
72
sync_romfast.bat
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
@echo off
|
||||||
|
rem Sincronizeaza romfast cu upstream. Fara /push: doar local. Cu /push: trimite pe Gitea.
|
||||||
|
rem Se auto-copiaza in TEMP si ruleaza de acolo, ca sa nu dispara de pe disc
|
||||||
|
rem cand scriptul face "git checkout master" (fisierul exista doar pe romfast).
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
if not "%SYNC_ROMFAST_RELAUNCHED%"=="1" (
|
||||||
|
set "SYNC_ROMFAST_ORIGDIR=%~dp0"
|
||||||
|
set "SYNC_ROMFAST_RELAUNCHED=1"
|
||||||
|
set "SYNC_ROMFAST_TMPBAT=!TEMP!\sync_romfast_!RANDOM!.bat"
|
||||||
|
copy /y "%~f0" "!SYNC_ROMFAST_TMPBAT!" >nul
|
||||||
|
call "!SYNC_ROMFAST_TMPBAT!" %*
|
||||||
|
set "SYNC_ROMFAST_EC=!ERRORLEVEL!"
|
||||||
|
del "!SYNC_ROMFAST_TMPBAT!" >nul 2>&1
|
||||||
|
exit /b !SYNC_ROMFAST_EC!
|
||||||
|
)
|
||||||
|
cd /d "%SYNC_ROMFAST_ORIGDIR%"
|
||||||
|
|
||||||
|
for /f "delims=" %%u in ('git remote get-url origin') do set GITHUB=%%u
|
||||||
|
for /f "delims=" %%u in ('git remote get-url romfast') do set GITEA=%%u
|
||||||
|
|
||||||
|
set STASHED=0
|
||||||
|
set DIRTY=
|
||||||
|
for /f "delims=" %%s in ('git status --porcelain') do set DIRTY=1
|
||||||
|
if defined DIRTY (
|
||||||
|
echo Ai modificari necomise:
|
||||||
|
git status -s
|
||||||
|
set /p ANSWER="Ce fac cu ele? (c = commit, s = stash, n = opresc) [c/s/n] "
|
||||||
|
if /i "!ANSWER!"=="s" (
|
||||||
|
git stash push -u -m "sync_romfast auto-stash" -- . ":(exclude)sync_romfast.bat" ":(exclude)sync_romfast.ps1" || exit /b 1
|
||||||
|
set STASHED=1
|
||||||
|
) else if /i "!ANSWER!"=="c" (
|
||||||
|
set /p MSG="Mesaj de commit (Enter = mesaj implicit): "
|
||||||
|
if "!MSG!"=="" set MSG=Actualizari locale inainte de sincronizare
|
||||||
|
git add -A
|
||||||
|
git commit -m "!MSG!"
|
||||||
|
) else (
|
||||||
|
echo Oprit. Comite sau fa stash manual, apoi ruleaza din nou scriptul.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
echo GitHub (origin) %GITHUB% --^> master
|
||||||
|
echo Gitea (romfast) %GITEA% --^> romfast
|
||||||
|
|
||||||
|
git fetch origin || exit /b 1
|
||||||
|
git checkout master || exit /b 1
|
||||||
|
git merge --ff-only origin/master || (echo master a divergat de origin/master, verifica manual. & exit /b 1)
|
||||||
|
|
||||||
|
git checkout romfast || exit /b 1
|
||||||
|
git merge master || (echo Conflicte la merge master -^> romfast, rezolva manual ^(git status^). & exit /b 1)
|
||||||
|
|
||||||
|
git log --oneline -5
|
||||||
|
|
||||||
|
if "%STASHED%"=="1" (
|
||||||
|
echo Restaurez modificarile din stash...
|
||||||
|
git stash pop || (echo Conflicte la restaurarea stash-ului, rezolva manual ^(git stash list / git stash pop^). & exit /b 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
set DOPUSH=0
|
||||||
|
if /i "%~1"=="/push" set DOPUSH=1
|
||||||
|
if "%DOPUSH%"=="0" (
|
||||||
|
set /p ANSWER="Local la zi. Fac push acum catre Gitea (romfast)? (y/n) "
|
||||||
|
if /i "!ANSWER!"=="y" set DOPUSH=1
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%DOPUSH%"=="1" (
|
||||||
|
git push romfast master || exit /b 1
|
||||||
|
git push romfast romfast || exit /b 1
|
||||||
|
echo Push facut catre %GITEA%
|
||||||
|
) else (
|
||||||
|
echo Push sarit. Inainte de push: DO ReCreate_FoxBin2Prg.prg in VFP9, apoi ruleaza cu /push.
|
||||||
|
)
|
||||||
65
sync_romfast.ps1
Normal file
65
sync_romfast.ps1
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Sincronizeaza romfast cu upstream. Fara -Push: doar local. Cu -Push: trimite pe Gitea.
|
||||||
|
param([switch]$Push)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
Set-Location $PSScriptRoot
|
||||||
|
|
||||||
|
$GITHUB = git remote get-url origin
|
||||||
|
$GITEA = git remote get-url romfast
|
||||||
|
|
||||||
|
$stashed = $false
|
||||||
|
$dirty = git status --porcelain
|
||||||
|
if ($dirty) {
|
||||||
|
Write-Host "Ai modificari necomise:" -ForegroundColor Yellow
|
||||||
|
git status -s
|
||||||
|
$answer = Read-Host "Ce fac cu ele? (c = commit, s = stash, n = opresc) [c/s/n]"
|
||||||
|
switch ($answer) {
|
||||||
|
's' {
|
||||||
|
git stash push -u -m "sync_romfast auto-stash" -- . ":(exclude)sync_romfast.bat" ":(exclude)sync_romfast.ps1"
|
||||||
|
$stashed = $true
|
||||||
|
}
|
||||||
|
'c' {
|
||||||
|
$msg = Read-Host "Mesaj de commit (Enter = mesaj implicit)"
|
||||||
|
if (-not $msg) { $msg = "Actualizari locale inainte de sincronizare" }
|
||||||
|
git add -A
|
||||||
|
git commit -m $msg
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
Write-Host "Oprit. Comite sau fa stash manual, apoi ruleaza din nou scriptul." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "GitHub (origin) $GITHUB --> master" -ForegroundColor Cyan
|
||||||
|
Write-Host "Gitea (romfast) $GITEA --> romfast" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
git fetch origin
|
||||||
|
git checkout master
|
||||||
|
git merge --ff-only origin/master
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "master a divergat de origin/master, verifica manual." }
|
||||||
|
|
||||||
|
git checkout romfast
|
||||||
|
git merge master
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Conflicte la merge master -> romfast, rezolva manual (git status)." }
|
||||||
|
|
||||||
|
git log --oneline -5
|
||||||
|
|
||||||
|
if ($stashed) {
|
||||||
|
Write-Host "Restaurez modificarile din stash..." -ForegroundColor Cyan
|
||||||
|
git stash pop
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Conflicte la restaurarea stash-ului, rezolva manual (git stash list / git stash pop)." }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $Push) {
|
||||||
|
$answer = Read-Host "Local la zi. Fac push acum catre Gitea (romfast)? (y/n)"
|
||||||
|
if ($answer -eq 'y') { $Push = $true }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Push) {
|
||||||
|
git push romfast master
|
||||||
|
git push romfast romfast
|
||||||
|
Write-Host "Push facut catre $GITEA" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Push sarit. Inainte de push: DO ReCreate_FoxBin2Prg.prg in VFP9, apoi ruleaza cu -Push." -ForegroundColor Yellow
|
||||||
|
}
|
||||||
256
test_git_sync.ps1
Normal file
256
test_git_sync.ps1
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Teste standalone (fara Pester) pentru git_sync.ps1, pe fixture-uri construite in TEMP.
|
||||||
|
Acopera cele 4 comportamente noi cerute de plan (Sectiunea 8, decizie eng-review D2):
|
||||||
|
(A) incremental - a doua rulare pe fixture neschimbat nu reconverteste nimic (Convertite=0, Sarite>0);
|
||||||
|
(B) curatare orfani - un .vc2 fara binar se sterge si se raporteaza; un .db2 a carui
|
||||||
|
tabela a iesit din -DbfList se sterge si se raporteaza;
|
||||||
|
(C) esec partial - un .vcx corupt produce exit nenul si apare in esecuri, DAR fisierele
|
||||||
|
valide din aceeasi rulare se convertesc totusi;
|
||||||
|
(D) protectia binarului - dupa orice rulare, toate binarele fixture-ului sunt byte-identice
|
||||||
|
(SHA256 inainte/dupa). Verificat si dupa fiecare rulare din A/B/C.
|
||||||
|
(E) scutire roundtrip - fisierul din -RoundtripExempt produce text fara garda de roundtrip;
|
||||||
|
(F) scutire orfani - textul din -OrphanExempt nu e raportat ca orfan, restul da.
|
||||||
|
|
||||||
|
Fixture: copie a celui mai mic .vcx+.vct real din Clase si a setului Locale (dbf+memo+indecsi+
|
||||||
|
container dbc) din proiectul sursa. NU se scrie nimic in proiectul sursa - doar se citeste/copiaza.
|
||||||
|
Se copiaza DOAR binarele si memo-urile, niciodata textul .??2 deja prezent in sursa.
|
||||||
|
|
||||||
|
.PARAMETER SourceProject
|
||||||
|
Proiectul din care se copiaza esantioanele de binare (implicit ROACONT).
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$SourceProject = 'D:\ROA\ROACONT'
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$script:GitSync = 'D:\ROA\UTIL\foxbin2prg\git_sync.ps1'
|
||||||
|
$script:Failures = @()
|
||||||
|
# extensiile considerate "binar" in fixture (tot ce NU e text .??2 generat)
|
||||||
|
$script:BinExts = @('.vcx','.vct','.dbf','.fpt','.cdx','.dbc','.dct','.dcx')
|
||||||
|
|
||||||
|
function Assert([bool]$cond, [string]$msg) {
|
||||||
|
if ($cond) { Write-Host " PASS: $msg" -ForegroundColor Green }
|
||||||
|
else { Write-Host " FAIL: $msg" -ForegroundColor Red; $script:Failures += $msg }
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Ruleaza git_sync.ps1 ca proces copil si parseaza sumarul + orfanii + exit code ---
|
||||||
|
function Invoke-GitSync {
|
||||||
|
param([string]$Fixture, [string[]]$DbfList, [string[]]$Exempt, [string[]]$OrphanExempt, [int]$TimeoutSeconds = 30)
|
||||||
|
# -Command (nu -File): powershell.exe -File colapseaza 'a','b' intr-un singur token, DbfList ar fi gol
|
||||||
|
$dbfLit = if ($DbfList -and $DbfList.Count) { '@(' + (($DbfList | ForEach-Object { "'" + $_ + "'" }) -join ',') + ')' } else { '@()' }
|
||||||
|
$exLit = if ($Exempt -and $Exempt.Count) { '@(' + (($Exempt | ForEach-Object { "'" + $_ + "'" }) -join ',') + ')' } else { '@()' }
|
||||||
|
$cmd = "& '$script:GitSync' -ProjectRoot '$Fixture' -DbfList $dbfLit -RoundtripExempt $exLit -TimeoutSeconds $TimeoutSeconds"
|
||||||
|
# fara -OrphanExempt explicit ramane valoarea implicita din git_sync.ps1 (testul F o verifica)
|
||||||
|
if ($OrphanExempt) {
|
||||||
|
$cmd += ' -OrphanExempt @(' + (($OrphanExempt | ForEach-Object { "'" + $_ + "'" }) -join ',') + ')'
|
||||||
|
}
|
||||||
|
# EAP=Continue local: git_sync ruleaza FoxBin2Prg care scrie pe stderr (ex. 'not a table');
|
||||||
|
# sub EAP=Stop, capturarea 2>&1 a acelui stderr ar arunca NativeCommandError si ar opri testul.
|
||||||
|
$prevEAP = $ErrorActionPreference
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
$sw = [Diagnostics.Stopwatch]::StartNew()
|
||||||
|
$raw = & powershell -ExecutionPolicy Bypass -Command $cmd 2>&1 | Out-String
|
||||||
|
$exit = $LASTEXITCODE
|
||||||
|
$sw.Stop()
|
||||||
|
$ErrorActionPreference = $prevEAP
|
||||||
|
$conv = $null; $skip = $null; $fail = $null
|
||||||
|
$m = [regex]::Match($raw, 'Convertite:\s*(\d+)\s+Sarite\(la zi\):\s*(\d+)\s+Esuate:\s*(\d+)')
|
||||||
|
if ($m.Success) { $conv = [int]$m.Groups[1].Value; $skip = [int]$m.Groups[2].Value; $fail = [int]$m.Groups[3].Value }
|
||||||
|
$orphans = @()
|
||||||
|
foreach ($line in ($raw -split "`r?`n")) {
|
||||||
|
$om = [regex]::Match($line, '^\s*orfan:\s*(.+?)\s*$')
|
||||||
|
if ($om.Success) { $orphans += $om.Groups[1].Value }
|
||||||
|
}
|
||||||
|
return [PSCustomObject]@{ Exit=$exit; Convertite=$conv; Sarite=$skip; Esuate=$fail; Orphans=$orphans; Raw=$raw; ElapsedSec=[math]::Round($sw.Elapsed.TotalSeconds,1); TimeoutSec=$TimeoutSeconds }
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Snapshot SHA256 al tuturor binarelor din fixture (pt protectia binarului) ---
|
||||||
|
function Get-BinSnapshot([string]$root) {
|
||||||
|
$h = @{}
|
||||||
|
Get-ChildItem -LiteralPath $root -Recurse -File -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $script:BinExts -contains $_.Extension.ToLower() } |
|
||||||
|
ForEach-Object { $h[$_.FullName.ToLower()] = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash }
|
||||||
|
return $h
|
||||||
|
}
|
||||||
|
|
||||||
|
function Assert-BinariesUnchanged([hashtable]$before, [string]$root, [string]$ctx) {
|
||||||
|
$after = Get-BinSnapshot $root
|
||||||
|
$why = ''
|
||||||
|
if ($before.Count -ne $after.Count) { $why = "numar binare $($before.Count)->$($after.Count)" }
|
||||||
|
else {
|
||||||
|
foreach ($k in $before.Keys) {
|
||||||
|
if (-not $after.ContainsKey($k)) { $why = "lipsa dupa rulare: $k"; break }
|
||||||
|
if ($after[$k] -ne $before[$k]) { $why = "modificat: $k"; break }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert ($why -eq '') "${ctx}: toate binarele byte-identice dupa rulare$(if($why){" ($why)"})"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Construieste un fixture in TEMP: atentie.vcx+.vct + setul Locale (fara text .??2) ---
|
||||||
|
function New-Fixture([string]$tag) {
|
||||||
|
$fx = Join-Path $env:TEMP ("gs_test_{0}_{1}_{2}" -f $tag, $PID, ([Guid]::NewGuid().ToString('N').Substring(0,8)))
|
||||||
|
New-Item -ItemType Directory -Force -Path (Join-Path $fx 'Clase'), (Join-Path $fx 'Locale') | Out-Null
|
||||||
|
# doar .vcx + .vct (memo); NU .vc2 care exista deja in sursa dintr-o rulare anterioara
|
||||||
|
Get-ChildItem (Join-Path $SourceProject 'Clase') -Filter 'atentie.*' -File |
|
||||||
|
Where-Object { @('.vcx','.vct') -contains $_.Extension.ToLower() } |
|
||||||
|
ForEach-Object { Copy-Item -LiteralPath $_.FullName -Destination (Join-Path $fx 'Clase') -Force }
|
||||||
|
# set Locale: doua tabele (pt scenariul de scoatere din lista) + indecsi + container dbc
|
||||||
|
foreach ($f in 'locale.dbf','locale.FPT','locale.CDX','locale.dbc','locale.DCT','locale.DCX','locale_lang.dbf','locale_lang.FPT','locale_lang.cdx') {
|
||||||
|
$s = Join-Path $SourceProject "Locale\$f"
|
||||||
|
if (Test-Path -LiteralPath $s) { Copy-Item -LiteralPath $s -Destination (Join-Path $fx 'Locale') -Force }
|
||||||
|
}
|
||||||
|
return $fx
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sterge fixture-ul + folderul-parinte de staging lasat de git_sync (TEMP\git_sync_<leaf>, ramane gol)
|
||||||
|
function Remove-Fixture([string]$fx) {
|
||||||
|
Remove-Item -Recurse -Force -LiteralPath $fx -ErrorAction SilentlyContinue
|
||||||
|
$stagingParent = Join-Path $env:TEMP ("git_sync_" + (Split-Path $fx -Leaf))
|
||||||
|
Remove-Item -Recurse -Force -LiteralPath $stagingParent -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
$script:BothDbf = @('Locale\locale.dbf','Locale\locale_lang.dbf')
|
||||||
|
|
||||||
|
# ============================== SCENARIILE ==============================
|
||||||
|
|
||||||
|
function Test-Incremental {
|
||||||
|
Write-Host "`n--- (A) incremental: a doua rulare nu reconverteste nimic ---"
|
||||||
|
$fx = New-Fixture 'inc'
|
||||||
|
try {
|
||||||
|
$snap = Get-BinSnapshot $fx
|
||||||
|
$r1 = Invoke-GitSync -Fixture $fx -DbfList $script:BothDbf
|
||||||
|
Assert ($r1.Exit -eq 0) "A: prima rulare exit 0 (exit=$($r1.Exit))"
|
||||||
|
Assert ($r1.Convertite -gt 0) "A: prima rulare converteste (Convertite=$($r1.Convertite) > 0)"
|
||||||
|
Assert-BinariesUnchanged $snap $fx "A(run1)"
|
||||||
|
|
||||||
|
$r2 = Invoke-GitSync -Fixture $fx -DbfList $script:BothDbf
|
||||||
|
Assert ($r2.Exit -eq 0) "A: a doua rulare exit 0 (exit=$($r2.Exit))"
|
||||||
|
Assert ($r2.Convertite -eq 0) "A: a doua rulare NU reconverteste (Convertite=$($r2.Convertite) = 0)"
|
||||||
|
Assert ($r2.Sarite -gt 0) "A: a doua rulare sare fisierele la zi (Sarite=$($r2.Sarite) > 0)"
|
||||||
|
Assert-BinariesUnchanged $snap $fx "A(run2)"
|
||||||
|
} finally { Remove-Fixture $fx }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-Orphans {
|
||||||
|
Write-Host "`n--- (B) curatare orfani: .vc2 fara binar + .db2 iesit din lista ---"
|
||||||
|
$fx = New-Fixture 'orf'
|
||||||
|
try {
|
||||||
|
# orfan de tip 1: text .vc2 fara binar .vcx corespondent
|
||||||
|
$orfVc2 = Join-Path $fx 'Clase\_orfan.vc2'
|
||||||
|
Set-Content -LiteralPath $orfVc2 -Value '* text orfan fara binar corespondent' -Encoding Ascii
|
||||||
|
$snap = Get-BinSnapshot $fx
|
||||||
|
|
||||||
|
# run1: ambele tabele in lista -> produce db2-uri; orfanul fara binar se sterge
|
||||||
|
$r1 = Invoke-GitSync -Fixture $fx -DbfList $script:BothDbf
|
||||||
|
Assert (-not (Test-Path -LiteralPath $orfVc2)) "B: .vc2 orfan (fara binar) sters"
|
||||||
|
Assert (($r1.Orphans -join ';') -match '(?i)_orfan\.vc2') "B: .vc2 orfan raportat in sumar"
|
||||||
|
Assert (Test-Path -LiteralPath (Join-Path $fx 'Locale\locale_lang.db2')) "B: locale_lang.db2 creat in run1"
|
||||||
|
Assert-BinariesUnchanged $snap $fx "B(run1)"
|
||||||
|
|
||||||
|
# run2: locale_lang scos din lista -> db2-ul lui iese din lista -> orfan
|
||||||
|
$r2 = Invoke-GitSync -Fixture $fx -DbfList @('Locale\locale.dbf')
|
||||||
|
Assert (-not (Test-Path -LiteralPath (Join-Path $fx 'Locale\locale_lang.db2'))) "B: db2 al tabelei iesite din lista sters"
|
||||||
|
Assert (($r2.Orphans -join ';') -match '(?i)locale_lang\.db2') "B: db2 iesit din lista raportat in sumar"
|
||||||
|
Assert (Test-Path -LiteralPath (Join-Path $fx 'Locale\locale.db2')) "B: db2 al tabelei ramase in lista pastrat"
|
||||||
|
Assert-BinariesUnchanged $snap $fx "B(run2)"
|
||||||
|
} finally { Remove-Fixture $fx }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-PartialFailure {
|
||||||
|
Write-Host "`n--- (C) esec partial: binar corupt esueaza, restul se convertesc ---"
|
||||||
|
$fx = New-Fixture 'fail'
|
||||||
|
try {
|
||||||
|
# binar corupt: octeti garbage in .vcx si .vct (FoxBin2Prg: 'not a table', ErrorLevel != 0)
|
||||||
|
$g1 = New-Object byte[] 800; (New-Object Random).NextBytes($g1)
|
||||||
|
[IO.File]::WriteAllBytes((Join-Path $fx 'Clase\corupt.vcx'), $g1)
|
||||||
|
$g2 = New-Object byte[] 600; (New-Object Random).NextBytes($g2)
|
||||||
|
[IO.File]::WriteAllBytes((Join-Path $fx 'Clase\corupt.vct'), $g2)
|
||||||
|
$snap = Get-BinSnapshot $fx
|
||||||
|
|
||||||
|
$r = Invoke-GitSync -Fixture $fx -DbfList @('Locale\locale.dbf')
|
||||||
|
Assert ($r.Exit -ne 0) "C: exit nenul la esec partial (exit=$($r.Exit))"
|
||||||
|
Assert ($r.Esuate -ge 1) "C: cel putin un esec raportat (Esuate=$($r.Esuate))"
|
||||||
|
Assert ($r.Raw -match '(?i)corupt\.vcx') "C: fisierul corupt apare in lista de esecuri"
|
||||||
|
Assert ($r.Convertite -ge 1) "C: fisierele valide se convertesc totusi (Convertite=$($r.Convertite))"
|
||||||
|
Assert (Test-Path -LiteralPath (Join-Path $fx 'Clase\atentie.vc2')) "C: text valid (atentie.vc2) produs in ciuda esecului"
|
||||||
|
# esecul trebuie sa fie rapid (ErrorLevel, nu dialog modal): sub watchdog inseamna ca nu a aparut MessageBox VFP
|
||||||
|
Assert ($r.ElapsedSec -lt $r.TimeoutSec) "C: esec rapid fara dialog modal ($($r.ElapsedSec)s < watchdog $($r.TimeoutSec)s)"
|
||||||
|
Assert-BinariesUnchanged $snap $fx "C"
|
||||||
|
} finally { Remove-Fixture $fx }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-BinaryProtection {
|
||||||
|
Write-Host "`n--- (D) protectia binarului: byte-identic dupa conversie completa ---"
|
||||||
|
$fx = New-Fixture 'bin'
|
||||||
|
try {
|
||||||
|
$snap = Get-BinSnapshot $fx
|
||||||
|
Assert ($snap.Count -gt 0) "D: fixture-ul contine binare de protejat ($($snap.Count))"
|
||||||
|
$r = Invoke-GitSync -Fixture $fx -DbfList $script:BothDbf
|
||||||
|
Assert ($r.Exit -eq 0) "D: rulare curata (exit=$($r.Exit))"
|
||||||
|
Assert-BinariesUnchanged $snap $fx "D"
|
||||||
|
} finally { Remove-Fixture $fx }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-Exempt {
|
||||||
|
Write-Host "`n--- (E) scutire roundtrip: fisierul din -RoundtripExempt produce text fara garda roundtrip ---"
|
||||||
|
$fx = New-Fixture 'exempt'
|
||||||
|
try {
|
||||||
|
$snap = Get-BinSnapshot $fx
|
||||||
|
$r = Invoke-GitSync -Fixture $fx -DbfList @('Locale\locale.dbf') -Exempt @('Clase\atentie.vcx')
|
||||||
|
Assert ($r.Exit -eq 0) "E: rulare curata cu scutire (exit=$($r.Exit))"
|
||||||
|
Assert ($r.Raw -match '(?i)atentie\.vcx \(roundtrip scutit\)') "E: fisierul scutit raportat 'roundtrip scutit'"
|
||||||
|
Assert (Test-Path -LiteralPath (Join-Path $fx 'Clase\atentie.vc2')) "E: text produs pentru fisierul scutit"
|
||||||
|
Assert-BinariesUnchanged $snap $fx "E"
|
||||||
|
} finally { Remove-Fixture $fx }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-OrphanExempt {
|
||||||
|
Write-Host "`n--- (F) scutire orfani: textul din -OrphanExempt nu e raportat, restul da ---"
|
||||||
|
$fx = New-Fixture 'orphanexempt'
|
||||||
|
try {
|
||||||
|
# textul scutit implicit: stub-ul de test din COMUN, al carui .scx e gitignored si generat local
|
||||||
|
$stubDir = Join-Path $fx 'COMUN\utile\Teste\achizitie_import\stub_verificare'
|
||||||
|
New-Item -ItemType Directory -Path $stubDir -Force | Out-Null
|
||||||
|
$stub = Join-Path $stubDir 'verificare.sc2'
|
||||||
|
Set-Content -LiteralPath $stub -Value '* stub de test, binarul se genereaza local' -Encoding Ascii
|
||||||
|
# martor: text fara binar in afara scutirii -> trebuie raportat in continuare
|
||||||
|
$ctrl = Join-Path $fx 'Clase\_orfan.sc2'
|
||||||
|
Set-Content -LiteralPath $ctrl -Value '* text orfan fara binar corespondent' -Encoding Ascii
|
||||||
|
|
||||||
|
$r = Invoke-GitSync -Fixture $fx -DbfList @('Locale\locale.dbf')
|
||||||
|
$orf = $r.Orphans -join ';'
|
||||||
|
Assert ($r.Exit -eq 0) "F: rulare curata cu scutire de orfani (exit=$($r.Exit))"
|
||||||
|
Assert ($orf -notmatch '(?i)verificare\.sc2') "F: textul scutit NU e raportat ca orfan"
|
||||||
|
Assert (Test-Path -LiteralPath $stub) "F: textul scutit ramane pe disc"
|
||||||
|
Assert ($orf -match '(?i)_orfan\.sc2') "F: orfanul din afara scutirii e raportat in continuare"
|
||||||
|
|
||||||
|
# cu pattern explicit care nu-l acopera, stub-ul redevine orfan raportat
|
||||||
|
$r2 = Invoke-GitSync -Fixture $fx -DbfList @('Locale\locale.dbf') -OrphanExempt @('*\nimic_care_nu_exista.sc2')
|
||||||
|
Assert ((($r2.Orphans -join ';')) -match '(?i)verificare\.sc2') "F: fara pattern-ul potrivit, stub-ul e raportat ca orfan"
|
||||||
|
} finally { Remove-Fixture $fx }
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================== MAIN ==============================
|
||||||
|
if (-not (Test-Path -LiteralPath $script:GitSync)) { throw "Nu gasesc git_sync.ps1: $script:GitSync" }
|
||||||
|
if (-not (Test-Path -LiteralPath $SourceProject)) { throw "Proiect sursa inexistent: $SourceProject" }
|
||||||
|
|
||||||
|
Write-Host "=== test_git_sync.ps1 -- SourceProject=$SourceProject ==="
|
||||||
|
Test-Incremental
|
||||||
|
Test-Orphans
|
||||||
|
Test-PartialFailure
|
||||||
|
Test-BinaryProtection
|
||||||
|
Test-Exempt
|
||||||
|
Test-OrphanExempt
|
||||||
|
|
||||||
|
Write-Host "`n=== REZULTAT ==="
|
||||||
|
if ($script:Failures.Count -eq 0) {
|
||||||
|
Write-Host "TOATE testele au trecut." -ForegroundColor Green
|
||||||
|
exit 0
|
||||||
|
} else {
|
||||||
|
Write-Host "$($script:Failures.Count) test(e) esuate:" -ForegroundColor Red
|
||||||
|
$script:Failures | ForEach-Object { Write-Host " - $_" -ForegroundColor Red }
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
507
test_roundtrip.ps1
Normal file
507
test_roundtrip.ps1
Normal file
@@ -0,0 +1,507 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Teste standalone (fara Pester) pentru fluxul bin<->text VFP (FoxBin2Prg) si scriptul
|
||||||
|
de write-back txt2vcx.ps1. Parametrizat per proiect (implicit ROAGEST).
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Faze:
|
||||||
|
- fidelity : esantion din FIECARE tip convertit, auto-descoperit din arborele proiectului.
|
||||||
|
vcx/scx -> roundtrip complet bin->text1->bin->text2 (comparare pe octeti);
|
||||||
|
frx/mnx/lbx/dbc/pjx/dbf -> doar validare bin->text (exit code + timeout + text nevid).
|
||||||
|
Totul in temp, nimic scris in proiect.
|
||||||
|
- edit : editare reala via txt2vcx.ps1 pe fixture-uri cunoscute (implicit ROAGEST);
|
||||||
|
daca fixture-ul lipseste in proiect, se sare (warn). Restaurarea binarelor
|
||||||
|
se face din backup temp (NU git checkout - binarele sunt git-ignored dupa migrare).
|
||||||
|
- negative : text stricat, fisier gol, guard-uri (staleness, binar lipsa, cale in afara
|
||||||
|
cache-ului, extensie nesuportata, COMUN, folder-foxbin2prg, -DryRun),
|
||||||
|
test multi-fisier. Textul stricat ruleaza DOAR in cache temp, niciodata in arbore.
|
||||||
|
- smoke : fidelity pe cate un .vcx din proiectele frate (-SmokeProjects).
|
||||||
|
- all : toate cele de mai sus, in ordine.
|
||||||
|
|
||||||
|
Restaurare: orice binar atins de write-back e copiat in backup temp INAINTE si restaurat
|
||||||
|
la final (in finally), cu asertie byte-identic fata de original. Cache-ul text implicit e
|
||||||
|
un director temporar creat pentru rulare (decuplat de arborele proiectului).
|
||||||
|
|
||||||
|
Exit 1 daca orice Assert a esuat, CU EXCEPTIA celor marcate warn-only.
|
||||||
|
|
||||||
|
.PARAMETER ProjectRoot
|
||||||
|
Radacina proiectului principal testat (implicit ROAGEST).
|
||||||
|
|
||||||
|
.PARAMETER CacheRoot
|
||||||
|
Cache-ul text folosit de teste. Gol (implicit) => director temporar creat/sters de test,
|
||||||
|
ca sa nu se scrie niciodata text in arborele proiectului.
|
||||||
|
|
||||||
|
.PARAMETER Phase
|
||||||
|
all | fidelity | edit | negative | smoke
|
||||||
|
|
||||||
|
.PARAMETER SmokeProjects
|
||||||
|
Lista proiectelor frate pentru faza smoke.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$ProjectRoot = 'D:\ROA\ROAGEST',
|
||||||
|
[string]$CacheRoot = '',
|
||||||
|
[ValidateSet('all','fidelity','edit','negative','smoke')]
|
||||||
|
[string]$Phase = 'all',
|
||||||
|
[string[]]$SmokeProjects = @('D:\ROA\ROAIMOB','D:\ROA\ROAACNPRO')
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$exe = 'D:\ROA\UTIL\foxbin2prg\FoxBin2Prg.EXE'
|
||||||
|
$vcx2txt = 'D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1'
|
||||||
|
$txt2vcx = 'D:\ROA\UTIL\foxbin2prg\txt2vcx.ps1'
|
||||||
|
. (Join-Path $PSScriptRoot 'vfp_filemap.ps1')
|
||||||
|
|
||||||
|
$script:Failures = @()
|
||||||
|
$script:Enc = [Text.Encoding]::GetEncoding(1252)
|
||||||
|
$script:BackupRoot = Join-Path $env:TEMP ("fb2p_test_backup_{0}_{1}" -f $PID, ([Guid]::NewGuid().ToString('N').Substring(0,8)))
|
||||||
|
$script:Backups = @{} # cheie: cale binar (lower) -> info backup, pt restaurare la final
|
||||||
|
|
||||||
|
function Assert([bool]$cond, [string]$msg) {
|
||||||
|
if ($cond) { Write-Host " PASS: $msg" -ForegroundColor Green }
|
||||||
|
else { Write-Host " FAIL: $msg" -ForegroundColor Red; $script:Failures += $msg }
|
||||||
|
}
|
||||||
|
|
||||||
|
function AssertWarn([bool]$cond, [string]$msg) {
|
||||||
|
if ($cond) { Write-Host " PASS(warn-only): $msg" -ForegroundColor Green }
|
||||||
|
else { Write-Warning " WARN (non-blocant): $msg" }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Normalize-Crlf([byte[]]$bytes) {
|
||||||
|
$out = New-Object System.Collections.Generic.List[byte]
|
||||||
|
for ($i = 0; $i -lt $bytes.Length; $i++) {
|
||||||
|
if ($bytes[$i] -eq 0x0D) {
|
||||||
|
if ($i + 1 -lt $bytes.Length -and $bytes[$i+1] -eq 0x0A) { continue }
|
||||||
|
$out.Add(0x0A) | Out-Null
|
||||||
|
} else { $out.Add($bytes[$i]) | Out-Null }
|
||||||
|
}
|
||||||
|
return $out.ToArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-BytesEqual([byte[]]$a, [byte[]]$b) {
|
||||||
|
$na = Normalize-Crlf $a; $nb = Normalize-Crlf $b
|
||||||
|
if ($na.Length -ne $nb.Length) { return $false }
|
||||||
|
for ($i = 0; $i -lt $na.Length; $i++) { if ($na[$i] -ne $nb[$i]) { return $false } }
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-Fb2p {
|
||||||
|
# Watchdog obligatoriu - FoxBin2Prg poate afisa un MessageBox modal nativ VFP la text stricat;
|
||||||
|
# fara asta, faza negativa ar ramane agatata la infinit intr-o rulare automata.
|
||||||
|
param([string]$InputFile, [string]$Recompile = '', [int]$TimeoutSeconds = 60)
|
||||||
|
# cfg in dir-ul input-ului: DontShowErrors suprima MessageBox-ul propriu FoxBin2Prg (ca in git_sync)
|
||||||
|
$cfgLines = [System.Collections.Generic.List[string]]@('DontShowErrors: 1','ShowProgressbar: 0')
|
||||||
|
if ([IO.Path]::GetExtension($InputFile).ToLower() -eq '.dbf') { $cfgLines.Add('DBF_Conversion_Support: 4') }
|
||||||
|
Set-Content -LiteralPath (Join-Path (Split-Path -Parent $InputFile) 'foxbin2prg.cfg') -Value $cfgLines -Encoding Ascii
|
||||||
|
if ($Recompile) {
|
||||||
|
$r = Invoke-FoxBin2PrgSafe -ExePath $exe -ExeArgs @($InputFile,'','','','1','0','1','',$Recompile) -TimeoutSeconds $TimeoutSeconds
|
||||||
|
} else {
|
||||||
|
$r = Invoke-FoxBin2PrgSafe -ExePath $exe -ExeArgs @($InputFile,'','','','1','0','1') -TimeoutSeconds $TimeoutSeconds
|
||||||
|
}
|
||||||
|
if ($r.TimedOut) { Write-Warning "Invoke-Fb2p: timeout pe '$InputFile' - tratat ca ESEC." }
|
||||||
|
return $r.ExitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-FileHashBytes([string]$path) {
|
||||||
|
if (-not (Test-Path -LiteralPath $path)) { return $null }
|
||||||
|
return (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-RelPath([string]$Root, [string]$Full) {
|
||||||
|
return $Full.Substring($Root.TrimEnd('\').Length).TrimStart('\')
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Auto-descoperire: un esantion mic non-COMUN de tipul cerut, cu memo companion ---
|
||||||
|
function Find-SampleBinary {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)][string]$ProjRoot,
|
||||||
|
[Parameter(Mandatory)][string]$Ext,
|
||||||
|
[bool]$RequireMemo = $true,
|
||||||
|
[int]$MaxBytes = 500000
|
||||||
|
)
|
||||||
|
if (-not (Test-Path -LiteralPath $ProjRoot)) { return $null }
|
||||||
|
$memoExt = $script:VfpFileMap[$Ext][0]
|
||||||
|
Get-ChildItem -LiteralPath $ProjRoot -Recurse -File -Filter ("*" + $Ext) -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object {
|
||||||
|
$_.FullName -notmatch '(?i)\\COMUN\\' -and
|
||||||
|
$_.Length -le $MaxBytes -and $_.Length -gt 0 -and
|
||||||
|
(-not $RequireMemo -or (Test-Path -LiteralPath ([IO.Path]::ChangeExtension($_.FullName, $memoExt))))
|
||||||
|
} |
|
||||||
|
Sort-Object Length |
|
||||||
|
Select-Object -First 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Backup/restore binare din temp (inlocuieste git checkout - binarele sunt git-ignored) ---
|
||||||
|
function Backup-ProjectBinary([string]$fullBin) {
|
||||||
|
$key = $fullBin.ToLower()
|
||||||
|
if ($script:Backups.ContainsKey($key)) { return }
|
||||||
|
$ext = [IO.Path]::GetExtension($fullBin).ToLower()
|
||||||
|
$memoExt = $script:VfpFileMap[$ext][0]
|
||||||
|
$dir = Split-Path $fullBin -Parent
|
||||||
|
$base = [IO.Path]::GetFileNameWithoutExtension($fullBin)
|
||||||
|
$memoEntry = Get-ChildItem -LiteralPath $dir -Filter ($base + $memoExt) -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||||
|
$memoFull = if ($memoEntry) { $memoEntry.FullName } else { [IO.Path]::ChangeExtension($fullBin, $memoExt) }
|
||||||
|
$bdir = Join-Path $script:BackupRoot ([Guid]::NewGuid().ToString('N').Substring(0,8))
|
||||||
|
New-Item -ItemType Directory -Force -Path $bdir | Out-Null
|
||||||
|
$binBak = Join-Path $bdir (Split-Path $fullBin -Leaf)
|
||||||
|
Copy-Item -LiteralPath $fullBin -Destination $binBak -Force
|
||||||
|
$memoBak = $null
|
||||||
|
if (Test-Path -LiteralPath $memoFull) {
|
||||||
|
$memoBak = Join-Path $bdir (Split-Path $memoFull -Leaf)
|
||||||
|
Copy-Item -LiteralPath $memoFull -Destination $memoBak -Force
|
||||||
|
}
|
||||||
|
$script:Backups[$key] = @{ BinFull=$fullBin; BinBak=$binBak; MemoFull=$memoFull; MemoBak=$memoBak; OrigHash=(Get-FileHashBytes $fullBin) }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Restore-ProjectBinaries {
|
||||||
|
foreach ($k in @($script:Backups.Keys)) {
|
||||||
|
$b = $script:Backups[$k]
|
||||||
|
Copy-Item -LiteralPath $b.BinBak -Destination $b.BinFull -Force
|
||||||
|
if ($b.MemoBak) { Copy-Item -LiteralPath $b.MemoBak -Destination $b.MemoFull -Force }
|
||||||
|
$now = Get-FileHashBytes $b.BinFull
|
||||||
|
Assert ($now -eq $b.OrigHash) "Restore: $(Split-Path $b.BinFull -Leaf) byte-identic cu originalul dupa restore"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Fidelitate: roundtrip complet bin->text1->bin->text2 (doar in temp) - pt vcx/scx ---
|
||||||
|
function Test-FidelityRoundtrip {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)][string]$RelPath,
|
||||||
|
[Parameter(Mandatory)][string]$ProjRoot,
|
||||||
|
[bool]$WarnOnly = $false
|
||||||
|
)
|
||||||
|
$full = Join-Path $ProjRoot $RelPath
|
||||||
|
if (-not (Test-Path -LiteralPath $full)) { AssertWarn $false "Faza A: fisier lipsa, sarit: $RelPath"; return }
|
||||||
|
$ext = [IO.Path]::GetExtension($full).ToLower()
|
||||||
|
$pair = $script:VfpFileMap[$ext]
|
||||||
|
if (-not $pair) { AssertWarn $false "Faza A: extensie necunoscuta, sarit: $RelPath"; return }
|
||||||
|
$memoExt = $pair[0]; $txtExt = $pair[1]
|
||||||
|
$memoFull = [IO.Path]::ChangeExtension($full, $memoExt)
|
||||||
|
|
||||||
|
$work = Join-Path $env:TEMP ("fb2p_fidelity_{0}_{1}" -f $PID, ([Guid]::NewGuid().ToString('N').Substring(0,8)))
|
||||||
|
New-Item -ItemType Directory -Force -Path $work | Out-Null
|
||||||
|
try {
|
||||||
|
$baseName = [IO.Path]::GetFileNameWithoutExtension($full)
|
||||||
|
$bin1 = Join-Path $work ($baseName + $ext); $memo1 = Join-Path $work ($baseName + $memoExt)
|
||||||
|
Copy-Item -LiteralPath $full -Destination $bin1 -Force
|
||||||
|
if (Test-Path -LiteralPath $memoFull) { Copy-Item -LiteralPath $memoFull -Destination $memo1 -Force }
|
||||||
|
$rc1 = Invoke-Fb2p -InputFile $bin1
|
||||||
|
$text1 = Join-Path $work ($baseName + $txtExt)
|
||||||
|
if ($rc1 -ne 0 -or -not (Test-Path -LiteralPath $text1)) {
|
||||||
|
if ($WarnOnly) { AssertWarn $false "Faza A ($RelPath): Bin2Prg #1 a esuat (ErrorLevel=$rc1)"; return }
|
||||||
|
Assert $false "Faza A ($RelPath): Bin2Prg #1 a esuat (ErrorLevel=$rc1)"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$stage2 = Join-Path $work 'stage2'
|
||||||
|
New-Item -ItemType Directory -Force -Path $stage2 | Out-Null
|
||||||
|
$text1copy = Join-Path $stage2 ($baseName + $txtExt)
|
||||||
|
Copy-Item -LiteralPath $text1 -Destination $text1copy -Force
|
||||||
|
$rc2 = Invoke-Fb2p -InputFile $text1copy -Recompile $ProjRoot
|
||||||
|
$bin2 = Join-Path $stage2 ($baseName + $ext); $memo2 = Join-Path $stage2 ($baseName + $memoExt)
|
||||||
|
if ($rc2 -ne 0 -or -not (Test-Path -LiteralPath $bin2) -or -not (Test-Path -LiteralPath $memo2)) {
|
||||||
|
$ok = $false
|
||||||
|
} else {
|
||||||
|
$rc3 = Invoke-Fb2p -InputFile $bin2
|
||||||
|
$text2 = Join-Path $stage2 ($baseName + $txtExt)
|
||||||
|
$ok = ($rc3 -eq 0) -and (Test-Path -LiteralPath $text2) -and (Test-BytesEqual ([IO.File]::ReadAllBytes($text1)) ([IO.File]::ReadAllBytes($text2)))
|
||||||
|
}
|
||||||
|
if ($WarnOnly) { AssertWarn $ok "Faza A ($RelPath): text1 == text2 dupa roundtrip complet" }
|
||||||
|
else { Assert $ok "Faza A ($RelPath): text1 == text2 dupa roundtrip complet" }
|
||||||
|
} finally {
|
||||||
|
Remove-Item -Recurse -Force -LiteralPath $work -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Validare bin->text (fara roundtrip) - pt frx/mnx/lbx/dbc/pjx/dbf (write-back nesuportat) ---
|
||||||
|
function Test-BinToTextValid {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)][string]$RelPath,
|
||||||
|
[Parameter(Mandatory)][string]$ProjRoot,
|
||||||
|
[bool]$WarnOnly = $false
|
||||||
|
)
|
||||||
|
$full = Join-Path $ProjRoot $RelPath
|
||||||
|
if (-not (Test-Path -LiteralPath $full)) { AssertWarn $false "Faza A: fisier lipsa, sarit: $RelPath"; return }
|
||||||
|
$ext = [IO.Path]::GetExtension($full).ToLower()
|
||||||
|
$pair = $script:VfpFileMap[$ext]
|
||||||
|
if (-not $pair) { AssertWarn $false "Faza A: extensie necunoscuta, sarit: $RelPath"; return }
|
||||||
|
$memoExt = $pair[0]; $txtExt = $pair[1]
|
||||||
|
$memoFull = [IO.Path]::ChangeExtension($full, $memoExt)
|
||||||
|
|
||||||
|
$work = Join-Path $env:TEMP ("fb2p_valid_{0}_{1}" -f $PID, ([Guid]::NewGuid().ToString('N').Substring(0,8)))
|
||||||
|
New-Item -ItemType Directory -Force -Path $work | Out-Null
|
||||||
|
try {
|
||||||
|
$baseName = [IO.Path]::GetFileNameWithoutExtension($full)
|
||||||
|
$bin1 = Join-Path $work ($baseName + $ext)
|
||||||
|
Copy-Item -LiteralPath $full -Destination $bin1 -Force
|
||||||
|
if (Test-Path -LiteralPath $memoFull) { Copy-Item -LiteralPath $memoFull -Destination (Join-Path $work ($baseName + $memoExt)) -Force }
|
||||||
|
$rc = Invoke-Fb2p -InputFile $bin1
|
||||||
|
$text1 = Join-Path $work ($baseName + $txtExt)
|
||||||
|
$ok = ($rc -eq 0) -and (Test-Path -LiteralPath $text1) -and ((Get-Item -LiteralPath $text1).Length -gt 0)
|
||||||
|
$msg = "Faza A ($RelPath): bin->text valid (exit=$rc, text nevid)"
|
||||||
|
if ($WarnOnly) { AssertWarn $ok $msg } else { Assert $ok $msg }
|
||||||
|
} finally {
|
||||||
|
Remove-Item -Recurse -Force -LiteralPath $work -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# specificatie per tip convertit: roundtrip doar vcx/scx; restul doar validare bin->text
|
||||||
|
$script:FidelitySpec = @(
|
||||||
|
@{ Ext='.vcx'; Roundtrip=$true; WarnOnly=$false; RequireMemo=$true }
|
||||||
|
@{ Ext='.scx'; Roundtrip=$true; WarnOnly=$false; RequireMemo=$true }
|
||||||
|
@{ Ext='.frx'; Roundtrip=$false; WarnOnly=$true; RequireMemo=$true }
|
||||||
|
@{ Ext='.mnx'; Roundtrip=$false; WarnOnly=$true; RequireMemo=$true }
|
||||||
|
@{ Ext='.lbx'; Roundtrip=$false; WarnOnly=$true; RequireMemo=$true }
|
||||||
|
@{ Ext='.dbc'; Roundtrip=$false; WarnOnly=$true; RequireMemo=$true }
|
||||||
|
@{ Ext='.pjx'; Roundtrip=$false; WarnOnly=$true; RequireMemo=$true }
|
||||||
|
@{ Ext='.dbf'; Roundtrip=$false; WarnOnly=$true; RequireMemo=$false }
|
||||||
|
)
|
||||||
|
|
||||||
|
function Invoke-FidelityForProject([string]$ProjRoot) {
|
||||||
|
foreach ($s in $script:FidelitySpec) {
|
||||||
|
$sample = Find-SampleBinary -ProjRoot $ProjRoot -Ext $s.Ext -RequireMemo $s.RequireMemo
|
||||||
|
if (-not $sample) { AssertWarn $false "Faza A: niciun esantion $($s.Ext) in $ProjRoot, sarit"; continue }
|
||||||
|
$rel = Get-RelPath $ProjRoot $sample.FullName
|
||||||
|
Write-Host " esantion $($s.Ext): $rel ($([int]($sample.Length/1024)) KB)"
|
||||||
|
if ($s.Roundtrip) { Test-FidelityRoundtrip -RelPath $rel -ProjRoot $ProjRoot -WarnOnly $s.WarnOnly }
|
||||||
|
else { Test-BinToTextValid -RelPath $rel -ProjRoot $ProjRoot -WarnOnly $s.WarnOnly }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Editare reala prin txt2vcx.ps1, cu backup/restore (nu git) ---
|
||||||
|
function Test-EditRoundtrip {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)][string]$RelPath,
|
||||||
|
[Parameter(Mandatory)][string]$CaptionOld,
|
||||||
|
[Parameter(Mandatory)][string]$CaptionNew,
|
||||||
|
[Parameter(Mandatory)][string]$ProcedureMarker
|
||||||
|
)
|
||||||
|
$full = Join-Path $ProjectRoot $RelPath
|
||||||
|
if (-not (Test-Path -LiteralPath $full)) { AssertWarn $false "Faza B: fixture lipsa in proiect, sarit: $RelPath"; return }
|
||||||
|
$ext = [IO.Path]::GetExtension($RelPath).ToLower()
|
||||||
|
$pair = $script:VfpFileMap[$ext]
|
||||||
|
$txtExt = $pair[1]
|
||||||
|
$relDir = Split-Path $RelPath -Parent
|
||||||
|
$base = [IO.Path]::GetFileNameWithoutExtension($RelPath)
|
||||||
|
$relText = if ($relDir) { Join-Path $relDir ($base + $txtExt) } else { $base + $txtExt }
|
||||||
|
|
||||||
|
Write-Host "-- Faza B: $RelPath --"
|
||||||
|
Backup-ProjectBinary $full
|
||||||
|
$origHash = $script:Backups[$full.ToLower()].OrigHash
|
||||||
|
|
||||||
|
& $vcx2txt -Source $full -ProjectRoot $ProjectRoot -CacheRoot $CacheRoot -Force | Out-Null
|
||||||
|
$cacheText = Join-Path $CacheRoot $relText
|
||||||
|
Assert (Test-Path -LiteralPath $cacheText) "Faza B ($RelPath): cache text regenerat"
|
||||||
|
|
||||||
|
$text = $script:Enc.GetString([IO.File]::ReadAllBytes($cacheText))
|
||||||
|
Assert ($text.Contains($CaptionOld)) "Faza B ($RelPath): linia Caption originala gasita"
|
||||||
|
$text = $text.Replace($CaptionOld, $CaptionNew)
|
||||||
|
$marker = "`t" + $ProcedureMarker + "`r`n"
|
||||||
|
$idx = $text.IndexOf($marker)
|
||||||
|
Assert ($idx -ge 0) "Faza B ($RelPath): marker '$ProcedureMarker' gasit"
|
||||||
|
if ($idx -ge 0) {
|
||||||
|
$insertPos = $idx + $marker.Length
|
||||||
|
$text = $text.Substring(0,$insertPos) + "`t`t* test roundtrip`r`n" + $text.Substring($insertPos)
|
||||||
|
}
|
||||||
|
[IO.File]::WriteAllBytes($cacheText, $script:Enc.GetBytes($text))
|
||||||
|
|
||||||
|
& $txt2vcx -TextFile $cacheText -ProjectRoot $ProjectRoot -CacheRoot $CacheRoot
|
||||||
|
$rc = $LASTEXITCODE
|
||||||
|
Assert ($rc -eq 0) "Faza B ($RelPath): txt2vcx.ps1 exit 0"
|
||||||
|
|
||||||
|
Assert ((Get-FileHashBytes $full) -ne $origHash) "Faza B ($RelPath): binarul din proiect a fost rescris (hash diferit de original)"
|
||||||
|
$bakStray = Join-Path (Join-Path $ProjectRoot $relDir) ($base + '.bak')
|
||||||
|
Assert (-not (Test-Path -LiteralPath $bakStray)) "Faza B ($RelPath): fara .bak lasat in proiect"
|
||||||
|
|
||||||
|
$regenText = $script:Enc.GetString([IO.File]::ReadAllBytes($cacheText))
|
||||||
|
Assert ($regenText.Contains($CaptionNew)) "Faza B ($RelPath): textul regenerat contine Caption editat"
|
||||||
|
Assert ($regenText.Contains('* test roundtrip')) "Faza B ($RelPath): textul regenerat contine comentariul adaugat"
|
||||||
|
# binarul e restaurat din backup in finally-ul global
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================== MAIN ==============================
|
||||||
|
$ownCache = $false
|
||||||
|
if ([string]::IsNullOrWhiteSpace($CacheRoot)) {
|
||||||
|
$CacheRoot = Join-Path $env:TEMP ("fb2p_test_cache_{0}_{1}" -f $PID, ([Guid]::NewGuid().ToString('N').Substring(0,8)))
|
||||||
|
$ownCache = $true
|
||||||
|
}
|
||||||
|
if (-not (Test-Path -LiteralPath $CacheRoot)) { New-Item -ItemType Directory -Force -Path $CacheRoot | Out-Null }
|
||||||
|
New-Item -ItemType Directory -Force -Path $script:BackupRoot | Out-Null
|
||||||
|
|
||||||
|
Write-Host "=== test_roundtrip.ps1 -- ProjectRoot=$ProjectRoot CacheRoot=$CacheRoot Phase=$Phase ==="
|
||||||
|
|
||||||
|
# fixture-uri de editare cunoscute (implicit ROAGEST); se sar automat daca lipsesc in proiect
|
||||||
|
$editFixtures = @(
|
||||||
|
@{ Rel='Clase\oavize.vcx'; Old='Lb_titlu_alb_b121.Caption = "Date aviz"'; New='Lb_titlu_alb_b121.Caption = "Atentie-diacritice-ok"'; Marker='PROCEDURE Init' }
|
||||||
|
@{ Rel='Ferestre\fundal.scx'; Old='Caption = ""'; New='Caption = "test"'; Marker='PROCEDURE Show' }
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($Phase -eq 'all' -or $Phase -eq 'fidelity') {
|
||||||
|
Write-Host "`n--- FAZA A: fidelitate (esantion din fiecare tip convertit) ---"
|
||||||
|
Invoke-FidelityForProject $ProjectRoot
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Phase -eq 'all' -or $Phase -eq 'edit') {
|
||||||
|
Write-Host "`n--- FAZA B: editare reala (write-back) ---"
|
||||||
|
foreach ($fx in $editFixtures) {
|
||||||
|
Test-EditRoundtrip -RelPath $fx.Rel -CaptionOld $fx.Old -CaptionNew $fx.New -ProcedureMarker $fx.Marker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Phase -eq 'all' -or $Phase -eq 'smoke') {
|
||||||
|
Write-Host "`n--- FAZA C: smoke pe proiecte frate ---"
|
||||||
|
foreach ($sp in $SmokeProjects) {
|
||||||
|
if (-not (Test-Path -LiteralPath $sp)) { AssertWarn $false "Faza C: proiect lipsa $sp, sarit"; continue }
|
||||||
|
$s = Find-SampleBinary -ProjRoot $sp -Ext '.vcx'
|
||||||
|
if ($s) { Test-FidelityRoundtrip -RelPath (Get-RelPath $sp $s.FullName) -ProjRoot $sp }
|
||||||
|
else { AssertWarn $false "Faza C: niciun .vcx in $sp, sarit" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Phase -eq 'all' -or $Phase -eq 'negative') {
|
||||||
|
Write-Host "`n--- FAZA D: negativ + guard-uri + multi-fisier ---"
|
||||||
|
$vcxSample = Find-SampleBinary -ProjRoot $ProjectRoot -Ext '.vcx'
|
||||||
|
if (-not $vcxSample) {
|
||||||
|
AssertWarn $false "Faza D: niciun .vcx in $ProjectRoot, faza sarita"
|
||||||
|
} else {
|
||||||
|
$vcxRel = Get-RelPath $ProjectRoot $vcxSample.FullName
|
||||||
|
$vcxFull = $vcxSample.FullName
|
||||||
|
$vcxDir = Split-Path $vcxRel -Parent
|
||||||
|
$vcxBase = [IO.Path]::GetFileNameWithoutExtension($vcxRel)
|
||||||
|
$vcxRelText = if ($vcxDir) { Join-Path $vcxDir ($vcxBase + '.vc2') } else { $vcxBase + '.vc2' }
|
||||||
|
Write-Host " esantion negativ .vcx: $vcxRel"
|
||||||
|
Backup-ProjectBinary $vcxFull
|
||||||
|
$origVcxHash = Get-FileHashBytes $vcxFull # baseline curent (independent de backup) pt asertii "neatins"
|
||||||
|
|
||||||
|
& $vcx2txt -Source $vcxFull -ProjectRoot $ProjectRoot -CacheRoot $CacheRoot -Force | Out-Null
|
||||||
|
$cacheText = Join-Path $CacheRoot $vcxRelText
|
||||||
|
Assert (Test-Path -LiteralPath $cacheText) "Faza D: text esantion generat pentru $vcxRel"
|
||||||
|
$origBytes = [IO.File]::ReadAllBytes($cacheText)
|
||||||
|
|
||||||
|
# (a) text stricat: adauga o clasa neinchisa -> recompilare esueaza (doar in cache temp)
|
||||||
|
$text = $script:Enc.GetString($origBytes)
|
||||||
|
$brokenSuffix = "`r`nDEFINE CLASS _brokenzzz AS Custom`r`n"
|
||||||
|
[IO.File]::WriteAllBytes($cacheText, $script:Enc.GetBytes($text + $brokenSuffix))
|
||||||
|
& $txt2vcx -TextFile $cacheText -ProjectRoot $ProjectRoot -CacheRoot $CacheRoot
|
||||||
|
Assert ($LASTEXITCODE -ne 0) "Faza D (a): txt2vcx exit != 0 pe text stricat (DEFINE CLASS neinchis)"
|
||||||
|
Assert ((Get-FileHashBytes $vcxFull) -eq $origVcxHash) "Faza D (a): binarul esantion neatins in proiect"
|
||||||
|
[IO.File]::WriteAllBytes($cacheText, $origBytes)
|
||||||
|
|
||||||
|
# (b) fisier text gol
|
||||||
|
[IO.File]::WriteAllBytes($cacheText, [byte[]]@())
|
||||||
|
& $txt2vcx -TextFile $cacheText -ProjectRoot $ProjectRoot -CacheRoot $CacheRoot
|
||||||
|
Assert ($LASTEXITCODE -ne 0) "Faza D (b): txt2vcx exit != 0 pe fisier text gol"
|
||||||
|
Assert ((Get-FileHashBytes $vcxFull) -eq $origVcxHash) "Faza D (b): binarul esantion neatins in proiect"
|
||||||
|
[IO.File]::WriteAllBytes($cacheText, $origBytes)
|
||||||
|
|
||||||
|
# (guard) staleness: binar mai nou decat textul -> refuz fara -Force
|
||||||
|
$origMtime = (Get-Item -LiteralPath $vcxFull).LastWriteTime
|
||||||
|
(Get-Item -LiteralPath $vcxFull).LastWriteTime = (Get-Date).AddDays(1)
|
||||||
|
try {
|
||||||
|
[IO.File]::WriteAllBytes($cacheText, $origBytes)
|
||||||
|
& $txt2vcx -TextFile $cacheText -ProjectRoot $ProjectRoot -CacheRoot $CacheRoot
|
||||||
|
Assert ($LASTEXITCODE -ne 0) "Faza D (guard staleness): refuz fara -Force cand binarul e mai nou"
|
||||||
|
Assert ((Get-FileHashBytes $vcxFull) -eq $origVcxHash) "Faza D (guard staleness): binar neatins"
|
||||||
|
} finally {
|
||||||
|
(Get-Item -LiteralPath $vcxFull).LastWriteTime = $origMtime
|
||||||
|
}
|
||||||
|
|
||||||
|
# (guard) binar tinta lipsa -> refuz PERMANENT (si cu -Force)
|
||||||
|
$missingCacheText = Join-Path $CacheRoot $(if ($vcxDir) { Join-Path $vcxDir '_nu_exista_deloc.vc2' } else { '_nu_exista_deloc.vc2' })
|
||||||
|
New-Item -ItemType Directory -Force -Path (Split-Path $missingCacheText -Parent) | Out-Null
|
||||||
|
[IO.File]::WriteAllBytes($missingCacheText, $script:Enc.GetBytes("* fake`r`nDEFINE CLASS x AS y`r`nENDDEFINE`r`n"))
|
||||||
|
try {
|
||||||
|
& $txt2vcx -TextFile $missingCacheText -ProjectRoot $ProjectRoot -CacheRoot $CacheRoot -Force
|
||||||
|
Assert ($LASTEXITCODE -ne 0) "Faza D (guard binar lipsa): refuz chiar si cu -Force"
|
||||||
|
} finally { Remove-Item -LiteralPath $missingCacheText -Force -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
|
# (guard) cale text in afara CacheRoot -> refuz
|
||||||
|
$outsideText = Join-Path $env:TEMP ('_outside_' + [Guid]::NewGuid().ToString('N').Substring(0,8) + '.vc2')
|
||||||
|
[IO.File]::WriteAllBytes($outsideText, $origBytes)
|
||||||
|
try {
|
||||||
|
& $txt2vcx -TextFile $outsideText -ProjectRoot $ProjectRoot -CacheRoot $CacheRoot
|
||||||
|
Assert ($LASTEXITCODE -ne 0) "Faza D (guard cale in afara CacheRoot): refuz"
|
||||||
|
} finally { Remove-Item -LiteralPath $outsideText -Force -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
|
# (guard) extensie nesuportata (.mn2)
|
||||||
|
$unsupported = Join-Path $CacheRoot $(if ($vcxDir) { Join-Path $vcxDir '_fake.mn2' } else { '_fake.mn2' })
|
||||||
|
[IO.File]::WriteAllBytes($unsupported, $script:Enc.GetBytes("* fake mn2`r`n"))
|
||||||
|
try {
|
||||||
|
& $txt2vcx -TextFile $unsupported -ProjectRoot $ProjectRoot -CacheRoot $CacheRoot
|
||||||
|
Assert ($LASTEXITCODE -ne 0) "Faza D (guard extensie nesuportata .mn2): refuz"
|
||||||
|
} finally { Remove-Item -LiteralPath $unsupported -Force -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
|
# (guard) COMUN fara -AllowComun -> refuz (-DryRun, zero scrieri)
|
||||||
|
$comunText = Join-Path $CacheRoot 'COMUN\clase\_fake.vc2'
|
||||||
|
New-Item -ItemType Directory -Force -Path (Split-Path $comunText -Parent) | Out-Null
|
||||||
|
[IO.File]::WriteAllBytes($comunText, $origBytes)
|
||||||
|
try {
|
||||||
|
& $txt2vcx -TextFile $comunText -ProjectRoot $ProjectRoot -CacheRoot $CacheRoot -DryRun
|
||||||
|
Assert ($LASTEXITCODE -ne 0) "Faza D (guard COMUN, fara -AllowComun): refuz"
|
||||||
|
} finally { Remove-Item -Recurse -Force -LiteralPath (Split-Path $comunText -Parent) -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
|
# (guard) folder-foxbin2prg -> refuz necondiționat (-DryRun)
|
||||||
|
$fb2pCache = Join-Path $env:TEMP ('_fb2pguard_' + [Guid]::NewGuid().ToString('N').Substring(0,8))
|
||||||
|
New-Item -ItemType Directory -Force -Path $fb2pCache | Out-Null
|
||||||
|
$fb2pText = Join-Path $fb2pCache 'fake.vc2'
|
||||||
|
[IO.File]::WriteAllBytes($fb2pText, $origBytes)
|
||||||
|
try {
|
||||||
|
& $txt2vcx -TextFile $fb2pText -ProjectRoot 'D:\ROA\UTIL\foxbin2prg' -CacheRoot $fb2pCache -DryRun
|
||||||
|
Assert ($LASTEXITCODE -ne 0) "Faza D (guard folder-foxbin2prg): refuz necondiționat"
|
||||||
|
} finally { Remove-Item -Recurse -Force -LiteralPath $fb2pCache -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
|
# (-DryRun caz valid): nimic modificat in proiect, exit 0
|
||||||
|
[IO.File]::WriteAllBytes($cacheText, $origBytes)
|
||||||
|
& $txt2vcx -TextFile $cacheText -ProjectRoot $ProjectRoot -CacheRoot $CacheRoot -DryRun
|
||||||
|
Assert ($LASTEXITCODE -eq 0) "Faza D (-DryRun, caz valid): exit 0"
|
||||||
|
Assert ((Get-FileHashBytes $vcxFull) -eq $origVcxHash) "Faza D (-DryRun, caz valid): binar neatins in proiect"
|
||||||
|
|
||||||
|
# --- multi-fisier: un scx valid (scris) + vcx stricat (refuzat) in aceeasi rulare ---
|
||||||
|
$scxSample = Find-SampleBinary -ProjRoot $ProjectRoot -Ext '.scx'
|
||||||
|
if ($scxSample) {
|
||||||
|
$scxRel = Get-RelPath $ProjectRoot $scxSample.FullName
|
||||||
|
$scxFull = $scxSample.FullName
|
||||||
|
$scxDir = Split-Path $scxRel -Parent
|
||||||
|
$scxBase = [IO.Path]::GetFileNameWithoutExtension($scxRel)
|
||||||
|
$scxRelText = if ($scxDir) { Join-Path $scxDir ($scxBase + '.sc2') } else { $scxBase + '.sc2' }
|
||||||
|
Backup-ProjectBinary $scxFull
|
||||||
|
$scxOrigMtime = (Get-Item -LiteralPath $scxFull).LastWriteTime
|
||||||
|
|
||||||
|
& $vcx2txt -Source $scxFull -ProjectRoot $ProjectRoot -CacheRoot $CacheRoot -Force | Out-Null
|
||||||
|
$scxCacheText = Join-Path $CacheRoot $scxRelText
|
||||||
|
$scxText = $script:Enc.GetString([IO.File]::ReadAllBytes($scxCacheText))
|
||||||
|
$m = [regex]::Match($scxText, "(?m)^\tPROCEDURE \w+\r?\n")
|
||||||
|
if ($m.Success) {
|
||||||
|
$pos = $m.Index + $m.Length
|
||||||
|
$scxText = $scxText.Substring(0,$pos) + "`t`t* test multi`r`n" + $scxText.Substring($pos)
|
||||||
|
[IO.File]::WriteAllBytes($scxCacheText, $script:Enc.GetBytes($scxText))
|
||||||
|
}
|
||||||
|
[IO.File]::WriteAllBytes($cacheText, $script:Enc.GetBytes($text + $brokenSuffix))
|
||||||
|
|
||||||
|
& $txt2vcx -TextFile @($scxCacheText, $cacheText) -ProjectRoot $ProjectRoot -CacheRoot $CacheRoot
|
||||||
|
Assert ($LASTEXITCODE -eq 1) "Faza D (multi-fisier): exit agregat = 1 (unul din doua a esuat)"
|
||||||
|
Assert ((Get-FileHashBytes $vcxFull) -eq $origVcxHash) "Faza D (multi-fisier): vcx stricat neatins in proiect"
|
||||||
|
Assert (((Get-Item -LiteralPath $scxFull).LastWriteTime) -ne $scxOrigMtime) "Faza D (multi-fisier): scx valid a fost scris in proiect (mtime schimbat)"
|
||||||
|
[IO.File]::WriteAllBytes($cacheText, $origBytes)
|
||||||
|
} else {
|
||||||
|
AssertWarn $false "Faza D (multi-fisier): niciun .scx in $ProjectRoot, sarit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Write-Host "`n--- Cleanup final: restore binare din backup + curatare temp ---"
|
||||||
|
Restore-ProjectBinaries
|
||||||
|
if ($ownCache -and (Test-Path -LiteralPath $CacheRoot)) { Remove-Item -Recurse -Force -LiteralPath $CacheRoot -ErrorAction SilentlyContinue }
|
||||||
|
if (Test-Path -LiteralPath $script:BackupRoot) { Remove-Item -Recurse -Force -LiteralPath $script:BackupRoot -ErrorAction SilentlyContinue }
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n=== REZULTAT ==="
|
||||||
|
if ($script:Failures.Count -eq 0) {
|
||||||
|
Write-Host "TOATE testele au trecut (Phase=$Phase)." -ForegroundColor Green
|
||||||
|
exit 0
|
||||||
|
} else {
|
||||||
|
Write-Host "$($script:Failures.Count) test(e) esuate:" -ForegroundColor Red
|
||||||
|
$script:Failures | ForEach-Object { Write-Host " - $_" -ForegroundColor Red }
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
320
txt2vcx.ps1
Normal file
320
txt2vcx.ps1
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Scrie inapoi in proiect binarele VFP (.vcx+.vct / .scx+.sct) regenerate din textul editat
|
||||||
|
in cache (.vc2/.sc2). Fratele "invers" al vcx2txt.ps1.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Flux complet text->bin, cu verificari de siguranta inainte de a atinge proiectul:
|
||||||
|
1. Mapare cache -> proiect (calea text trebuie sa fie sub -CacheRoot).
|
||||||
|
2. Guard-uri: extensie suportata (.vc2/.sc2 - faza 1), tinta nu e sub folderul
|
||||||
|
foxbin2prg, tinta sub COMUN\ necesita -AllowComun, staleness (binarul din
|
||||||
|
proiect mai nou decat textul din cache => refuz fara -Force), binar tinta
|
||||||
|
lipsa => refuz PERMANENT (nici -Force nu trece peste asta).
|
||||||
|
3. Regenerare (Prg2Bin + compilare) intr-un folder de staging unic per rulare.
|
||||||
|
4. Fidelity check IN STAGING (bin -> text, comparat pe octeti cu textul editat,
|
||||||
|
normalizat CRLF) inainte de orice copiere in proiect.
|
||||||
|
5. Copiere in proiect (.vct intai, apoi .vcx), cu backup temporar (nu in proiect)
|
||||||
|
si acces exclusiv verificat inainte.
|
||||||
|
6. Refresh cache: textul canonic de la fidelity check se scrie peste .vc2-ul din
|
||||||
|
cache, cu LastWriteTime >= mtime-ul noilor binare (evita staleness fals la
|
||||||
|
urmatoarea rulare).
|
||||||
|
7. Sumar OK/ESEC per fisier; proceseaza toate fisierele; exit 1 daca vreunul a esuat.
|
||||||
|
|
||||||
|
Encoding: fisierele .vc2/.sc2 sunt Windows-1252 (VFP CODEPAGE=1252). Orice citire/
|
||||||
|
scriere foloseste explicit acest encoding; comparatiile de fidelitate se fac PE
|
||||||
|
OCTETI (ReadAllBytes), normalizate CRLF -> nu pe stringuri cu encoding implicit.
|
||||||
|
|
||||||
|
.PARAMETER TextFile
|
||||||
|
Unul sau mai multe fisiere .vc2/.sc2 (absolute sau relative la locatia curenta),
|
||||||
|
care trebuie sa fie sub -CacheRoot.
|
||||||
|
|
||||||
|
.PARAMETER ProjectRoot
|
||||||
|
Radacina proiectului tinta (implicit D:\ROA\ROAACNPRO, ca vcx2txt.ps1).
|
||||||
|
|
||||||
|
.PARAMETER CacheRoot
|
||||||
|
Radacina versiunilor text. Implicit egala cu ProjectRoot (flux text in-arbore,
|
||||||
|
langa binare): maparea cache->proiect devine identitate, iar refresh-ul textului
|
||||||
|
e in-place. Da o radacina separata pentru fluxul vechi cu cache extern.
|
||||||
|
|
||||||
|
.PARAMETER AllowComun
|
||||||
|
Obligatoriu pentru a scrie tinte aflate sub COMUN\ (repo partajat intre toate
|
||||||
|
aplicatiile ROA - blast radius mare).
|
||||||
|
|
||||||
|
.PARAMETER Force
|
||||||
|
Sare peste verificarea de staleness (binar mai nou decat textul din cache).
|
||||||
|
NU acopera cazul "binar tinta lipsa" - acela e refuz permanent.
|
||||||
|
|
||||||
|
.PARAMETER NoVerify
|
||||||
|
Sare peste fidelity check (bin->text->compara). Cache-ul tot se reimprospateaza
|
||||||
|
(ruleaza bin->text pe binarul nou doar pentru refresh).
|
||||||
|
|
||||||
|
.PARAMETER DryRun
|
||||||
|
Ruleaza maparea, guard-urile si regenerarea/compilarea/fidelity check-ul IN STAGING,
|
||||||
|
dar nu copiaza nimic in proiect si nu atinge cache-ul.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\txt2vcx.ps1 `
|
||||||
|
-TextFile 'D:\ROA\_vfp_textcache\roagest\Clase\atentie.vc2' `
|
||||||
|
-ProjectRoot 'D:\ROA\ROAGEST' -CacheRoot 'D:\ROA\_vfp_textcache\roagest'
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)][string[]]$TextFile,
|
||||||
|
[string]$ProjectRoot = 'D:\ROA\ROAACNPRO',
|
||||||
|
[string]$CacheRoot = $ProjectRoot,
|
||||||
|
[switch]$AllowComun,
|
||||||
|
[switch]$Force,
|
||||||
|
[switch]$NoVerify,
|
||||||
|
[switch]$DryRun
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$exe = 'D:\ROA\UTIL\foxbin2prg\FoxBin2Prg.EXE'
|
||||||
|
if (-not (Test-Path $exe)) {
|
||||||
|
throw "Nu gasesc $exe. Compileaza intai FoxBin2Prg (deschide foxbin2prg.pj2 in VFP si Build)."
|
||||||
|
}
|
||||||
|
. (Join-Path $PSScriptRoot 'vfp_filemap.ps1')
|
||||||
|
|
||||||
|
$script:Enc = [Text.Encoding]::GetEncoding(1252)
|
||||||
|
$script:FoxBin2PrgFolder = 'D:\ROA\UTIL\foxbin2prg'
|
||||||
|
|
||||||
|
function Normalize-Crlf([byte[]]$bytes) {
|
||||||
|
# normalizeaza CRLF -> LF, pe octeti, ca sa compare fidelity-ul indiferent de terminatorul de linie
|
||||||
|
$out = New-Object System.Collections.Generic.List[byte]
|
||||||
|
for ($i = 0; $i -lt $bytes.Length; $i++) {
|
||||||
|
if ($bytes[$i] -eq 0x0D) {
|
||||||
|
if ($i + 1 -lt $bytes.Length -and $bytes[$i+1] -eq 0x0A) { continue } # skip CR of CRLF
|
||||||
|
$out.Add(0x0A) | Out-Null # CR izolat -> LF
|
||||||
|
} else {
|
||||||
|
$out.Add($bytes[$i]) | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $out.ToArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-BytesEqual([byte[]]$a, [byte[]]$b) {
|
||||||
|
$na = Normalize-Crlf $a
|
||||||
|
$nb = Normalize-Crlf $b
|
||||||
|
if ($na.Length -ne $nb.Length) { return $false }
|
||||||
|
for ($i = 0; $i -lt $na.Length; $i++) { if ($na[$i] -ne $nb[$i]) { return $false } }
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-ExclusiveAccess([string]$path) {
|
||||||
|
try {
|
||||||
|
$fs = [IO.File]::Open($path, [IO.FileMode]::Open, [IO.FileAccess]::ReadWrite, [IO.FileShare]::None)
|
||||||
|
$fs.Close()
|
||||||
|
return $true
|
||||||
|
} catch {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-FoxBin2Prg {
|
||||||
|
# Watchdog obligatoriu: FoxBin2Prg.EXE poate afisa un MessageBox MODAL nativ al
|
||||||
|
# motorului VFP (nu al propriului error-handler) cand recompilarea unui text stricat
|
||||||
|
# esueaza - cDontShowErrors='1' NU suprima acest dialog. Fara watchdog, o rulare
|
||||||
|
# automata ar ramane agatata la infinit. Orice timeout => tratat ca ESEC (ExitCode -1).
|
||||||
|
param([string]$InputFile, [string]$Recompile = '', [int]$TimeoutSeconds = 60)
|
||||||
|
if ($Recompile) {
|
||||||
|
$r = Invoke-FoxBin2PrgSafe -ExePath $exe -ExeArgs @($InputFile,'','','','1','0','1','',$Recompile) -TimeoutSeconds $TimeoutSeconds
|
||||||
|
} else {
|
||||||
|
$r = Invoke-FoxBin2PrgSafe -ExePath $exe -ExeArgs @($InputFile,'','','','1','0','1') -TimeoutSeconds $TimeoutSeconds
|
||||||
|
}
|
||||||
|
if ($r.TimedOut) { Write-Warning "Invoke-FoxBin2Prg: timeout pe '$InputFile' - tratat ca ESEC." }
|
||||||
|
return $r.ExitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Summary($results) {
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host '--- Sumar txt2vcx ---'
|
||||||
|
$fail = 0
|
||||||
|
foreach ($r in $results) {
|
||||||
|
if ($r.Ok) { Write-Host "OK $($r.Text)" }
|
||||||
|
else { Write-Host "ESEC $($r.Text) -- $($r.Reason)"; $fail++ }
|
||||||
|
}
|
||||||
|
Write-Host ''
|
||||||
|
if ($fail -gt 0) {
|
||||||
|
Write-Host "$fail/$($results.Count) fisier(e) au esuat."
|
||||||
|
Write-Host 'Ruleaza git diff --stat in proiect pentru a revizui ce s-a schimbat.'
|
||||||
|
} else {
|
||||||
|
Write-Host "Toate cele $($results.Count) fisier(e) au fost scrise cu succes."
|
||||||
|
Write-Host 'Ruleaza git diff --stat in proiect pentru a revizui ce s-a schimbat.'
|
||||||
|
}
|
||||||
|
return $fail
|
||||||
|
}
|
||||||
|
|
||||||
|
$results = @()
|
||||||
|
$runStamp = "{0}-{1}" -f $PID, (Get-Date -Format 'yyyyMMddHHmmssfff')
|
||||||
|
$stagingRoot = Join-Path $env:TEMP ("txt2bin_" + (Split-Path $ProjectRoot -Leaf) + "\$runStamp")
|
||||||
|
|
||||||
|
foreach ($tf in $TextFile) {
|
||||||
|
$reason = $null
|
||||||
|
$ok = $false
|
||||||
|
$stagingDir = $null
|
||||||
|
try {
|
||||||
|
# --- resolve full path fara sa ceara existenta anterioara garantata pe alte disk-uri ---
|
||||||
|
$fullTextPath = if ([IO.Path]::IsPathRooted($tf)) { $tf } else { Join-Path (Get-Location) $tf }
|
||||||
|
if (-not (Test-Path -LiteralPath $fullTextPath -PathType Leaf)) {
|
||||||
|
throw "Fisierul text nu exista: $fullTextPath"
|
||||||
|
}
|
||||||
|
$fullTextPath = (Get-Item -LiteralPath $fullTextPath).FullName
|
||||||
|
|
||||||
|
# --- 1. Mapare cache -> proiect ---
|
||||||
|
$cacheRootFull = (Get-Item -LiteralPath $CacheRoot).FullName
|
||||||
|
if (-not $fullTextPath.ToLower().StartsWith($cacheRootFull.ToLower() + '\')) {
|
||||||
|
throw "Calea text trebuie sa fie sub CacheRoot ($cacheRootFull): $fullTextPath"
|
||||||
|
}
|
||||||
|
$relText = $fullTextPath.Substring($cacheRootFull.Length).TrimStart('\')
|
||||||
|
$textExt = [IO.Path]::GetExtension($fullTextPath).ToLower()
|
||||||
|
|
||||||
|
# --- 2a. Guard extensie (doar .vc2/.sc2 in faza 1) ---
|
||||||
|
$pair = Get-VfpBinExtensionForTextExtension -TextExtension $textExt
|
||||||
|
if (-not $pair -or ($pair[0] -ne '.vcx' -and $pair[0] -ne '.scx')) {
|
||||||
|
throw "Extensie nesuportata in faza 2 (doar .vc2/.sc2): $textExt"
|
||||||
|
}
|
||||||
|
$binExt = $pair[0]
|
||||||
|
$memoExt = $pair[1]
|
||||||
|
|
||||||
|
$relBinNoExt = [IO.Path]::ChangeExtension($relText, $null).TrimEnd('.')
|
||||||
|
$relBin = [IO.Path]::ChangeExtension($relText, $binExt)
|
||||||
|
$binDir = Split-Path (Join-Path $ProjectRoot $relBin) -Parent
|
||||||
|
$binBaseName = [IO.Path]::GetFileNameWithoutExtension($relBin)
|
||||||
|
|
||||||
|
# --- 2b. Guard: refuz necondiționat sub folderul foxbin2prg ---
|
||||||
|
$projectRootFull = if (Test-Path -LiteralPath $ProjectRoot) { (Get-Item -LiteralPath $ProjectRoot).FullName } else { $ProjectRoot }
|
||||||
|
$prospectiveBinFull = Join-Path $projectRootFull $relBin
|
||||||
|
if ($prospectiveBinFull.ToLower().StartsWith($script:FoxBin2PrgFolder.ToLower() + '\')) {
|
||||||
|
throw "Refuz: tinta sub folderul foxbin2prg ($script:FoxBin2PrgFolder) - nu e permis sa scrii aici."
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 2c. Guard COMUN ---
|
||||||
|
if ($relBin -match '(?i)^COMUN\\' -and -not $AllowComun) {
|
||||||
|
throw "Tinta e sub COMUN\ (repo partajat) - ruleaza cu -AllowComun daca esti sigur."
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Rezolva numele real (casing) al binarului si memo-ului din director ---
|
||||||
|
if (-not (Test-Path -LiteralPath $binDir -PathType Container)) {
|
||||||
|
throw "Director tinta lipsa: $binDir"
|
||||||
|
}
|
||||||
|
$binEntry = Get-ChildItem -LiteralPath $binDir -Filter ($binBaseName + $binExt) -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||||
|
$memoEntry = Get-ChildItem -LiteralPath $binDir -Filter ($binBaseName + $memoExt) -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||||
|
|
||||||
|
# --- 2d. Guard: binar tinta lipsa -> refuz PERMANENT, -Force nu trece peste asta ---
|
||||||
|
if (-not $binEntry -or -not $memoEntry) {
|
||||||
|
throw "Binarul/memo-ul tinta nu exista in proiect ($binBaseName$binExt / $binBaseName$memoExt) - fisier nou, trebuie creat in IDE (SET CLASSLIB/.pjx) inainte de write-back."
|
||||||
|
}
|
||||||
|
$binFull = $binEntry.FullName
|
||||||
|
$memoFull = $memoEntry.FullName
|
||||||
|
|
||||||
|
# --- 2e. Guard staleness ---
|
||||||
|
$binMtime = $binEntry.LastWriteTime
|
||||||
|
$memoMtime = $memoEntry.LastWriteTime
|
||||||
|
$newestBinMtime = if ($binMtime -gt $memoMtime) { $binMtime } else { $memoMtime }
|
||||||
|
$textMtime = (Get-Item -LiteralPath $fullTextPath).LastWriteTime
|
||||||
|
if ($newestBinMtime -gt $textMtime -and -not $Force) {
|
||||||
|
throw "Staleness: binarul/memo-ul din proiect ($newestBinMtime) e mai nou decat textul din cache ($textMtime) - probabil modificat in IDE. Ruleaza vcx2txt.ps1 -Force intai, sau -Force aici daca esti sigur ca vrei sa suprascrii."
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 3. Regenerare in staging ---
|
||||||
|
$stagingDir = Join-Path $stagingRoot $relBinNoExt
|
||||||
|
New-Item -ItemType Directory -Force -Path $stagingDir | Out-Null
|
||||||
|
# cfg in staging: DontShowErrors suprima MessageBox-ul propriu la write-back esuat (mostenit de verify/refresh)
|
||||||
|
Set-Content -LiteralPath (Join-Path $stagingDir 'foxbin2prg.cfg') -Value @('DontShowErrors: 1','ShowProgressbar: 0') -Encoding Ascii
|
||||||
|
$stagingText = Join-Path $stagingDir ($binBaseName + $textExt)
|
||||||
|
Copy-Item -LiteralPath $fullTextPath -Destination $stagingText -Force
|
||||||
|
|
||||||
|
$rc = Invoke-FoxBin2Prg -InputFile $stagingText -Recompile $ProjectRoot
|
||||||
|
$stagingBin = Join-Path $stagingDir ($binBaseName + $binExt)
|
||||||
|
$stagingMemo = Join-Path $stagingDir ($binBaseName + $memoExt)
|
||||||
|
if ($rc -ne 0 -or -not (Test-Path -LiteralPath $stagingBin) -or -not (Test-Path -LiteralPath $stagingMemo)) {
|
||||||
|
throw "Prg2Bin/compilare esuata (ErrorLevel=$rc) sau lipsesc $binExt/$memoExt in staging."
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 4. Fidelity check IN STAGING ---
|
||||||
|
$canonicalText = $null
|
||||||
|
if (-not $NoVerify) {
|
||||||
|
$verifyDir = Join-Path $stagingDir 'verify'
|
||||||
|
New-Item -ItemType Directory -Force -Path $verifyDir | Out-Null
|
||||||
|
$verifyBin = Join-Path $verifyDir ($binBaseName + $binExt)
|
||||||
|
$verifyMemo = Join-Path $verifyDir ($binBaseName + $memoExt)
|
||||||
|
Copy-Item -LiteralPath $stagingBin -Destination $verifyBin -Force
|
||||||
|
Copy-Item -LiteralPath $stagingMemo -Destination $verifyMemo -Force
|
||||||
|
$rc2 = Invoke-FoxBin2Prg -InputFile $verifyBin
|
||||||
|
$verifyText = Join-Path $verifyDir ($binBaseName + $textExt)
|
||||||
|
if ($rc2 -ne 0 -or -not (Test-Path -LiteralPath $verifyText)) {
|
||||||
|
throw "Fidelity check: Bin2Prg pe binarul regenerat a esuat (ErrorLevel=$rc2) - probabil text stricat (ex. lipsa ENDPROC)."
|
||||||
|
}
|
||||||
|
$editedBytes = [IO.File]::ReadAllBytes($stagingText)
|
||||||
|
$verifyBytes = [IO.File]::ReadAllBytes($verifyText)
|
||||||
|
if (-not (Test-BytesEqual $editedBytes $verifyBytes)) {
|
||||||
|
throw "Fidelity check: textul regenerat difera de textul editat (posibil proprietate nealfabetizata sau linie reformatata)."
|
||||||
|
}
|
||||||
|
$canonicalText = $verifyText
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($DryRun) {
|
||||||
|
$ok = $true
|
||||||
|
$reason = '(dry-run: regenerare+fidelity OK in staging, nimic copiat in proiect)'
|
||||||
|
} else {
|
||||||
|
# --- 5. Copiere in proiect ---
|
||||||
|
if (-not (Test-ExclusiveAccess $binFull) -or -not (Test-ExclusiveAccess $memoFull)) {
|
||||||
|
throw "Nu pot obtine acces exclusiv la $binBaseName$binExt/$memoExt - inchide IDE-ul VFP si reincearca."
|
||||||
|
}
|
||||||
|
$backupDir = Join-Path $stagingDir 'backup_proiect'
|
||||||
|
New-Item -ItemType Directory -Force -Path $backupDir | Out-Null
|
||||||
|
$backupBin = Join-Path $backupDir ($binBaseName + $binExt)
|
||||||
|
$backupMemo = Join-Path $backupDir ($binBaseName + $memoExt)
|
||||||
|
Copy-Item -LiteralPath $binFull -Destination $backupBin -Force
|
||||||
|
Copy-Item -LiteralPath $memoFull -Destination $backupMemo -Force
|
||||||
|
|
||||||
|
Copy-Item -LiteralPath $stagingMemo -Destination $memoFull -Force
|
||||||
|
try {
|
||||||
|
Copy-Item -LiteralPath $stagingBin -Destination $binFull -Force
|
||||||
|
} catch {
|
||||||
|
# restaureaza memo-ul vechi din backup, ca sa nu ramana o pereche rupta
|
||||||
|
Copy-Item -LiteralPath $backupMemo -Destination $memoFull -Force
|
||||||
|
throw "Copierea $binExt a esuat, memo-ul a fost restaurat din backup: $_"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 6. Refresh cache ---
|
||||||
|
if (-not $NoVerify -and $canonicalText) {
|
||||||
|
Copy-Item -LiteralPath $canonicalText -Destination $fullTextPath -Force
|
||||||
|
} else {
|
||||||
|
# fara fidelity text disponibil (NoVerify) - ruleaza bin->text pe binarul nou doar pentru refresh
|
||||||
|
$refreshDir = Join-Path $stagingDir 'refresh'
|
||||||
|
New-Item -ItemType Directory -Force -Path $refreshDir | Out-Null
|
||||||
|
$refreshBin = Join-Path $refreshDir ($binBaseName + $binExt)
|
||||||
|
$refreshMemo = Join-Path $refreshDir ($binBaseName + $memoExt)
|
||||||
|
Copy-Item -LiteralPath $binFull -Destination $refreshBin -Force
|
||||||
|
Copy-Item -LiteralPath $memoFull -Destination $refreshMemo -Force
|
||||||
|
Invoke-FoxBin2Prg -InputFile $refreshBin | Out-Null
|
||||||
|
$refreshText = Join-Path $refreshDir ($binBaseName + $textExt)
|
||||||
|
if (Test-Path -LiteralPath $refreshText) {
|
||||||
|
Copy-Item -LiteralPath $refreshText -Destination $fullTextPath -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$newBinMtime = (Get-Item -LiteralPath $binFull).LastWriteTime
|
||||||
|
$newMemoMtime = (Get-Item -LiteralPath $memoFull).LastWriteTime
|
||||||
|
$newestMtime = if ($newBinMtime -gt $newMemoMtime) { $newBinMtime } else { $newMemoMtime }
|
||||||
|
(Get-Item -LiteralPath $fullTextPath).LastWriteTime = $newestMtime.AddSeconds(1)
|
||||||
|
|
||||||
|
$ok = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
# curatare staging la succes
|
||||||
|
if ($ok -and -not $DryRun) {
|
||||||
|
Remove-Item -Recurse -Force -LiteralPath $stagingDir -ErrorAction SilentlyContinue
|
||||||
|
} elseif ($ok -and $DryRun) {
|
||||||
|
Remove-Item -Recurse -Force -LiteralPath $stagingDir -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
$ok = $false
|
||||||
|
$reason = $_.Exception.Message
|
||||||
|
if ($stagingDir) { Write-Warning "Staging pastrat pentru diagnostic: $stagingDir" }
|
||||||
|
}
|
||||||
|
$results += [PSCustomObject]@{ Text = $tf; Ok = $ok; Reason = $reason }
|
||||||
|
}
|
||||||
|
|
||||||
|
$failCount = Write-Summary $results
|
||||||
|
exit ([int]($failCount -gt 0))
|
||||||
168
vcx2txt.ps1
Normal file
168
vcx2txt.ps1
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Converteste in TEXT (.vc2/.sc2/.fr2/.mn2/.dc2/.lb2) librariile/formele VFP binare,
|
||||||
|
ca sa poata fi cautat codul din proceduri/metode cu grep.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Implicit ("project mode"), converteste DOAR fisierele incluse in proiect (citite din .pjx),
|
||||||
|
nu tot folderul COMUN (care contine multe librarii nefolosite in ROAACNPRO).
|
||||||
|
|
||||||
|
Pentru fiecare fisier copiaza binarul + memo-ul (.vct/.sct/...) intr-un folder de cache care
|
||||||
|
oglindeste structura proiectului (ca sa NU murdareasca arborele SVN), ruleaza FoxBin2Prg.exe
|
||||||
|
acolo, si pastreaza doar fisierul TEXT. Incremental: sare peste ce e deja la zi.
|
||||||
|
|
||||||
|
NU e nevoie de IDE-ul VFP deschis (se foloseste FoxBin2Prg.exe compilat).
|
||||||
|
|
||||||
|
.PARAMETER Source
|
||||||
|
Optional. Daca e dat, ignora proiectul si converteste exact: un fisier .vcx/.scx/... SAU un
|
||||||
|
folder (recursiv). Folosit pentru conversii punctuale.
|
||||||
|
|
||||||
|
.PARAMETER Project
|
||||||
|
Fisierul .pjx din care se citeste lista de fisiere (implicit roaacnpro.PJX).
|
||||||
|
|
||||||
|
.PARAMETER Types
|
||||||
|
Ce extensii sa includa in project mode (implicit vcx,scx = cod). Ex: -Types vcx,scx,frx,mnx
|
||||||
|
|
||||||
|
.PARAMETER ProjectRoot
|
||||||
|
Radacina proiectului (implicit D:\ROA\ROAACNPRO). Caile din .pjx sunt relative la ea.
|
||||||
|
|
||||||
|
.PARAMETER CacheRoot
|
||||||
|
Unde se scrie textul (implicit D:\ROA\_vfp_textcache\roacnpro).
|
||||||
|
IMPORTANT: tine cache-ul in AFARA folderului foxbin2prg, altfel ReCreate_FoxBin2Prg.prg
|
||||||
|
(care face Prg2Bin recursiv pe tot folderul tool-ului) reconverteste .??2-urile din cache in binare.
|
||||||
|
|
||||||
|
.PARAMETER Clean
|
||||||
|
Sterge tot cache-ul inainte de conversie (util dupa ce s-au convertit accidental fisiere extra).
|
||||||
|
|
||||||
|
.PARAMETER Force
|
||||||
|
Reconverteste chiar daca textul exista si e mai nou decat sursa.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
# doar fisierele proiectului (recomandat, rapid)
|
||||||
|
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
# include si rapoartele si meniurile
|
||||||
|
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1 -Types vcx,scx,frx,mnx
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
# conversie punctuala a unei librarii (chiar daca nu e in proiect)
|
||||||
|
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1 -Source D:\ROA\ROAACNPRO\COMUN\clase\caut.vcx
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$Source,
|
||||||
|
[string]$Project = 'D:\ROA\ROAACNPRO\roaacnpro.PJX',
|
||||||
|
[string[]]$Types = @('vcx','scx'),
|
||||||
|
[string]$ProjectRoot = 'D:\ROA\ROAACNPRO',
|
||||||
|
[string]$CacheRoot = 'D:\ROA\_vfp_textcache\roacnpro',
|
||||||
|
[switch]$Clean,
|
||||||
|
[switch]$Force
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$exe = 'D:\ROA\UTIL\foxbin2prg\FoxBin2Prg.EXE'
|
||||||
|
if (-not (Test-Path $exe)) {
|
||||||
|
throw "Nu gasesc $exe. Compileaza intai FoxBin2Prg (deschide foxbin2prg.pj2 in VFP si Build)."
|
||||||
|
}
|
||||||
|
|
||||||
|
# binar -> (memo, extensie text) -- harta comuna, partajata cu txt2vcx.ps1
|
||||||
|
. (Join-Path $PSScriptRoot 'vfp_filemap.ps1')
|
||||||
|
$map = $script:VfpFileMap
|
||||||
|
|
||||||
|
function Convert-One($fullPath) {
|
||||||
|
$f = Get-Item -LiteralPath $fullPath
|
||||||
|
$ext = $f.Extension.ToLower()
|
||||||
|
if (-not $map.ContainsKey($ext)) { return 'skip' }
|
||||||
|
$memoX = $map[$ext][0]; $txtX = $map[$ext][1]
|
||||||
|
$memo = [IO.Path]::ChangeExtension($f.FullName, $memoX)
|
||||||
|
|
||||||
|
$rel = $f.FullName
|
||||||
|
if ($rel.ToLower().StartsWith($ProjectRoot.ToLower())) {
|
||||||
|
$rel = $rel.Substring($ProjectRoot.Length).TrimStart('\')
|
||||||
|
} else { $rel = $f.Name }
|
||||||
|
$relDir = Split-Path $rel -Parent
|
||||||
|
$workDir = if ($relDir) { Join-Path $CacheRoot $relDir } else { $CacheRoot }
|
||||||
|
$txtOut = Join-Path $workDir ([IO.Path]::GetFileNameWithoutExtension($f.Name) + $txtX)
|
||||||
|
|
||||||
|
if (-not $Force -and (Test-Path $txtOut) -and ((Get-Item $txtOut).LastWriteTime -ge $f.LastWriteTime)) {
|
||||||
|
return 'uptodate'
|
||||||
|
}
|
||||||
|
if (-not (Test-Path $workDir)) { New-Item -ItemType Directory -Force -Path $workDir | Out-Null }
|
||||||
|
|
||||||
|
$binCopy = Join-Path $workDir $f.Name
|
||||||
|
Copy-Item -LiteralPath $f.FullName -Destination $binCopy -Force
|
||||||
|
if (Test-Path $memo) { Copy-Item -LiteralPath $memo -Destination (Join-Path $workDir (Split-Path $memo -Leaf)) -Force }
|
||||||
|
|
||||||
|
# FoxBin2Prg.EXE e aplicatie GUI: "&" poate preda controlul inainte de final, iar stergerea
|
||||||
|
# binarului copiat ar omori conversia in curs -> Start-Process -Wait asteapta terminarea reala.
|
||||||
|
$started = Get-Date
|
||||||
|
$outLog = Join-Path $workDir ([IO.Path]::GetFileNameWithoutExtension($f.Name) + '.fb2p_out.tmp')
|
||||||
|
Start-Process -FilePath $exe -ArgumentList "`"$binCopy`" `"`" `"`" `"`" 1 0 1" -Wait -WindowStyle Hidden -RedirectStandardOutput $outLog
|
||||||
|
$exeOut = ''
|
||||||
|
if (Test-Path $outLog) { $exeOut = Get-Content -Raw $outLog; Remove-Item -LiteralPath $outLog -Force -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
|
Remove-Item -LiteralPath $binCopy -Force -ErrorAction SilentlyContinue
|
||||||
|
$memoCopy = Join-Path $workDir (Split-Path $memo -Leaf)
|
||||||
|
if (Test-Path $memoCopy) { Remove-Item -LiteralPath $memoCopy -Force -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
|
# succes = textul rescris acum SAU exe-ul raporteaza 0 erori (linia "O,P1,E0,S1,X0,<txt>").
|
||||||
|
# FoxBin2Prg NU actualizeaza mtime cand textul regenerat e identic cu cel existent (ex. binar
|
||||||
|
# reatins de Project > Build fara schimbari reale) -> sincronizam mtime-ul textului, altfel
|
||||||
|
# fisierul pare stale si se reconverteste la fiecare rulare.
|
||||||
|
$fresh = (Test-Path $txtOut) -and ((Get-Item $txtOut).LastWriteTime -ge $started)
|
||||||
|
if ($fresh -or ((Test-Path $txtOut) -and ($exeOut -match ',E0,'))) {
|
||||||
|
if ((Get-Item $txtOut).LastWriteTime -lt $f.LastWriteTime) { (Get-Item $txtOut).LastWriteTime = Get-Date }
|
||||||
|
return 'ok'
|
||||||
|
}
|
||||||
|
return 'fail'
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Construieste lista de fisiere ---
|
||||||
|
$list = @()
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($Source)) {
|
||||||
|
if (Test-Path $Source -PathType Leaf) { $list = @((Get-Item -LiteralPath $Source).FullName) }
|
||||||
|
elseif (Test-Path $Source -PathType Container) {
|
||||||
|
$list = Get-ChildItem -LiteralPath $Source -Recurse -File |
|
||||||
|
Where-Object { $map.ContainsKey($_.Extension.ToLower()) } |
|
||||||
|
ForEach-Object { $_.FullName }
|
||||||
|
} else { throw "Sursa nu exista: $Source" }
|
||||||
|
} else {
|
||||||
|
# PROJECT MODE: citeste .pjx -> pj2 si extrage fisierele
|
||||||
|
if (-not (Test-Path $Project)) { throw "Nu gasesc proiectul: $Project" }
|
||||||
|
$tmp = Join-Path $env:TEMP ('pjx_' + [IO.Path]::GetFileNameWithoutExtension($Project))
|
||||||
|
New-Item -ItemType Directory -Force -Path $tmp | Out-Null
|
||||||
|
$pjxName = [IO.Path]::GetFileName($Project)
|
||||||
|
Copy-Item -LiteralPath $Project -Destination (Join-Path $tmp $pjxName) -Force
|
||||||
|
$pjt = [IO.Path]::ChangeExtension($Project, '.pjt')
|
||||||
|
if (Test-Path $pjt) { Copy-Item -LiteralPath $pjt -Destination (Join-Path $tmp ([IO.Path]::GetFileName($pjt))) -Force }
|
||||||
|
& $exe (Join-Path $tmp $pjxName) '' '' '' '1' '0' '1' | Out-Null
|
||||||
|
$pj2 = Join-Path $tmp ([IO.Path]::GetFileNameWithoutExtension($Project) + '.pj2')
|
||||||
|
if (-not (Test-Path $pj2)) { throw "Conversia .pjx a esuat: $pj2" }
|
||||||
|
|
||||||
|
$extPat = ($Types | ForEach-Object { [regex]::Escape($_) }) -join '|'
|
||||||
|
$rx = [regex]("(?i)\.ADD\('([^']*\.(?:$extPat))'\)")
|
||||||
|
foreach ($m in $rx.Matches((Get-Content -Raw $pj2))) {
|
||||||
|
$relp = $m.Groups[1].Value
|
||||||
|
$abs = Join-Path $ProjectRoot $relp
|
||||||
|
if (Test-Path $abs) { $list += (Get-Item -LiteralPath $abs).FullName }
|
||||||
|
else { Write-Warning "lipsa: $relp" }
|
||||||
|
}
|
||||||
|
$list = $list | Sort-Object -Unique
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Clean -and (Test-Path $CacheRoot)) { Remove-Item -Recurse -Force $CacheRoot }
|
||||||
|
if (-not $list) { Write-Host 'Nimic de convertit.'; return }
|
||||||
|
|
||||||
|
$ok=0;$up=0;$fail=0
|
||||||
|
foreach ($p in $list) {
|
||||||
|
switch (Convert-One $p) {
|
||||||
|
'ok' { $ok++; $r = $p; if ($r.ToLower().StartsWith($ProjectRoot.ToLower())) { $r = $r.Substring($ProjectRoot.Length).TrimStart('\') }; Write-Host "OK $r" }
|
||||||
|
'uptodate' { $up++ }
|
||||||
|
'fail' { $fail++; Write-Warning "ESEC $p" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host "Gata. Convertite: $ok Sarite(la zi): $up Esuate: $fail (tipuri: $($Types -join ','))"
|
||||||
|
Write-Host "Text in: $CacheRoot"
|
||||||
|
Write-Host "Cauta cu: grep -rn 'expresie' `"$CacheRoot`""
|
||||||
81
vfp_filemap.ps1
Normal file
81
vfp_filemap.ps1
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Harta comuna bin<->memo<->text pentru conversiile VFP (FoxBin2Prg), dot-sourced de
|
||||||
|
vcx2txt.ps1 (Bin2Prg) si txt2vcx.ps1 (Prg2Bin), ca sa nu existe doua copii ale hartii.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Defineste $script:VfpFileMap: extensie-binar (lowercase, cu punct) -> @(extensie-memo, extensie-text).
|
||||||
|
Si o functie inversa Get-VfpBinExtensionForTextExtension pentru txt2vcx.ps1 (text -> binar).
|
||||||
|
#>
|
||||||
|
|
||||||
|
$script:VfpFileMap = @{
|
||||||
|
'.vcx' = @('.vct', '.vc2'); '.scx' = @('.sct', '.sc2'); '.frx' = @('.frt', '.fr2')
|
||||||
|
'.lbx' = @('.lbt', '.lb2'); '.mnx' = @('.mnt', '.mn2'); '.dbc' = @('.dct', '.dc2')
|
||||||
|
'.pjx' = @('.pjt', '.pj2'); '.dbf' = @('.fpt', '.db2')
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-VfpBinExtensionForTextExtension {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Data o extensie text (.vc2, .sc2, ...), intoarce @(extensie-binar, extensie-memo), sau $null daca nu e cunoscuta.
|
||||||
|
#>
|
||||||
|
param([Parameter(Mandatory)][string]$TextExtension)
|
||||||
|
$t = $TextExtension.ToLower()
|
||||||
|
foreach ($binExt in $script:VfpFileMap.Keys) {
|
||||||
|
if ($script:VfpFileMap[$binExt][1] -eq $t) {
|
||||||
|
return @($binExt, $script:VfpFileMap[$binExt][0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-FoxBin2PrgSafe {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Ruleaza FoxBin2Prg.EXE cu un watchdog de timp, ca sa nu ramana blocat la infinit.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
FoxBin2Prg.EXE poate afisa un MessageBox MODAL nativ al motorului VFP (nu al
|
||||||
|
propriului sau error-handler) cand recompilarea textului esueaza (ex. text
|
||||||
|
stricat, ENDPROC lipsa) - vazut concret: "Error 1098, Procedure not closed...".
|
||||||
|
Parametrul cDontShowErrors='1' NU suprima acest dialog (acela controleaza doar
|
||||||
|
raportarea de erori proprie a FoxBin2Prg, nu eroarea nativa de compilare VFP).
|
||||||
|
Fara acest watchdog, un rulaj automat/CI ar ramane agatat la infinit asteptand
|
||||||
|
un om sa apese OK. Orice timeout se trateaza ca ESEC pentru fisierul respectiv.
|
||||||
|
|
||||||
|
.PARAMETER ExePath
|
||||||
|
Calea catre FoxBin2Prg.EXE.
|
||||||
|
|
||||||
|
.PARAMETER ExeArgs
|
||||||
|
Lista de argumente pozitionale, in ordinea folosita de FoxBin2Prg (inclusiv
|
||||||
|
string-urile goale intre ele, ca in apelul cu "&").
|
||||||
|
|
||||||
|
.PARAMETER TimeoutSeconds
|
||||||
|
Cat asteapta procesul sa termine normal inainte sa-l omoare (implicit 60s -
|
||||||
|
conversiile uzuale dureaza sub o secunda; un timeout mare inseamna aproape
|
||||||
|
sigur un dialog modal blocat).
|
||||||
|
|
||||||
|
.OUTPUTS
|
||||||
|
PSCustomObject cu TimedOut (bool) si ExitCode (int; -1 daca a fost omorat la timeout).
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)][string]$ExePath,
|
||||||
|
[Parameter(Mandatory)][AllowEmptyCollection()][AllowEmptyString()][string[]]$ExeArgs,
|
||||||
|
[int]$TimeoutSeconds = 60
|
||||||
|
)
|
||||||
|
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||||
|
$psi.FileName = $ExePath
|
||||||
|
$psi.Arguments = ($ExeArgs | ForEach-Object { '"' + ($_ -replace '"', '\"') + '"' }) -join ' '
|
||||||
|
$psi.UseShellExecute = $false
|
||||||
|
|
||||||
|
$proc = [System.Diagnostics.Process]::Start($psi)
|
||||||
|
$finished = $proc.WaitForExit($TimeoutSeconds * 1000)
|
||||||
|
if (-not $finished) {
|
||||||
|
Write-Warning "Invoke-FoxBin2PrgSafe: TIMEOUT dupa $TimeoutSeconds s (probabil MessageBox modal blocat) - omor procesul (PID $($proc.Id)) si eventualele lui ferestre/copii."
|
||||||
|
try { & taskkill /PID $proc.Id /T /F 2>&1 | Out-Null } catch {}
|
||||||
|
try { if (-not $proc.HasExited) { $proc.Kill() } } catch {}
|
||||||
|
Start-Sleep -Milliseconds 300
|
||||||
|
return [PSCustomObject]@{ TimedOut = $true; ExitCode = -1 }
|
||||||
|
}
|
||||||
|
return [PSCustomObject]@{ TimedOut = $false; ExitCode = $proc.ExitCode }
|
||||||
|
}
|
||||||
321
vfp_symbols.ps1
Normal file
321
vfp_symbols.ps1
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Index de simboluri (clase, metode, proceduri) peste cache-ul text VFP si peste .prg-urile
|
||||||
|
proiectului. Raspunde la "in ce clasa/metoda e linia N" si "unde e definit X".
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Grep-ul gaseste textul, dar intr-un .vc2 de 18.000 de linii nu spune in ce metoda a carei clase
|
||||||
|
a nimerit (header-ul clasei poate fi cu 13.000 de linii mai sus, iar ~2/3 din fisier sunt
|
||||||
|
proprietati si metadata, nu cod). Scriptul construieste un index cu intervalul de linii al
|
||||||
|
fiecarei clase/metode si il foloseste ca sa etichete rezultatele.
|
||||||
|
|
||||||
|
Indexul e derivat 100% din cache-ul text (vezi vcx2txt.ps1) - se poate sterge oricand.
|
||||||
|
|
||||||
|
.PARAMETER Grep
|
||||||
|
Cauta expresia (regex) in fisierele indexate si eticheteaza fiecare rezultat cu clasa.metoda.
|
||||||
|
|
||||||
|
.PARAMETER CodeOnly
|
||||||
|
Cu -Grep: arata doar potrivirile din corpul metodelor/procedurilor (taie metadata si listele
|
||||||
|
de proprietati din .vc2/.sc2).
|
||||||
|
|
||||||
|
.PARAMETER Where
|
||||||
|
"fisier:linie" -> ce clasa/metoda contine linia respectiva.
|
||||||
|
|
||||||
|
.PARAMETER Find
|
||||||
|
Numele unui simbol (accepta * si ?) -> unde e DEFINIT (nu si unde e apelat).
|
||||||
|
|
||||||
|
.PARAMETER Class
|
||||||
|
Numele unei clase -> lantul de mostenire + metodele ei, cu intervalele de linii.
|
||||||
|
|
||||||
|
.PARAMETER Build
|
||||||
|
Reconstruieste indexul (implicit se reconstruieste automat cand e invechit).
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
# construieste/actualizeaza indexul
|
||||||
|
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vfp_symbols.ps1
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
# cautare etichetata, doar in cod
|
||||||
|
... vfp_symbols.ps1 -Grep 'calcul_cheiaj' -CodeOnly
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
... vfp_symbols.ps1 -Where 'oacnpro.vc2:14937'
|
||||||
|
... vfp_symbols.ps1 -Find 'do_calcul_*'
|
||||||
|
... vfp_symbols.ps1 -Class frm_calcul_cheiaj
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$Grep,
|
||||||
|
[switch]$CodeOnly,
|
||||||
|
[string]$Where,
|
||||||
|
[string]$Find,
|
||||||
|
[string]$Class,
|
||||||
|
# implicit: fluxul in-tree din ROAACNPRO (textul .vc2/.sc2 sta langa binare, nu intr-un cache)
|
||||||
|
[string]$CacheRoot = 'D:\ROA\ROAACNPRO',
|
||||||
|
[string]$ProjectRoot = 'D:\ROA\ROAACNPRO',
|
||||||
|
[string[]]$PrgDirs = @('Programe', 'COMUN\programe'),
|
||||||
|
[string]$ExcludePrg = '\\programe\\roa\\',
|
||||||
|
[string]$IndexFile,
|
||||||
|
[switch]$Build,
|
||||||
|
[switch]$Stats
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
if (-not $IndexFile) {
|
||||||
|
# la fluxul in-tree indexul nu se scrie in repo, ci in zona de cache comuna
|
||||||
|
$IndexFile = if ($CacheRoot -like 'D:\ROA\_vfp_textcache\*') { Join-Path $CacheRoot '_symbols.tsv' }
|
||||||
|
else { Join-Path ('D:\ROA\_vfp_textcache\' + (Split-Path $ProjectRoot -Leaf).ToLower()) '_symbols.tsv' }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Codepage-ul din antetul .vc2 nu e de incredere (vezi CLAUDE.md), dar aici se citesc doar
|
||||||
|
# cuvinte-cheie si identificatori ASCII, iar fisierele nu sunt niciodata rescrise.
|
||||||
|
$cp1252 = [System.Text.Encoding]::GetEncoding(1252)
|
||||||
|
|
||||||
|
# .vc2/.sc2 = clase/forme convertite din binar; .prg = sursa directa (are si Define Class, ex. roa.prg)
|
||||||
|
$rxClass = [regex]'(?i)^\s*DEFINE\s+CLASS\s+([\w]+)\s+AS\s+([\w\.]+)\s*(?:OF\s+"?([^"]*?)"?\s*)?$'
|
||||||
|
$rxEndDef = [regex]'(?i)^\s*ENDDEFINE\b'
|
||||||
|
$rxProc = [regex]'(?i)^\s*(?:(?:HIDDEN|PROTECTED)\s+)*(?:PROCEDURE|FUNCTION)\s+([\w\.]+)'
|
||||||
|
$rxEndPrc = [regex]'(?i)^\s*(?:ENDPROC|ENDFUNC)\b'
|
||||||
|
|
||||||
|
function Get-SourceFiles {
|
||||||
|
$files = @()
|
||||||
|
# -Include e ignorat cu -LiteralPath in PS 5.1 -> filtrarea se face cu Where-Object
|
||||||
|
if (Test-Path $CacheRoot) {
|
||||||
|
$files += Get-ChildItem -LiteralPath $CacheRoot -Recurse -File |
|
||||||
|
Where-Object { $_.Extension -match '(?i)^\.(vc2|sc2)$' }
|
||||||
|
}
|
||||||
|
foreach ($d in $PrgDirs) {
|
||||||
|
$full = Join-Path $ProjectRoot $d
|
||||||
|
if (Test-Path $full) {
|
||||||
|
$files += Get-ChildItem -LiteralPath $full -Recurse -File -Filter *.prg |
|
||||||
|
Where-Object { $_.FullName -notmatch '\\\.svn\\' -and
|
||||||
|
($ExcludePrg -eq '' -or $_.FullName -notmatch "(?i)$ExcludePrg") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$files
|
||||||
|
}
|
||||||
|
|
||||||
|
function Parse-File($file) {
|
||||||
|
$lines = [System.IO.File]::ReadAllLines($file.FullName, $cp1252)
|
||||||
|
$recs = New-Object System.Collections.ArrayList
|
||||||
|
|
||||||
|
$cls = $null; $clsLine = 0; $clsParent = ''; $clsOf = ''
|
||||||
|
$prc = $null; $prcLine = 0
|
||||||
|
|
||||||
|
# inchide procedura curenta la linia $end si o adauga in lista
|
||||||
|
$closeProc = {
|
||||||
|
param($end)
|
||||||
|
if ($prc) {
|
||||||
|
$q = if ($cls) { "$cls.$prc" } else { $prc }
|
||||||
|
$k = if ($cls) { 'method' } else { 'proc' }
|
||||||
|
[void]$recs.Add([pscustomobject]@{
|
||||||
|
kind = $k; qname = $q; class = [string]$cls; member = $prc
|
||||||
|
file = $file.FullName; line = $prcLine; endline = $end
|
||||||
|
parent = ''; parentfile = ''
|
||||||
|
})
|
||||||
|
$script:prcClosed = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0; $i -lt $lines.Length; $i++) {
|
||||||
|
$ln = $lines[$i]
|
||||||
|
$num = $i + 1
|
||||||
|
$t = $ln.TrimStart()
|
||||||
|
if ($t.Length -eq 0 -or $t[0] -eq '*') { continue } # comentariile nu deschid blocuri
|
||||||
|
|
||||||
|
$m = $rxClass.Match($ln)
|
||||||
|
if ($m.Success) {
|
||||||
|
& $closeProc ($num - 1); $prc = $null
|
||||||
|
if ($cls) {
|
||||||
|
[void]$recs.Add([pscustomobject]@{
|
||||||
|
kind='class'; qname=$cls; class=$cls; member=''
|
||||||
|
file=$file.FullName; line=$clsLine; endline=($num-1)
|
||||||
|
parent=$clsParent; parentfile=$clsOf })
|
||||||
|
}
|
||||||
|
$cls = $m.Groups[1].Value; $clsLine = $num
|
||||||
|
$clsParent = $m.Groups[2].Value; $clsOf = $m.Groups[3].Value
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ($rxEndDef.IsMatch($ln)) {
|
||||||
|
& $closeProc ($num - 1); $prc = $null
|
||||||
|
if ($cls) {
|
||||||
|
[void]$recs.Add([pscustomobject]@{
|
||||||
|
kind='class'; qname=$cls; class=$cls; member=''
|
||||||
|
file=$file.FullName; line=$clsLine; endline=$num
|
||||||
|
parent=$clsParent; parentfile=$clsOf })
|
||||||
|
$cls = $null
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
$m = $rxProc.Match($ln)
|
||||||
|
if ($m.Success) {
|
||||||
|
& $closeProc ($num - 1) # VFP permite proceduri fara ENDPROC
|
||||||
|
$prc = $m.Groups[1].Value; $prcLine = $num
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ($rxEndPrc.IsMatch($ln) -and $prc) {
|
||||||
|
& $closeProc $num; $prc = $null
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& $closeProc $lines.Length
|
||||||
|
if ($cls) {
|
||||||
|
[void]$recs.Add([pscustomobject]@{
|
||||||
|
kind='class'; qname=$cls; class=$cls; member=''
|
||||||
|
file=$file.FullName; line=$clsLine; endline=$lines.Length
|
||||||
|
parent=$clsParent; parentfile=$clsOf })
|
||||||
|
}
|
||||||
|
$recs
|
||||||
|
}
|
||||||
|
|
||||||
|
function Build-Index {
|
||||||
|
$src = Get-SourceFiles
|
||||||
|
if (-not $src) { throw "Nu am gasit surse. Ruleaza intai vcx2txt.ps1 (cache: $CacheRoot)." }
|
||||||
|
$all = New-Object System.Collections.ArrayList
|
||||||
|
foreach ($f in $src) { foreach ($r in (Parse-File $f)) { [void]$all.Add($r) } }
|
||||||
|
|
||||||
|
$dir = Split-Path $IndexFile -Parent
|
||||||
|
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null }
|
||||||
|
$all | Select-Object kind, qname, class, member, file, line, endline, parent, parentfile |
|
||||||
|
Export-Csv -LiteralPath $IndexFile -Delimiter "`t" -NoTypeInformation -Encoding UTF8
|
||||||
|
|
||||||
|
$nc = ($all | Where-Object kind -eq 'class').Count
|
||||||
|
$nm = ($all | Where-Object kind -eq 'method').Count
|
||||||
|
$np = ($all | Where-Object kind -eq 'proc').Count
|
||||||
|
Write-Host "Index: $($src.Count) fisiere -> $nc clase, $nm metode, $np proceduri"
|
||||||
|
Write-Host "Scris in: $IndexFile"
|
||||||
|
$all
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-IndexStale {
|
||||||
|
if (-not (Test-Path $IndexFile)) { return $true }
|
||||||
|
# acelasi -IndexFile poate veni dintr-o alta radacina (alt -CacheRoot): datele vechi nu par invechite
|
||||||
|
$head = Get-Content -LiteralPath $IndexFile -TotalCount 2
|
||||||
|
if ($head.Count -ge 2) {
|
||||||
|
$cols = $head[1].Split("`t")
|
||||||
|
if ($cols.Count -gt 4) {
|
||||||
|
$src = $cols[4].Trim('"')
|
||||||
|
if (-not ($src.StartsWith($CacheRoot, 'OrdinalIgnoreCase') -or
|
||||||
|
$src.StartsWith($ProjectRoot, 'OrdinalIgnoreCase'))) { return $true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$idx = (Get-Item $IndexFile).LastWriteTime
|
||||||
|
foreach ($f in (Get-SourceFiles)) { if ($f.LastWriteTime -gt $idx) { return $true } }
|
||||||
|
$false
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-Index {
|
||||||
|
if ($Build -or (Test-IndexStale)) { return Build-Index }
|
||||||
|
Import-Csv -LiteralPath $IndexFile -Delimiter "`t" | ForEach-Object {
|
||||||
|
$_.line = [int]$_.line; $_.endline = [int]$_.endline; $_
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Indexul se grupeaza o singura data pe fisier: cautarea liniara prin toate inregistrarile
|
||||||
|
# la fiecare potrivire face -Grep de ~100x mai lent pe simboluri des folosite (ex. goExecutor).
|
||||||
|
$script:ownerIdx = $null
|
||||||
|
function Init-OwnerIndex($index) {
|
||||||
|
$h = @{}
|
||||||
|
foreach ($r in $index) {
|
||||||
|
$k = $r.file.ToLower()
|
||||||
|
if (-not $h.ContainsKey($k)) { $h[$k] = New-Object System.Collections.ArrayList }
|
||||||
|
[void]$h[$k].Add($r)
|
||||||
|
}
|
||||||
|
$script:ownerIdx = $h
|
||||||
|
}
|
||||||
|
|
||||||
|
# proprietarul unei linii: metoda bate clasa (intervalul cel mai strans)
|
||||||
|
function Get-Owner($file, $lineNo) {
|
||||||
|
$k = $file.ToLower()
|
||||||
|
$recs = $script:ownerIdx[$k]
|
||||||
|
if (-not $recs) {
|
||||||
|
foreach ($kk in $script:ownerIdx.Keys) { # accepta si doar numele fisierului
|
||||||
|
if ($kk.EndsWith('\' + $k)) { $recs = $script:ownerIdx[$kk]; break }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not $recs) { return $null }
|
||||||
|
$best = $null; $bestSpan = [int]::MaxValue
|
||||||
|
foreach ($r in $recs) {
|
||||||
|
if ($r.line -le $lineNo -and $r.endline -ge $lineNo) {
|
||||||
|
$span = $r.endline - $r.line
|
||||||
|
if ($span -lt $bestSpan) { $bestSpan = $span; $best = $r }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$best
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- moduri ---
|
||||||
|
|
||||||
|
if ($Where) {
|
||||||
|
$idx = Get-Index
|
||||||
|
if ($Where -notmatch '^(?<f>.+?):(?<l>\d+)\s*$') { throw "Format asteptat: 'fisier:linie'" }
|
||||||
|
Init-OwnerIndex $idx
|
||||||
|
$o = Get-Owner $Matches['f'] ([int]$Matches['l'])
|
||||||
|
if ($o) { "{0} [{1}] {2}:{3}-{4}" -f $o.qname, $o.kind, (Split-Path $o.file -Leaf), $o.line, $o.endline }
|
||||||
|
else { "(in afara oricarei clase/metode - metadata sau cod la nivel de fisier)" }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Find) {
|
||||||
|
$idx = Get-Index
|
||||||
|
# numele unei clase intoarce definitia ei, nu toti membrii (pentru membri: -Class)
|
||||||
|
$hits = $idx | Where-Object { $_.member -like $Find -or $_.qname -like $Find -or
|
||||||
|
($_.kind -eq 'class' -and $_.class -like $Find) }
|
||||||
|
if (-not $hits) { Write-Host "Nicio definitie pentru '$Find'."; return }
|
||||||
|
$hits | Sort-Object kind, qname | ForEach-Object {
|
||||||
|
"{0,-7} {1,-45} {2}:{3}" -f $_.kind, $_.qname, $_.file.Replace($CacheRoot + '\', '').Replace($ProjectRoot + '\', ''), $_.line
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Class) {
|
||||||
|
$idx = Get-Index
|
||||||
|
$c = $idx | Where-Object { $_.kind -eq 'class' -and $_.class -eq $Class } | Select-Object -First 1
|
||||||
|
if (-not $c) { Write-Host "Clasa '$Class' nu e in index."; return }
|
||||||
|
|
||||||
|
Write-Host "Lant de mostenire:"
|
||||||
|
$cur = $c; $depth = 0; $seen = @{}
|
||||||
|
while ($cur -and $depth -lt 12) {
|
||||||
|
"{0}{1} ({2}:{3})" -f (' ' * $depth), $cur.class, (Split-Path $cur.file -Leaf), $cur.line
|
||||||
|
if (-not $cur.parent -or $seen[$cur.parent]) { break }
|
||||||
|
$seen[$cur.class] = $true
|
||||||
|
$next = $idx | Where-Object { $_.kind -eq 'class' -and $_.class -eq $cur.parent } | Select-Object -First 1
|
||||||
|
if (-not $next) { "{0}{1} (clasa de baza VFP sau librarie neindexata: {2})" -f (' ' * ($depth+1)), $cur.parent, $cur.parentfile; break }
|
||||||
|
$cur = $next; $depth++
|
||||||
|
}
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host "Metode proprii ($($c.class)):"
|
||||||
|
$idx | Where-Object { $_.kind -eq 'method' -and $_.class -eq $Class } | Sort-Object line |
|
||||||
|
ForEach-Object { " {0,-50} {1}-{2}" -f $_.member, $_.line, $_.endline }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Grep) {
|
||||||
|
$idx = Get-Index
|
||||||
|
$files = Get-SourceFiles
|
||||||
|
$res = $files | Select-String -Pattern $Grep -Encoding default
|
||||||
|
if (-not $res) { Write-Host "Nicio potrivire pentru '$Grep'."; return }
|
||||||
|
Init-OwnerIndex $idx
|
||||||
|
$shown = 0; $hidden = 0
|
||||||
|
foreach ($r in $res) {
|
||||||
|
$o = Get-Owner $r.Path $r.LineNumber
|
||||||
|
if (-not $o -or $o.kind -eq 'class') {
|
||||||
|
if ($CodeOnly) { $hidden++; continue }
|
||||||
|
}
|
||||||
|
$owner = if ($o -and $o.kind -ne 'class') { $o.qname } elseif ($o) { "$($o.qname) (metadata)" } else { '(nivel fisier)' }
|
||||||
|
$rel = $r.Path.Replace($CacheRoot + '\', '').Replace($ProjectRoot + '\', '')
|
||||||
|
"{0}:{1}: [{2}] {3}" -f $rel, $r.LineNumber, $owner, $r.Line.Trim()
|
||||||
|
$shown++
|
||||||
|
}
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host "$shown potriviri$(if ($hidden) { " ($hidden ascunse: metadata/proprietati)" })"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Stats) {
|
||||||
|
$idx = Get-Index
|
||||||
|
$idx | Group-Object kind | Sort-Object Count -Descending | ForEach-Object { "{0,-8} {1}" -f $_.Name, $_.Count }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Build-Index | Out-Null
|
||||||
Reference in New Issue
Block a user