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.

8 min read • View on GitHub • More from theobori

A retro switchboard where paper gophermap lines are patched into different destinations. One cable leads to a directory shelf, another to a live handler stack, and another to a plain text drawer. It explains Fleur's core move: menu text becomes executable routing.
Fleur treats the menu file as a dispatch surface, not a dead listing.
Key Takeaways

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.

Théo Borémans, Project Creator / Maintainer · theobori/fleur: RFC 1436 Gopher application framework and CLI

A gophermap line passes through evaluation, then gets claimed by the highest-priority matching rule, or falls back to text if nothing matches.

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.

A close-up stack of layered rule cards, with one regex card glowing above lower-weight catch-all cards. A line of text flows through the stack toward a handler output slot. It explains how Fleur's weighted extension matching decides which rule owns a gophermap line.
Specific rules can outrank catch-alls, so the menu behaves like a small router.

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.

LayerWeb framework instinctFleur analogue
Route definitionPath to handlerSelector to callback
Dispatch logicMiddleware plus routerWeighted regex matching plus extension rules
State passed inRequest objectRequestContext
Fallback behavior404 or default viewStatic menu or text fallback
Deployment goalHTTP app serverGopher 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

Documentation, Project Documentation · 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.

ProjectLanguagePrimary roleRouting modelDynamic behaviorBest fit
theobori/fleurGoApplication frameworkRegex selectors plus weighted rulesHigh, through extensions and form logicBuilding dynamic Gopher apps
GophernicusCProduction serverServer-oriented, mostly static or CGIModerateServing traditional Gopher spaces
peterhellberg/gopherGoProtocol libraryManual or customLowBuilding your own server primitives
prologic/go-gopherGoLightweight implementationSimple server patternsLow to moderateSmall 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.