lofigui

package module
v0.17.11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 2, 2026 License: MIT Imports: 10 Imported by: 0

README ¶

lofigui

Lofi GUI - A minimalist library for creating really simple web-based GUIs for CLI tools and small projects. Available in both Python and Go, it provides a print-like interface for building lightweight web UIs with minimal complexity.

The aplication is where you have a single real object (eg machine or long running) processing which then have a number of pages around it to show various aspects of it.

Python Version Go Version

🚀 Now Available in Go!

Check out the Go version for:

  • 10-100x faster performance
  • Single binary deployment
  • WebAssembly support (~2MB vs ~10MB for Python)
  • Type safety at compile time

Choose the version that fits your needs - same simple API, different strengths!

Overview

lofigui provides a print-like interface for building lightweight web applications with minimal complexity. Perfect for:

  • Creating quick GUIs for command-line tools
  • Internal tools for small teams (1-10 users)
  • Single-process or single-object front-ends
  • Rapid prototyping without JavaScript overhead

Key Features

  • Simple API: Print-like interface (print(), markdown(), html(), table())
  • No JavaScript: Pure HTML/CSS using the Bulma framework to make it look prettier as I am terrible at design.
  • MVC Architecture: Model, view, and controller architecture
  • Async-ready: Built on asyncio for modern web frameworks (FastAPI, etc.)
  • Type-safe: Full type hints and mypy support
  • Secure: HTML escaping by default to prevent XSS attacks

Element

Your project is essentially a web site. To make design simple you completely refresh pages so no code for partial refreshes. To make things dynamic it has to be asynchonous so for python using fastapi as a server and Uvicorn to provide the https server.

Like a normal terminal program you essentially just print things to a screen but now have the ability to print enriched objects.

model view controller architecture

All I really want to do is to write the model. The controller and view (in the browser and templating system) are a necessary evil. The controller includes the routing and webserver. The controller is split between the app (single instance) and a model specific controller. The view is the html templating and the browser.

Buffer

In order to be able to decouple the display from the output and to be able to refesh you need to be able to buffer the output. It is more efficient to buffer the output in the browser but more complicated. Moving the buffer to the server simplifies the software but requires you to refresh the whole page. lofigui relies on hyperlinks to perform updates. Forms are useful for nice buttons but in general to get the right level of interactivity (click on somthing and it changes) you don't want to have forms. HTMLx would play nicely here if you were intersted in improving interactivity and spending a bit more time on the UI.

Installation

Using pip
pip install lofigui
Using uv
uv add lofigui
From source
git clone https://github.com/drummonds/lofigui.git
cd lofigui
uv sync --all-extras

Quick Start

Look at the example for a quick start.

API Reference

Output Functions
print(msg, ctx=None, end="\n", escape=True)

Print text to the buffer as HTML paragraphs.

Parameters:

  • msg (str): Message to print
  • ctx (PrintContext, optional): Custom context (default: global context)
  • end (str): End character - "\n" for paragraphs, "" for inline
  • escape (bool): Escape HTML entities (default: True)

Example:

import lofigui as lg

lg.print("Hello world")  # <p>Hello world</p>
lg.print("Inline", end="")  # &nbsp;Inline&nbsp;
lg.print("<script>alert('safe')</script>")  # Escaped by default
lg.print("<b>Bold</b>", escape=False)  # Raw HTML (use with caution!)
markdown(msg, ctx=None)

Convert markdown to HTML and add to buffer.

Parameters:

  • msg (str): Markdown-formatted text
  • ctx (PrintContext, optional): Custom context

Example:

lg.markdown("# Heading\n\nThis is **bold** text")
html(msg, ctx=None)

Add raw HTML to buffer (no escaping).

WARNING: Only use with trusted input to avoid XSS vulnerabilities.

Parameters:

  • msg (str): Raw HTML
  • ctx (PrintContext, optional): Custom context

Example:

lg.html("<div class='notification is-info'>Custom HTML</div>")
table(table, header=None, ctx=None, escape=True)

Generate an HTML table with Bulma styling.

