Architecture
This page describes the internal architecture of flk for contributors and curious users.
Overview
flk is a Rust CLI application that generates and modifies Nix flake configurations. It acts as a user-friendly layer on top of Nix, handling the complexity of flake syntax and structure.
┌─────────────────────────────────────────────────────────┐
│ flk CLI │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Commands │ │ Parsers │ │ Generator │ │
│ │ (clap CLI) │ │ (nom-based) │ │ (templates) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Nix Flake Files │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ flake.nix │ │ .flk/ │ │ flake.lock │ │
│ │ (root) │ │ profiles/ │ │ (inputs) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
Crate Structure
-
Binary crate (
src/main.rs): CLI entrypoint usingclap. Parses arguments and dispatches to command handlers insrc/commands/. -
Library crate (
src/lib.rs): Core functionality exposed as a library:flk::flake- Flake generation, parsing, and interfacesflk::utils- Backup management, visual output, helpers
-
Nix integration (
src/nix/): Wrappers for invokingnixcommands (search, evaluate, develop) and processing output.
Parser Design
Parsers in src/flake/parsers/ use the nom library to read and modify specific sections of Nix files:
- Surgical editing: Instead of parsing entire Nix files, parsers target specific sections (packages, env vars, commands) and track byte positions for precise modifications.
- Round-trip safety: Parse → modify struct → render back to Nix syntax, preserving comments and formatting outside the edited section.
Each parser returns a section struct containing:
- Parsed entries (packages, variables, etc.)
- Byte positions (
section_start,section_end,list_end) - Indentation information for consistent formatting
Template System
Templates in templates/ are embedded at compile time via include_str!:
templates/flake.nix- Root flake templatetemplates/default.nix- Default profile loadertemplates/pins.nix- Version pinning structuretemplates/profiles/*.nix- Language-specific profiles (rust, python, node, go, generic)
The generator (src/flake/generator.rs) selects and instantiates these templates based on project type.
Data Layout
A flk-managed project has this structure:
project/
├── flake.nix # Root flake (generated by flk init)
├── flake.lock # Nix lock file (managed by Nix)
└── .flk/
├── default.nix # Default profile selector
├── pins.nix # Version pinning data
├── overlays.nix # Package overlays
├── profiles/
│ ├── default.nix # Default profile (symlink or import)
│ ├── rust.nix # Language-specific profile
│ └── ...
└── backups/ # Lockfile backups from flk update
Command Flow
- CLI parsing:
clapinsrc/main.rsparses arguments - Command dispatch: Each subcommand calls a
run_*function insrc/commands/ - File operations: Commands use parsers to read/modify
.flk/files - Nix operations: Search, version lookup, and activation run through
nixwrappers - User feedback: Spinners and formatted output via
src/utils/visual.rs
Key Interfaces
FlakeConfig: Complete flake configuration with profiles, packages, env varsProfile: Single profile with packages, commands, environment variablesPackage: Package entry with name and optional version pinSourcesSection/OverlaysSection: Version pinning data structures