Compare commits
6 Commits
558973f0c6
...
romfast
| Author | SHA1 | Date | |
|---|---|---|---|
| 339ad0a4bc | |||
| 57c4a63d06 | |||
| 9b6671b0ed | |||
| 7f06f3ca4c | |||
| 234f6a5e7f | |||
| 51cab198c4 |
13
CLAUDE.md
13
CLAUDE.md
@@ -9,7 +9,7 @@ A local shared copy of **FoxBin2Prg** (the VFPX tool by Fernando D. Bozzo, maint
|
||||
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`, `git_svn_parallel_init_prompt.md`). The pre-git local state is kept on branch `local-snapshot-v1.21.04`.
|
||||
- **`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*`).
|
||||
|
||||
@@ -22,6 +22,7 @@ It serves two purposes:
|
||||
|
||||
- **`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.
|
||||
@@ -51,6 +52,16 @@ It serves two purposes:
|
||||
|
||||
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
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
# Prompt: init git in paralel cu SVN pentru un proiect ROA
|
||||
|
||||
Foloseste acest prompt intr-o sesiune Claude Code noua, deschisa in radacina proiectului
|
||||
tinta (ex. `D:\ROA\ROAIMOB`). Functioneaza generic pentru orice proiect VFP din `D:\ROA\`
|
||||
care are structura ROAACNPRO (proiect + subfolder COMUN\ partajat, ambele in SVN).
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
Vreau sa initializezi git pentru proiectul VFP din directorul curent, in paralel cu SVN-ul
|
||||
existent (nu atinge SVN, nu rula comenzi svn, doar adauga .svn/ in .gitignore).
|
||||
|
||||
Structura e identica cu D:\ROA\ROAACNPRO: acest folder e propriul proiect VFP (are un .PJX
|
||||
al lui), si contine un subfolder COMUN\ care e framework-ul PARTAJAT intre toate aplicatiile
|
||||
ROA, versionat separat prin SVN (are propriul .svn\).
|
||||
|
||||
Fa exact ce s-a facut deja pentru ROAACNPRO (poti citi acolo ca referinta/sablon):
|
||||
- D:\ROA\ROAACNPRO\.gitignore (sablon pentru .gitignore-ul proiectului principal)
|
||||
- D:\ROA\ROAACNPRO\COMUN\.gitignore (sablon pentru .gitignore-ul din COMUN)
|
||||
- D:\ROA\ROAACNPRO\CLAUDE.md (conventii proiect) si docs\cautare_vcx_vct.md (cum se cauta
|
||||
cod in interiorul .vcx/.scx binare, via D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1)
|
||||
|
||||
Pasi:
|
||||
|
||||
1. Verifica intai ce exista deja: `git status`/`.git` in radacina si in COMUN\, si
|
||||
`git remote -v` daca exista deja vreun remote configurat gresit (asa cum a fost cazul la
|
||||
ROAACNPRO, unde originul proiectului principal pointa gresit catre repo-ul comun.git).
|
||||
|
||||
2. REPO PRINCIPAL (radacina acestui folder):
|
||||
- Daca nu exista .git, `git init -b main`.
|
||||
- Creeaza/adapteaza .gitignore dupa modelul ROAACNPRO/.gitignore (artefacte VFP compilate:
|
||||
*.fxp, *.mpx, *.bak, *.mpr, *.err, log.txt, *.tmp, *.dct, *.exe, cruft Windows), plus
|
||||
obligatoriu `COMUN/` (COMUN e gestionat de git-ul lui separat, nu de-al acestui repo)
|
||||
si `.svn/`.
|
||||
- Deduce numele repo-ului gitea dupa conventia romfast/<nume-folder-lowercase>.git
|
||||
(ex. romfast/roaimob.git). INAINTE sa creezi/pushezi orice, verifica cu
|
||||
`git ls-remote git@gitea.romfast.ro:romfast/<nume>.git` daca exista deja si ce contine
|
||||
(clone --depth 1 intr-un folder temporar din scratchpad daca ai dubii). Daca exista deja
|
||||
cu istoric care nu se potriveste cu ce ai local, OPRESTE-TE si intreaba-ma inainte sa
|
||||
faci push sau orice suprascriere.
|
||||
- Daca remote-ul e gol/nou: add -A, commit "Initial commit — sursa <PROIECT>", adauga
|
||||
origin, push normal (fara --force).
|
||||
|
||||
3. COMUN\ (subfolder-ul partajat):
|
||||
- Initializeaza un git SEPARAT, propriu, in interiorul COMUN\ (COMUN are propriul .git,
|
||||
nu e tracked de repo-ul principal — de-asta l-am exclus mai sus).
|
||||
- .gitignore propriu in COMUN\ dupa modelul ROAACNPRO/COMUN/.gitignore (artefacte VFP +
|
||||
`.svn/` + `bash.exe.stackdump`).
|
||||
- Origin-ul din COMUN\ trebuie sa fie EXACT acelasi repo pentru toate proiectele ROA:
|
||||
git@gitea.romfast.ro:romfast/comun.git — repo-ul asta e deja populat (din COMUN-ul lui
|
||||
ROAACNPRO). NU face commit+push orbeste peste el.
|
||||
- Fa intai `git fetch origin`, compara continutul de pe origin/main cu fisierele locale
|
||||
din acest COMUN\ (diff pe folder, nu doar pe nume). Daca sunt identice sau diferentele
|
||||
sunt neglijabile, seteaza pur si simplu branch-ul local sa urmareasca origin/main (fara
|
||||
sa strici fisierele din working copy SVN). Daca sunt diferente reale intre COMUN-ul
|
||||
acestui proiect si cel deja impins pe gitea, arata-mi un rezumat al diferentelor si
|
||||
intreaba-ma explicit cum procedez (nu face force-push fara aprobarea mea explicita —
|
||||
ar sterge ireversibil istoricul comun tuturor proiectelor ROA).
|
||||
|
||||
4. Cautare in cod VFP binar (.vcx/.scx) pentru acest proiect:
|
||||
- Foloseste acelasi vcx2txt.ps1, dar parametrizat pentru proiectul curent (nu ROAACNPRO):
|
||||
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1 `
|
||||
-Project D:\ROA\<PROIECT>\<proiect>.PJX -ProjectRoot D:\ROA\<PROIECT> `
|
||||
-CacheRoot D:\ROA\_vfp_textcache\<proiect>
|
||||
- Cache separat per proiect, in D:\ROA\_vfp_textcache\ (in AFARA folderului foxbin2prg, ca
|
||||
ReCreate_FoxBin2Prg.prg sa nu-l reconverteasca). Nu-l amesteca cu cache-ul ROAACNPRO
|
||||
(D:\ROA\_vfp_textcache\roacnpro). Grep in cache-ul respectiv, la fel ca in docs\cautare_vcx_vct.md.
|
||||
|
||||
5. La final, adauga/actualizeaza CLAUDE.md al acestui proiect cu o sectiune scurta: git ruleaza
|
||||
in paralel cu SVN legacy (SVN ramane sursa "vie" pentru COMUN, sincronizat manual catre git);
|
||||
COMUN e partajat intre toate aplicatiile ROA prin acelasi git@gitea.romfast.ro:romfast/comun.git;
|
||||
cautarile in .vcx/.scx se fac cu vcx2txt.ps1 parametrizat pentru acest proiect (vezi mai sus).
|
||||
|
||||
Inainte de orice `push --force` sau orice operatie care ar suprascrie istoric remote deja
|
||||
existent (mai ales pe romfast/comun.git, care e comun mai multor proiecte), opreste-te si
|
||||
intreaba-ma explicit — nu decide singur.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Note
|
||||
|
||||
- Cel mai sensibil pas e COMUN\: daca versiunea de COMUN a noului proiect difera de cea deja
|
||||
impinsa pe `comun.git` (din ROAACNPRO), Claude trebuie sa se opreasca si sa intrebe cum se
|
||||
reconciliaza, nu sa faca force-push automat.
|
||||
- Daca `romfast/<proiect>.git` nu exista inca pe gitea, trebuie creat gol acolo inainte de push
|
||||
(manual sau prin API) — altfel push-ul esueaza cu "repository not found".
|
||||
34
git_sync.ps1
34
git_sync.ps1
@@ -24,8 +24,10 @@
|
||||
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) se sterg si se
|
||||
raporteaza; la fel .db2-urile ale caror tabele au IESIT din lista -DbfList.
|
||||
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).
|
||||
|
||||
@@ -53,7 +55,14 @@
|
||||
Reconverteste chiar daca textul e la zi.
|
||||
|
||||
.PARAMETER NoOrphanCleanup
|
||||
Nu sterge textele orfane (doar le raporteaza).
|
||||
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
|
||||
@@ -65,14 +74,21 @@ param(
|
||||
[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'),
|
||||
'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]$NoOrphanCleanup,
|
||||
[switch]$OrphanCleanup
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
@@ -271,6 +287,8 @@ 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)
|
||||
@@ -282,8 +300,8 @@ if (-not $SubDir) {
|
||||
$isOrphan = $true # tabela a iesit din lista
|
||||
}
|
||||
if ($isOrphan) {
|
||||
$orphansDeleted += (Get-RelPath $t.FullName)
|
||||
if (-not $NoOrphanCleanup) { Remove-Item -LiteralPath $t.FullName -Force -ErrorAction SilentlyContinue }
|
||||
$orphansDeleted += $tRel
|
||||
if ($OrphanCleanup -and -not $NoOrphanCleanup) { Remove-Item -LiteralPath $t.FullName -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,7 +313,7 @@ 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 ($NoOrphanCleanup) { 'raportate (nesterse)' } else { 'sterse' }
|
||||
$verb = if ($OrphanCleanup -and -not $NoOrphanCleanup) { 'sterse' } else { 'raportate (nesterse)' }
|
||||
Write-Host "Orfani ${verb}: $($orphansDeleted.Count)"
|
||||
$orphansDeleted | ForEach-Object { Write-Host " orfan: $_" }
|
||||
}
|
||||
|
||||
186
roa_sync.ps1
186
roa_sync.ps1
@@ -1,30 +1,21 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Sincronizare de inceput de sesiune pentru un proiect ROA (flux SVN + git text FoxBin2Prg).
|
||||
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
|
||||
Pasii:
|
||||
1. svn update pe ProjectRoot (include externalul COMUN\). Esec / conflicte -> stop, exit nenul.
|
||||
2. git_sync.ps1 -ProjectRoot <ProjectRoot> (improspateaza textele .??2, inclusiv COMUN).
|
||||
Esec (exit nenul) -> avertizare, fara commit-uri de sync, exit nenul.
|
||||
3. Repo proiect (ProjectRoot\.git): pe main comite "sync SVN r<REV>" daca sunt schimbari.
|
||||
Pe alt branch: avertizare si skip commit (nu schimba branch-ul daca are modificari necomise;
|
||||
daca working tree-ul e curat face checkout main).
|
||||
4. La fel repo COMUN (ProjectRoot\COMUN\.git), cu revizia din svn info pe folderul COMUN.
|
||||
5. Inchide branch-urile claude/* terminate (git diff main..branch gol) in ambele repo-uri.
|
||||
6. Sumar final.
|
||||
|
||||
.PARAMETER ProjectRoot
|
||||
Radacina proiectului. Implicit: directorul curent, daca are .git si un .pjx.
|
||||
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 = ''
|
||||
[string]$ProjectRoot = '',
|
||||
[switch]$GitOnly
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# --- deduce ProjectRoot din directorul curent daca nu e dat ---
|
||||
if (-not $ProjectRoot) {
|
||||
$cwd = (Get-Location).Path
|
||||
$hasGit = Test-Path -LiteralPath (Join-Path $cwd '.git')
|
||||
@@ -36,158 +27,19 @@ if (-not $ProjectRoot) {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $ProjectRoot -PathType Container)) {
|
||||
Write-Host "EROARE: ProjectRoot nu exista: $ProjectRoot"
|
||||
exit 1
|
||||
}
|
||||
$ProjectRoot = (Get-Item -LiteralPath $ProjectRoot).FullName
|
||||
$comunRoot = Join-Path $ProjectRoot 'COMUN'
|
||||
$gitSync = Join-Path $PSScriptRoot 'git_sync.ps1'
|
||||
|
||||
Write-Host "=== roa_sync: ProjectRoot=$ProjectRoot ==="
|
||||
|
||||
# --- helper: revizia SVN a unui folder ---
|
||||
function Get-SvnRev([string]$path) {
|
||||
$rev = (& svn info --show-item revision $path 2>$null | Out-String).Trim()
|
||||
if ($LASTEXITCODE -ne 0 -or -not $rev) { return '' }
|
||||
return $rev
|
||||
}
|
||||
|
||||
# =========================== 1. svn update ===========================
|
||||
Write-Host ''
|
||||
Write-Host "--- 1. svn update ---"
|
||||
$svnOut = & svn update $ProjectRoot
|
||||
$svnExit = $LASTEXITCODE
|
||||
$svnOut | ForEach-Object { Write-Host " $_" }
|
||||
if ($svnExit -ne 0) {
|
||||
Write-Host "EROARE: svn update a esuat (exit $svnExit). Ma opresc."
|
||||
exit 1
|
||||
}
|
||||
# svn poate iesi 0 dar raporta conflicte in sumar
|
||||
if ($svnOut | Where-Object { $_ -match 'Summary of conflicts|conflict' }) {
|
||||
Write-Host "EROARE: svn update raporteaza conflicte. Rezolva-le manual, apoi reia. Ma opresc."
|
||||
exit 1
|
||||
}
|
||||
$rev = Get-SvnRev $ProjectRoot
|
||||
Write-Host " Revizie SVN ProjectRoot: $(if($rev){$rev}else{'necunoscuta'})"
|
||||
|
||||
# =========================== 2. git_sync ===========================
|
||||
Write-Host ''
|
||||
Write-Host "--- 2. git_sync (texte .??2) ---"
|
||||
if (-not (Test-Path -LiteralPath $gitSync)) {
|
||||
Write-Host "EROARE: nu gasesc git_sync.ps1 la $gitSync. Ma opresc."
|
||||
exit 1
|
||||
}
|
||||
& powershell -ExecutionPolicy Bypass -NoProfile -File $gitSync -ProjectRoot $ProjectRoot
|
||||
$syncExit = $LASTEXITCODE
|
||||
if ($syncExit -ne 0) {
|
||||
Write-Host ''
|
||||
Write-Host "AVERTISMENT: git_sync a raportat esecuri (exit $syncExit). NU fac commit-uri de sync. Ma opresc."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# =========================== 3-4. commit sync per repo ===========================
|
||||
# Sincronizeaza un repo git: pe main comite schimbarile ca "sync SVN r<rev>".
|
||||
# Intoarce: 'commit' / 'nimic' / 'skip-branch'.
|
||||
function Sync-Repo {
|
||||
param([string]$RepoDir, [string]$Rev, [string]$Label)
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $RepoDir '.git'))) {
|
||||
Write-Host " ${Label}: fara repo git ($RepoDir) - sar peste."
|
||||
return 'nimic'
|
||||
$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
|
||||
}
|
||||
$branch = (& git -C $RepoDir rev-parse --abbrev-ref HEAD).Trim()
|
||||
if ($branch -ne 'main') {
|
||||
# tree curat = putem trece pe main; altfel nu atingem branch-ul
|
||||
$dirty = @(& git -C $RepoDir status --porcelain).Count -gt 0
|
||||
if ($dirty) {
|
||||
Write-Host " ${Label}: esti pe '$branch'; sync-ul se face pe main. Ai modificari necomise - sar peste commit."
|
||||
return 'skip-branch'
|
||||
}
|
||||
Write-Host " ${Label}: pe '$branch' cu tree curat - fac checkout main."
|
||||
& git -C $RepoDir checkout main | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host " ${Label}: checkout main a esuat - sar peste commit."
|
||||
return 'skip-branch'
|
||||
}
|
||||
}
|
||||
& git -C $RepoDir add -A | Out-Null
|
||||
$staged = @(& git -C $RepoDir status --porcelain).Count -gt 0
|
||||
if (-not $staged) {
|
||||
Write-Host " ${Label}: nimic de sincronizat."
|
||||
return 'nimic'
|
||||
}
|
||||
$msg = if ($Rev) { "sync SVN r$Rev" } else { "sync SVN" }
|
||||
& git -C $RepoDir commit -m $msg | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host " ${Label}: commit a esuat."
|
||||
return 'skip-branch'
|
||||
}
|
||||
Write-Host " ${Label}: commit '$msg'."
|
||||
return 'commit'
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host "--- 3. sync repo proiect ---"
|
||||
$resProj = Sync-Repo -RepoDir $ProjectRoot -Rev $rev -Label 'proiect'
|
||||
|
||||
Write-Host ''
|
||||
Write-Host "--- 4. sync repo COMUN ---"
|
||||
$revComun = ''
|
||||
$resComun = 'nimic'
|
||||
if (Test-Path -LiteralPath $comunRoot -PathType Container) {
|
||||
$revComun = Get-SvnRev $comunRoot
|
||||
$resComun = Sync-Repo -RepoDir $comunRoot -Rev $revComun -Label 'COMUN'
|
||||
} else {
|
||||
Write-Host " COMUN: folderul nu exista ($comunRoot) - sar peste."
|
||||
}
|
||||
|
||||
# =========================== 5. inchidere branch-uri claude/* ===========================
|
||||
# Inchide branch-urile claude/* al caror diff fata de main e gol (munca lor a intrat prin sync).
|
||||
function Close-ClaudeBranches {
|
||||
param([string]$RepoDir, [string]$Label)
|
||||
$closed = @(); $active = @()
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $RepoDir '.git'))) { return @{ Closed=$closed; Active=$active } }
|
||||
$branches = @(& git -C $RepoDir for-each-ref --format='%(refname:short)' refs/heads/claude 2>$null) |
|
||||
Where-Object { $_ -and $_.Trim() } | ForEach-Object { $_.Trim() }
|
||||
foreach ($b in $branches) {
|
||||
$diff = @(& git -C $RepoDir diff --name-only main..$b 2>$null) | Where-Object { $_ -and $_.Trim() }
|
||||
if ($diff.Count -eq 0) {
|
||||
& git -C $RepoDir branch -D $b | Out-Null
|
||||
if ($LASTEXITCODE -eq 0) { $closed += $b; Write-Host " ${Label}: inchis $b" }
|
||||
else { Write-Host " ${Label}: nu am putut sterge $b" }
|
||||
} else {
|
||||
$active += $b
|
||||
Write-Host " ${Label}: activ $b ($($diff.Count) fisiere diferite)"
|
||||
}
|
||||
}
|
||||
return @{ Closed=$closed; Active=$active }
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host "--- 5. inchidere branch-uri claude/* ---"
|
||||
$brProj = Close-ClaudeBranches -RepoDir $ProjectRoot -Label 'proiect'
|
||||
$brComun = @{ Closed=@(); Active=@() }
|
||||
if (Test-Path -LiteralPath $comunRoot -PathType Container) {
|
||||
$brComun = Close-ClaudeBranches -RepoDir $comunRoot -Label 'COMUN'
|
||||
}
|
||||
|
||||
# =========================== 6. sumar ===========================
|
||||
$commits = @()
|
||||
if ($resProj -eq 'commit') { $commits += "proiect: sync SVN r$rev" }
|
||||
if ($resComun -eq 'commit') { $commits += "COMUN: sync SVN r$revComun" }
|
||||
$closedAll = @($brProj.Closed + $brComun.Closed)
|
||||
$activeAll = @($brProj.Active + $brComun.Active)
|
||||
|
||||
Write-Host ''
|
||||
Write-Host "===================== SUMAR ====================="
|
||||
Write-Host "Revizie SVN: proiect=$(if($rev){$rev}else{'?'})$(if($revComun){" COMUN=$revComun"})"
|
||||
if ($commits.Count -gt 0) {
|
||||
Write-Host "Commit-uri de sync:"
|
||||
$commits | ForEach-Object { Write-Host " $_" }
|
||||
} else {
|
||||
Write-Host "Commit-uri de sync: nimic de sincronizat"
|
||||
}
|
||||
Write-Host "Branch-uri inchise: $(if($closedAll.Count){$closedAll -join ', '}else{'niciunul'})"
|
||||
Write-Host "Branch-uri active: $(if($activeAll.Count){$activeAll -join ', '}else{'niciunul'})"
|
||||
Write-Host "================================================"
|
||||
exit 0
|
||||
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
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
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.
|
||||
@@ -35,11 +37,15 @@ function Assert([bool]$cond, [string]$msg) {
|
||||
|
||||
# --- Ruleaza git_sync.ps1 ca proces copil si parseaza sumarul + orfanii + exit code ---
|
||||
function Invoke-GitSync {
|
||||
param([string]$Fixture, [string[]]$DbfList, [string[]]$Exempt, [int]$TimeoutSeconds = 30)
|
||||
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
|
||||
@@ -200,6 +206,32 @@ function Test-Exempt {
|
||||
} 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" }
|
||||
@@ -210,6 +242,7 @@ Test-Orphans
|
||||
Test-PartialFailure
|
||||
Test-BinaryProtection
|
||||
Test-Exempt
|
||||
Test-OrphanExempt
|
||||
|
||||
Write-Host "`n=== REZULTAT ==="
|
||||
if ($script:Failures.Count -eq 0) {
|
||||
|
||||
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