Parameters:

  • table (Sequence[Sequence]): Table data as nested sequences
  • header (List[str], optional): Column headers
  • ctx (PrintContext, optional): Custom context
  • escape (bool): Escape cell content (default: True)

Example:

data = [
    ["Alice", 30, "Engineer"],
    ["Bob", 25, "Designer"],
]
lg.table(data, header=["Name", "Age", "Role"])
Buffer Management
buffer(ctx=None)

Get accumulated HTML output.

Returns: str

Example:

content = lg.buffer()
reset(ctx=None)

Clear the buffer.

Example:

lg.reset()
Context Management
PrintContext(max_buffer_size=None)

Context manager for buffering HTML output.

Parameters:

  • max_buffer_size (int, optional): Warn if buffer exceeds this size

Example:

from lofigui import PrintContext, print

# Using context manager (auto-cleanup)
with PrintContext() as ctx:
    print("Hello", ctx=ctx)
    # Buffer automatically reset on exit

# Or create manually
ctx = PrintContext(max_buffer_size=10000)
Favicon Support
get_favicon_response()

Get a FastAPI/Starlette Response object for serving the favicon.

Example:

@app.get("/favicon.ico")
async def favicon():
    return lg.get_favicon_response()
get_favicon_html_tag()

Get an HTML link tag with embedded favicon data URI.

Example:

# In your template <head>
{{ get_favicon_html_tag()|safe }}
save_favicon_ico(path)

Save the favicon to a file.

Example:

lg.save_favicon_ico("static/favicon.ico")

Architecture

MVC Pattern

lofigui follows the Model-View-Controller pattern:

  • Model: Your business logic (functions that call lg.print(), etc.)
  • View: Jinja2 templates that render the buffered HTML
  • Controller: FastAPI/Flask routes that orchestrate model and view
Buffering Strategy

Server-side buffering simplifies the architecture:

  1. Model functions write to a queue
  2. buffer() drains the queue and returns HTML
  3. Templates render the complete HTML
  4. Full page refresh (no partial DOM updates)

This approach trades interactivity for simplicity - perfect for internal tools.

Security

By default, all output functions escape HTML to prevent XSS attacks:

lg.print("<script>alert('xss')</script>")
# Output: <p>&lt;script&gt;alert('xss')&lt;/script&gt;</p>

Use escape=False or html() only with trusted input.

Examples

Each example introduces new features progressively. Study them in order, or jump to the one closest to your use case.

# Name Features introduced Run command
01 Hello World App, background model, auto-refresh polling task go-example:01
02 SVG Graph Synchronous render, SVG via lofigui.HTML() task go-example:02
03 WASM Hello World Go compiled to WASM, browser-only, no server task go-wasm:03
04 TinyGo WASM Smaller WASM binaries with TinyGo task go-wasm:04
05 Demo App Python template inheritance (Jinja2) task example-05
06 Notes CRUD Form POST, Controller directly (no App), redirect-after-POST task go-example:06
07 Water Tank SVG schematic generation, simulation goroutine, clickable SVG links, WASM-compatible task go-example:07
08 Water Tank Multi Multiple pages sharing one model, LayoutNavbar, HTTP Refresh polling task go-example:08
09 Water Tank HTMX HTMX partial updates, fragment endpoints, no full-page reload task go-example:09

Starting points for new projects:

  • Status page / dashboard — example 01 (polling) or 02 (sync)
  • CRUD / forms app — example 06
  • Real-time dashboard — example 08 (HTTP Refresh) or 09 (HTMX, smoother updates)
  • Browser-only (no server) — example 03 (WASM)

Python examples also available for 01, 02, 05, 06: task example-01, task example-02, etc.

Using Taskfile

The project uses Task for all development commands:

task --list              # Show all available tasks
task test                # Run Python + Go tests
task lint                # Run linters
task go-example:09       # Run any Go example by number
task example-01          # Run any Python example by number
task tidy                # go mod tidy for all modules
task clean               # Clean build artifacts

Development

Setup
git clone https://github.com/drummonds/lofigui.git
cd lofigui
uv sync --all-extras
Running Tests
uv run pytest

