- #golang
- #cli
- #git
- #tooling
- #design
Progettare una CLI per gestire file locali: algoritmo e scelte
In questo post entro nel design di `localfiles`: le scelte progettuali e l’algoritmo che permettono di gestire file locali fuori da Git senza comportamenti impliciti.
-3 min read
Contesto
Nel post precedente ho raccontato il problema: file e directory che servono solo localmente e che non devono finire nel repository, senza però intasare .gitignore.
Qui non parlo di implementazione in Go riga per riga. Parlo invece di come ho progettato il comportamento del tool, perché è lì che si decidono quasi tutti i compromessi.
Niente magia come vincolo principale
La prima decisione è stata più filosofica che tecnica:
Se non riesco a spiegare cosa fa il tool senza guardare il codice, allora il design è sbagliato.
Questo ha portato a evitare:
- auto-detection aggressive
- comportamenti impliciti
- regole “furbe” ma difficili da prevedere
Ogni scelta doveva essere deterministica e spiegabile a parole.
Project root ≠ Git repository
Una delle confusioni più comuni è trattare progetto e repository come la stessa cosa.
In localfiles ho separato esplicitamente i due concetti:
- project root → da dove partono gli scan
- git repository → un’integrazione opzionale
La regola è semplice:
- se passo
--root, quella è la root - altrimenti la root è la directory corrente
- Git non cambia mai questa scelta
Questo evita ambiguità e rende il comportamento prevedibile anche in strutture complesse.
I glob come linguaggio di selezione
Per selezionare i file ho scelto i glob pattern, con supporto a **.
Motivi principali:
- sono leggibili
- sono sufficientemente espressivi
- non richiedono regex
Esempio di configurazione:
exclude:
- "**/.env.local"
- "tools/agents/**"
I glob selezionano path, non definiscono logica. Tutto quello che viene dopo è responsabilità dell’algoritmo.
Algoritmo di scan
Lo scanner segue sempre gli stessi passi, senza eccezioni.
1. Raccolta dei match
- ogni glob viene applicato alla project root
- i path sono sempre relativi alla root
.git/viene esclusa immediatamente
In questa fase non viene copiato nulla.
2. Espansione delle directory
Se un glob matcha una directory:
- la directory viene attraversata ricorsivamente
- tutto viene ridotto a file singoli
Le directory non sono mai l’output finale. Sono solo un modo per arrivare ai file.
3. Deduplicazione
Lo stesso file può essere selezionato:
- da più glob
- da una directory padre e da un pattern più specifico
Prima di qualsiasi side-effect, tutti i path vengono deduplicati.
Questo rende l’algoritmo stabile e prevedibile.
Perché copiare i file
Il tool non si limita a ignorare i file. Li copia in una directory separata.
Questo permette:
- di sapere sempre cosa è stato escluso
- di poter ripristinare i file in futuro
- di rendere il tool estendibile senza promettere feature
La copia non è un dettaglio implementativo, è una scelta di design.
Dry-run come strumento di fiducia
Ogni tool che tocca il filesystem dovrebbe poter dire:
“Questo è quello che farei, se mi lasciassi fare.”
Con --dry-run:
- l’algoritmo è lo stesso
- non ci sono side-effect
- l’output mostra chiaramente cosa verrebbe copiato ed escluso
Non è una feature di comodità. È una garanzia di sicurezza.
Git come effetto collaterale controllato
Git non è il centro del tool. È solo uno degli output possibili.
Se la project root è dentro un repository Git:
- i file copiati vengono aggiunti a
.git/info/exclude - solo path espliciti
- nessun glob
Se non c’è Git, il tool funziona comunque.
Cosa localfiles non fa volutamente
Per scelta progettuale, localfiles non fa:
- sync bidirezionale
- restore automatico
- diff tra stato locale e copiato
- configurazioni avanzate
Ogni feature non necessaria è complessità in più.
Stato attuale e prossimi passi
Il tool è funzionale, ma non perfetto. Ho dovuto correggere alcune parti durante lo sviluppo ed è normale.
Nel prossimo post entrerò nel codice Go vero e proprio, mostrando:
- come ho strutturato il progetto
- dove ho incontrato problemi
- cosa migliorerei oggi
Senza vendere soluzioni magiche. Solo codice che serve davvero.