Fleur: The Go Framework That Turns Gophermaps Into Programs
A modern Gopher server that treats selectors, routes, and menu files like a tiny runtime, not just static text.
- Fleur's main trick is to treat a gophermap as a programmable runtime instead of a static menu.
- Its weighted extension system lets specific rules claim a line before fallback handlers do.
- The router, safe path handling, and item-type inference give the protocol web-like ergonomics without turning it into HTTP.
- Nix support makes the project feel like deployable infrastructure, not a retro curiosity.
Most Gopher servers do one job well: they serve a menu and get out of the way. Fleur does something stranger. It treats the menu file itself as a place where behavior can live, so a line in a gophermap can be interpreted, routed, overridden, or left alone depending on which extension wins.
That shift matters because it changes the unit of composition. In Fleur, the humble selector is not just a path to content. It is the input to a small runtime that can decide whether to render a static item, dispatch a handler, or fall back to plain text.
The Gophermap Is Not Just Content. It’s a Runtime
The heart of Fleur sits in gophermap/evaluator. The evaluator walks each line, hands it to an extension manager, and lets weighted rules decide what happens next. The result is a familiar Gopher menu format with a far less familiar property: it can behave.
The name of the project is fleur, pronounced \flœʁ\, which is the French word for flower.
The clever part is the ordering. Fleur does not ask, "Can this line be handled?" It asks, "Which rule should own it?" That makes the extension system feel less like a plugin list and more like dispatch logic. Specific rules can outrank broad ones, which is exactly what you want when a text protocol starts acting like an application layer.
How One Line Becomes a Dynamic Response
Fleur's evaluator is backed by a weighted extension manager. In practice, that means a line can be matched by RFC 1436 defaults, a custom extension, or a fallback rule. The highest-priority match wins, and the rest stay out of the way.
// Conceptual flow in Fleur
for _, line := range gophermapLines {
result := evaluator.Process(line)
if result.Matched {
conn.Send(result.Output)
continue
}
conn.Send(line)
}
That is a small idea with a large effect. The same file can keep old-school Gopher behavior intact while also becoming a place for dynamic responses. Static content still works. Dynamic behavior layers on top, not instead of it.
This is why the project feels more like a framework than a server. The protocol stays tiny and text-first, but the implementation starts to look like application plumbing: parsing, matching, dispatch, and fallback.
Why Fleur Feels Like a Web Framework Without Being HTTP
The router in server/router.go maps selectors to callbacks using regex matching and a request context. That sounds webby, and it is, but the surface area stays Gopher-small. You still think in selectors and menus, not verbs, headers, or JSON payloads.
| Layer | Web framework instinct | Fleur analogue |
|---|---|---|
| Route definition | Path to handler | Selector to callback |
| Dispatch logic | Middleware plus router | Weighted regex matching plus extension rules |
| State passed in | Request object | RequestContext |
| Fallback behavior | 404 or default view | Static menu or text fallback |
| Deployment goal | HTTP app server | Gopher app runtime |
That is the sweet spot. Fleur borrows the ergonomics of modern frameworks without inheriting their complexity. The result is a protocol server that feels approachable to developers who know Go web apps, but still faithful to Gopher's simplicity.
The Safety Layer Hidden Under the Retro Surface
The nostalgia is wrapped around real defensive work. SafePath normalizes paths manually, which matters in a protocol that exposes directory structure so directly. Fleur also infers item types with a mix of file extensions and magic-byte sniffing, then defaults unknowns to text so the system stays readable by default.
// Conceptual safety and typing choices
clean := SafePath(requestedPath)
itemType := InferItemType(file)
if itemType == Unknown {
itemType = TextFile
}
That tradeoff is sensible. If the server cannot confidently classify something, Fleur prefers text over mystery. If a path tries to wander, it gets cleaned before it can do damage. The retro exterior does not excuse sloppy internals.
Why Nix Matters Here
Fleur is not packaged like a hobby script. It ships with Nix, flakes, and a NixOS module, which signals a different ambition: reproducible deployment. That matters for a niche protocol where the hardest part is often not writing the server, but keeping the service predictable.
Fleur router extension that allows you to create gophermap forms - theobori/fleur-form
The ecosystem around Fleur reinforces that point. A router extension for forms, a CLI entry point, and a deployment story through Nix together make it feel like software meant to be used, not just admired.
Fleur in Context
Compared with Gophernicus, Fleur is less of a production daemon and more of an application layer. Compared with peterhellberg/gopher, it reaches for framework behavior instead of low-level protocol primitives. And compared with prologic/go-gopher, it leans harder into dynamic routing and programmable menu logic.
| Project | Language | Primary role | Routing model | Dynamic behavior | Best fit |
|---|---|---|---|---|---|
| theobori/fleur | Go | Application framework | Regex selectors plus weighted rules | High, through extensions and form logic | Building dynamic Gopher apps |
| Gophernicus | C | Production server | Server-oriented, mostly static or CGI | Moderate | Serving traditional Gopher spaces |
| peterhellberg/gopher | Go | Protocol library | Manual or custom | Low | Building your own server primitives |
| prologic/go-gopher | Go | Lightweight implementation | Simple server patterns | Low to moderate | Small Gopher deployments |
That is Fleur's niche. It is not trying to be the biggest Gopher server or the smallest Go library. It is trying to be the missing layer in between, where plain text menus can start behaving like applications.
What Fleur Suggests About the Small Web
Fleur makes a strong case for a useful kind of retrofitting. You do not need to turn every old protocol into a web clone to make it modern. Sometimes the real upgrade is giving a tiny format enough structure to support routing, extension points, safety checks, and reproducible deployment.
That is the larger lesson here. Small does not have to mean static. In Fleur, the oldest part of Gopher, the menu file, becomes the most modern part of the stack.