With coverage:

uv run pytest --cov=lofigui --cov-report=html
Type Checking
uv run mypy lofigui
Code Formatting
uv run black lofigui tests

Comparison with Alternatives

Feature lofigui Streamlit PyWebIO Textual
JavaScript No Yes Yes No
Complexity Very Low Medium Medium Medium
Use Case Internal tools Data apps Web apps Terminal UIs
Learning Curve Minimal Moderate Moderate Moderate
Partial Updates Via HTMX Yes Yes Yes

Choose lofigui if:

  • You want maximum simplicity
  • You're building internal tools
  • You don't need fancy interactivity
  • You want to understand every line of code

Choose alternatives if:

  • You need rich interactivity
  • You're building public-facing apps
  • You want widgets and components

Roadmap

See ROADMAP.md for planned features and future direction.

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

MIT License - see LICENSE file for details.

Author

Humphrey Drummond - [email protected]

Documentation ¶

Overview ¶

Package lofigui provides a simple interface for building lightweight web UIs Similar to the Python version, it provides a print-like interface for generating HTML

Index ¶

Constants ¶

View Source
const FaviconICOBase64 = `` /* 364-byte string literal not displayed */

FaviconICOBase64 is the base64-encoded favicon.ico file This is a 16x16 pixel ICO file with a simple "L" logo

View Source
const FaviconSVG = `` /* 199-byte string literal not displayed */

FaviconSVG is the SVG version of the favicon

View Source
const LayoutNavbar = `` /* 986-byte string literal not displayed */

LayoutNavbar has a fixed navbar (is-primary) with app name + status tag, a section with container for results, and a footer with version.

View Source
const LayoutSingle = `` /* 449-byte string literal not displayed */

LayoutSingle is a minimal layout: section > container > results. No navbar, no footer.

View Source
const LayoutThreePanel = `` /* 1202-byte string literal not displayed */

LayoutThreePanel has a navbar, a sidebar column (is-3) and main content column, plus a footer. Pass sidebar content via extra context key "sidebar".

Variables ¶

This section is empty.

Functions ¶

func Buffer ¶

func Buffer() string

Buffer returns the accumulated HTML output

func GetFaviconDataURI ¶

func GetFaviconDataURI() string

GetFaviconDataURI returns the favicon as a data URI

func GetFaviconHTMLTag ¶

func GetFaviconHTMLTag() string

GetFaviconHTMLTag returns an HTML link tag for the favicon

func GetFaviconICO ¶

func GetFaviconICO() ([]byte, error)

GetFaviconICO returns the favicon as ICO format bytes

func GetFaviconSVG ¶

func GetFaviconSVG() string

GetFaviconSVG returns the favicon as SVG string

func HTML ¶

func HTML(msg string)

HTML adds raw HTML to buffer (no escaping) WARNING: Only use with trusted input to avoid XSS

func Markdown ¶

func Markdown(msg string)

Markdown converts markdown to HTML and adds to buffer

func Print ¶

func Print(msg string, options ...PrintOption)

Print adds text to the buffer as HTML paragraphs Similar to Python's lofigui.print()

func Printf ¶

func Printf(format string, args ...interface{})

Printf is a convenience function for formatted printing

func Reset ¶

func Reset()

Reset clears the buffer

func ServeFavicon ¶

func ServeFavicon(w http.ResponseWriter, r *http.Request)

ServeFavicon is an http.HandlerFunc that serves the favicon Usage:

http.HandleFunc("/favicon.ico", lofigui.ServeFavicon)

func Table ¶

func Table(data [][]string, options ...TableOption)

Table generates an HTML table with Bulma styling

Types ¶

type App ¶ added in v0.9.0

type App struct {
	Version string // Version/name of the application

	PollCount int // Number of polling cycles
	// contains filtered or unexported fields
}

App provides a wrapper around a Controller with safe controller replacement.

The app manages:

  • Action state (running/stopped)
  • Auto-refresh polling during actions
  • Version information
  • Controller lifecycle and composition

