- #golang
- #cli
- #git
- #tooling
- #implementation
Implementare `localfiles` in Go: struttura, codice e compromessi
In questo post entro nel codice di `localfiles`: come è strutturato il progetto, come funziona lo scanner e quali compromessi ho accettato durante l’implementazione.
-3 min read
Stato del progetto
A questo punto localfiles esiste davvero:
- lo sto usando da qualche giorno
- il repository è su GitHub (per ora privato)
- il tool fa quello per cui è nato, senza sorprese
Questo post non è una celebrazione del codice. È una ricostruzione onesta di come è stato implementato.
Struttura del progetto
La prima scelta è stata separare chiaramente le responsabilità.
localfiles/
├── cmd/
│ └── localfiles/
│ └── main.go
├── internal/
│ ├── app/
│ │ └── scan.go
│ ├── config/
│ │ └── config.go
│ ├── scanner/
│ │ └── scanner.go
│ └── git/
│ └── git.go
└── go.mod
Non è una struttura “enterprise”. È solo abbastanza chiara da capire dove mettere le cose.
Il punto di ingresso: main
main.go fa pochissimo, ed è voluto.
Responsabilità:
- parsing dei flag
- risoluzione della project root
- dispatch del comando
Tutta la logica reale vive altrove. Se main cresce troppo, significa che il design sta cedendo.
Configurazione: leggere .localfiles.yaml
La configurazione è volutamente minimale.
exclude:
- "**/.env.local"
- "tools/agents/**"
Nel codice:
- parsing diretto
- nessuna validazione avanzata
- errori chiari se il file è malformato
La configurazione non è un linguaggio. È solo un input strutturato.
Lo scanner
Lo scanner è il cuore del tool.
Il suo lavoro è semplice:
- applicare i glob
- espandere le directory
- restituire una lista di file
Non copia nulla. Non conosce Git. Non ha side-effect.
Questa separazione ha reso più facile correggere bug emersi nei primi utilizzi.
Deduplicazione e path
Una delle parti meno visibili, ma più importanti, è la gestione dei path.
Scelte fatte:
- path sempre relativi alla project root
- normalizzazione prima della deduplicazione
- nessuna assunzione sul filesystem
Molti piccoli bug iniziali venivano proprio da qui.
Copia dei file
La copia avviene solo dopo che:
- lo scan è completo
- i file sono deduplicati
Questo permette:
- un
--dry-runaffidabile - meno stati intermedi
La copia non è ottimizzata. È volutamente semplice.
Dry-run
Il --dry-run usa lo stesso identico flusso del comando reale.
L’unica differenza è che:
- le operazioni di scrittura vengono saltate
Questo evita divergenze tra:
- quello che il tool dice
- quello che poi fa davvero
Integrazione Git
Git entra in gioco solo alla fine.
Se la project root è dentro un repository:
- viene individuata la git root
- i file copiati vengono aggiunti a
.git/info/exclude
Scelte importanti:
- solo path espliciti
- nessun glob
- nessuna modifica a
.gitignore
Se Git non c’è, il tool continua a funzionare.
Bug incontrati (e corretti)
Durante l’uso reale sono emersi problemi:
- parsing dei flag nei sottocomandi
- path calcolati in modo errato in alcuni edge case
- comportamenti non chiari in dry-run
Niente di drammatico, ma sufficiente a ricordarmi che:
il codice che compila non è il codice che funziona.
Compromessi consapevoli
Ci sono molte cose che localfiles potrebbe fare.
Non le fa, per scelta:
- niente restore automatico
- niente sync bidirezionale
- niente astrazioni complesse
Ogni feature in meno è superficie di bug in meno.
Considerazioni finali
localfiles non è un tool “definitivo”.
È una soluzione piccola a un problema concreto. Funziona perché:
- fa poco
- è prevedibile
- non prova a essere furbo
Se in futuro crescerà, sarà solo partendo dall’uso reale.
Ed è esattamente così che volevo chiudere questa serie.