·
  • #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:

  1. applicare i glob
  2. espandere le directory
  3. 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-run affidabile
  • 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.