When replacing a controller, App ensures that any running action is safely stopped before the new controller is set. This prevents dangling goroutines and ensures clean state transitions.

Example usage:

app := lofigui.NewApp()
ctrl, err := lofigui.NewController(lofigui.ControllerConfig{
    TemplatePath: "templates/page.html",
})
if err != nil {
    log.Fatal(err)
}
app.SetController(ctrl)

func NewApp ¶ added in v0.9.0

func NewApp() *App

NewApp creates a new App with no controller.

func NewAppWithController ¶ added in v0.9.0

func NewAppWithController(ctrl *Controller) *App

NewAppWithController creates a new App with the given controller.

func (*App) ControllerName ¶ added in v0.13.0

func (app *App) ControllerName() string

ControllerName returns the name of the current controller. Returns "Lofigui no controller" if no controller is set.

func (*App) EndAction ¶ added in v0.9.0

func (app *App) EndAction()

EndAction stops the action and disables auto-refresh polling. Also cancels the context returned by StartAction.

func (*App) GetController ¶ added in v0.9.0

func (app *App) GetController() *Controller

GetController returns the current controller. Returns nil if no controller is set.

func (*App) HandleDisplay ¶ added in v0.9.0

func (app *App) HandleDisplay(w http.ResponseWriter, r *http.Request)

HandleDisplay renders the template with full app state (including polling/refresh). Sets the HTTP Refresh header when polling is active, so the browser reloads the current page (not a hardcoded URL). Returns an error if no controller is set.

func (*App) HandleRoot ¶ added in v0.9.0

func (app *App) HandleRoot(w http.ResponseWriter, r *http.Request, modelFunc func(context.Context, *App), resetBuffer bool)

HandleRoot is a helper for the root endpoint that starts an action.

This function:

  1. Resets the buffer (if resetBuffer is true)
  2. Starts the action (app-level state) and gets a cancellable context
  3. Launches the model function in a goroutine with the context
  4. Returns HTML that redirects to the display page

The model function receives a context.Context that is cancelled when the action is stopped (EndAction) or replaced by a new action (another HandleRoot call).

Example:

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    app.HandleRoot(w, r, model, true)
})

func (*App) IsActionRunning ¶ added in v0.9.0

func (app *App) IsActionRunning() bool

IsActionRunning returns whether an action is currently running. This checks the app-level state (singleton active model).

func (*App) SetController ¶ added in v0.9.0

func (app *App) SetController(ctrl *Controller)

SetController sets a new controller with safe cleanup of the existing controller.

If there's an existing controller with a running action, this will safely stop the action before replacing with the new controller. This prevents dangling goroutines and ensures clean state transitions.

The cleanup logic is defensive - it handles controllers that may not have standard methods implemented and silently ignores any errors during cleanup.

This method is idempotent - if the same controller is being set again, no cleanup is performed and the running action continues.

Args:

  • ctrl: The new controller to set (can be nil to clear)

func (*App) SetDisplayURL ¶ added in v0.13.0

func (app *App) SetDisplayURL(url string)

SetDisplayURL sets the URL to redirect to for displaying results.

func (*App) SetRefreshTime ¶ added in v0.13.0

func (app *App) SetRefreshTime(seconds int)

SetRefreshTime sets the refresh time in seconds for auto-refresh polling.

func (*App) StartAction ¶ added in v0.9.0

func (app *App) StartAction() context.Context

StartAction starts an action and enables auto-refresh polling. This implements the singleton active model concept - only one action can be running at a time across the entire app.

If a previous action is still running, its context is cancelled before starting the new one. Returns a context that will be cancelled when the action is stopped or replaced.

func (*App) StateDict ¶ added in v0.13.0

func (app *App) StateDict(r *http.Request, extraContext pongo2.Context) pongo2.Context

StateDict generates a template context dictionary with app and controller state merged.

This method provides centralized state management by combining:

  • App-level state (version, controller name, polling status)
  • Controller-level state (buffer content)
  • Extra context passed by the caller

Returns a pongo2.Context containing:

  • request: The HTTP request object
  • version: Application version string
  • controller_name: Name of the active controller
  • results: Buffer content from Print/Markdown calls
  • polling: "Running" or "Stopped" (app-level singleton state)
  • poll_count: Number of refresh cycles (app-level)
  • refresh: Always empty string (refresh now uses HTTP header, kept for template compat)
  • Any additional keys from extraContext

Example:

func (app *App) HandleCustomDisplay(w http.ResponseWriter, r *http.Request) {
    extra := pongo2.Context{"title": "My Page"}
    data := app.StateDict(r, extra)
    // Use data for template rendering
}

func (*App) WriteRefreshHeader ¶ added in v0.17.4

func (app *App) WriteRefreshHeader(w http.ResponseWriter)

WriteRefreshHeader sets the HTTP Refresh header when polling is active. This causes the browser to reload the current page after refreshTime seconds, which works correctly for multi-page apps (unlike a meta refresh with a hardcoded URL).

type Context ¶

type Context struct {
	// contains filtered or unexported fields
}

Context manages the output buffer for HTML generation

func NewContext ¶

func NewContext() *Context

NewContext creates a new Context with optional max buffer size

func (*Context) Buffer ¶

func (c *Context) Buffer() string

Buffer returns the accumulated HTML output

func (*Context) HTML ¶

func (c *Context) HTML(msg string)

HTML adds raw HTML to buffer (no escaping)

func (*Context) Markdown ¶

func (c *Context) Markdown(msg string)

Markdown converts markdown to HTML and adds to buffer

func (*Context) Print ¶

func (c *Context) Print(msg string, options ...PrintOption)

Print adds text to the buffer as HTML paragraphs

func (*Context) Printf ¶

func (c *Context) Printf(format string, args ...interface{})

Printf is a convenience function for formatted printing

func (*Context) Reset ¶

func (c *Context) Reset()

Reset clears the buffer

func (*Context) Table ¶

func (c *Context) Table(data [][]string, options ...TableOption)

Table generates an HTML table

type Controller ¶ added in v0.6.0

type Controller struct {
	Name string // Name of the controller
	// contains filtered or unexported fields
}

Controller manages template rendering and buffer content for lofigui apps.

The Controller provides:

  • Template rendering with state
  • Access to the output buffer
  • Customizable template directories and paths

NOTE: Action state management (polling, refresh) is now handled at the App level to implement the singleton active model concept. Use App methods for action control.

Example usage:

// Basic usage with defaults
ctrl, err := lofigui.NewController(lofigui.ControllerConfig{
    TemplatePath: "../templates/hello.html",
})

// With custom settings
ctrl, err := lofigui.NewController(lofigui.ControllerConfig{
    TemplatePath: "../templates/hello.html",
    Name:         "My Custom Controller",
})

func NewController ¶ added in v0.6.0

func NewController(config ControllerConfig) (*Controller, error)

NewController creates a new Controller with the given configuration.

Either TemplatePath or TemplateString must be provided. If both are set, TemplateString takes precedence.

Returns an error if the template cannot be loaded or parsed.

Example:

// From file:
ctrl, err := lofigui.NewController(lofigui.ControllerConfig{
    TemplatePath: "../templates/hello.html",
})

// From embedded string:
//go:embed templates/hello.html
var helloTemplate string
ctrl, err := lofigui.NewController(lofigui.ControllerConfig{
    TemplateString: helloTemplate,
})

func NewControllerFromDir ¶ added in v0.6.0

func NewControllerFromDir(templateDir, templateName string) (*Controller, error)

NewControllerFromDir creates a new Controller by loading a template from a directory.

This is a convenience function that constructs the full template path.

Example:

ctrl, err := lofigui.NewControllerFromDir("../templates", "hello.html")

func NewControllerFromString ¶ added in v0.13.3

func NewControllerFromString(templateString string) (*Controller, error)

NewControllerFromString creates a new Controller from a template string.

This is a convenience function for embedded templates.

Example:

//go:embed templates/hello.html
var helloTemplate string
ctrl, err := lofigui.NewControllerFromString(helloTemplate)

func NewControllerWithLayout ¶ added in v0.16.1

func NewControllerWithLayout(layout string, name string) (*Controller, error)

NewControllerWithLayout creates a Controller from a built-in layout template.

Example:

ctrl, err := lofigui.NewControllerWithLayout(lofigui.LayoutNavbar, "My App")

func (*Controller) GetTemplate ¶ added in v0.6.0

func (ctrl *Controller) GetTemplate() *pongo2.Template

GetTemplate returns the underlying pongo2 template. This allows advanced users to work directly with the template if needed.

func (*Controller) HandleDisplay ¶ added in v0.6.0

func (ctrl *Controller) HandleDisplay(w http.ResponseWriter, r *http.Request, extraContext pongo2.Context)

HandleDisplay renders the template with the provided context.

NOTE: This method now only handles template rendering. For complete state management including polling status, use app.HandleDisplay() or app.StateDict().

This function:

  1. Generates basic state dict with buffer content
  2. Merges extra context if provided
  3. Renders the template

Example:

http.HandleFunc("/display", func(w http.ResponseWriter, r *http.Request) {
    ctrl.HandleDisplay(w, r, nil)
})

// With extra context
http.HandleFunc("/display", func(w http.ResponseWriter, r *http.Request) {
    extra := pongo2.Context{"title": "My Page"}
    ctrl.HandleDisplay(w, r, extra)
})

func (*Controller) ReloadTemplate ¶ added in v0.6.0

func (ctrl *Controller) ReloadTemplate(templatePath string) error

ReloadTemplate reloads the template from the original path. This is useful during development when templates change.

func (*Controller) RenderTemplate ¶ added in v0.6.0

func (ctrl *Controller) RenderTemplate(w http.ResponseWriter, context pongo2.Context) error

RenderTemplate renders the controller's template with custom context. This is useful for one-off custom rendering.

func (*Controller) ServeHTTP ¶ added in v0.6.0

func (ctrl *Controller) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP allows Controller to be used as an http.Handler. It serves the display page by default.

func (*Controller) StateDict ¶ added in v0.6.0

func (ctrl *Controller) StateDict(r *http.Request) pongo2.Context

StateDict generates a template context dictionary with controller state.

NOTE: This method now only provides basic controller state (request, buffer). Polling state and action management are now handled at the App level. Use app.StateDict() for complete state including polling status.

Returns a pongo2.Context containing:

  • request: The HTTP request object
  • results: Buffer content from Print/Markdown calls

You can merge additional context by using pongo2.Context.Update().

type ControllerConfig ¶ added in v0.6.0

type ControllerConfig struct {
	// Name is the display name for the controller.
	// Default: "Lofigui Controller"
	Name string

	// TemplatePath is the path to the template file.
	// Can be absolute or relative. Examples:
	//   - "../templates/hello.html"
	//   - "/opt/myapp/templates/custom.html"
	//   - "templates/page.html"
	// Either TemplatePath or TemplateString must be provided.
	TemplatePath string

	// TemplateString is the template content as a string.
	// Use this for embedded templates (e.g. via Go's embed package).
	// Either TemplatePath or TemplateString must be provided.
	TemplateString string

	// Context is an optional custom Context for buffer management.
	// If nil, uses the default global context.
	Context *Context
}

ControllerConfig holds configuration for creating a Controller.

type PrintOption ¶

type PrintOption func(*printOptions)

PrintOption is a functional option for Print

func WithEnd ¶

func WithEnd(end string) PrintOption

WithEnd sets the end character (use "" for inline, "\n" for paragraph)

func WithEscape ¶

func WithEscape(escape bool) PrintOption

WithEscape controls HTML escaping (default true)

type TableOption ¶

type TableOption func(*tableOptions)

TableOption is a functional option for Table

func WithHeader ¶

func WithHeader(header []string) TableOption

WithHeader sets the table header

func WithTableEscape ¶

func WithTableEscape(escape bool) TableOption

WithTableEscape controls HTML escaping for table cells

Directories ¶

Path Synopsis
cmd
serve command
Command serve is a static file server with correct WASM MIME type.
Command serve is a static file server with correct WASM MIME type.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL