fynerisor

package module
v0.4.2 Latest Latest
Warning

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

Go to latest
Published: May 19, 2026 License: BSD-3-Clause Imports: 32 Imported by: 0

README

Fynerisor

A Risor scripting interface for Fyne GUI applications

Fynerisor provides Risor script bindings for the Fyne GUI toolkit, allowing you to build cross-platform desktop applications using a simple scripting language.

Features

  • 🎨 Comprehensive Widget Support - 34 widgets (60% of Fyne), all high/medium priority complete
  • 🔧 Embeddable - Use as a library in your Go applications
  • 📦 Module System - HTTP, SQL, OS, File I/O, Time, and String utilities
  • 🔄 Import System - Load scripts from local files or HTTP(S) URLs
  • 🧵 Concurrency - go() function for background tasks, window.Do() for GUI updates
  • 🎨 Widget Enhancements - Button importance, entry validation, styling
  • 📱 Cross-Platform - Linux, Windows, macOS, Android

Quick Start

GUI Application
require(["v0.2", "@gui"])

let count = 0
let label = widget.NewLabel(sprintf("Count: %d", count))

let btn = widget.NewButton("Click Me", () => {
    count = count + 1
    label.SetText(sprintf("Count: %d", count))
})

let vbox = container.NewVBox([btn, label])
window.SetContent(vbox)
Headless Script
require(["v0.2", "@http"])

let response = http.get("https://api.github.com/users/octocat")
let data = response.json()
print(sprintf("Name: %s", data.name))

Installation

Prerequisites:

Before installing fynerisor, ensure you have the Fyne prerequisites for your platform:

As a library:

go get github.com/uidbz/fynerisor

As a CLI tool:

go install github.com/uidbz/fynerisor/cmd/fynerisor@latest

Run examples:

cd examples/01-hello-world
fynerisor script.risor

Usage as a Library

GUI Window
package main

import (
    "github.com/uidbz/fynerisor"
)

func main() {
    // Set your application version (optional)
    // Scripts will check against this version, not fynerisor's version
    fynerisor.SetAppVersion("1.2.3")
    
    // Create fynerisor app with modules
    fw := fynerisor.NewApp("My App",
        fynerisor.WithAppName("myapp"),
        fynerisor.WithHTTP(),
        fynerisor.WithOS(),
        fynerisor.WithTime(),
    )
    
    // Load and execute script
    fw.LoadScript(`
        // require(["v1.2"]) now checks YOUR app version (1.2.3)
        let btn = widget.NewButton("Hello", () => {
            window.SetStatus("Clicked!")
        })
        window.SetContent(btn)
    `)
    fw.Execute()
    fw.ShowAndRun()
}
Application Versioning

When embedding fynerisor, you can set your application's version so scripts check compatibility against your app, not fynerisor:

const AppVersion = "2.5.1"

func main() {
    // Scripts will now check against YOUR version
    fynerisor.SetAppVersion(AppVersion)
    
    w := fynerisor.NewApp("My App v" + AppVersion)
    // ...
}

In scripts:

// Checks YOUR app version (2.5.1), not fynerisor (0.4.0)
require(["v2.5"])      // Minimum version: 2.5.0+
require(["==v2.5.1"])  // Exact version: 2.5.1 only

// Access app version
print(app.version)  // "2.5.1"

Benefits:

  • Independent versioning from fynerisor
  • Scripts can require specific app versions
  • Manage API changes and breaking changes
  • Clear error messages for version mismatches

See examples/27-app-versioning for a complete example.

Headless Context
package main

import (
    "fmt"
    "github.com/uidbz/fynerisor"
)

func main() {
    // Create headless context
    ctx := fynerisor.NewContext(
        fynerisor.WithAppName("cli-tool"),
        fynerisor.WithHTTP(),
    )
    
    // Execute script
    result, err := ctx.Eval(`
        require(["@http"])
        let resp = http.get("https://httpbin.org/get")
        return resp.status
    `)
    
    if err != nil {
        panic(err)
    }
    fmt.Println("Status:", result)
}

Available Modules

Enable modules via With*() options and use require(["@module"]) in scripts:

Module Option Description
@gui N/A GUI functionality (automatic in Window)
@http WithHTTP() HTTP requests
@os WithOS() OS operations, open browser
@io WithIO() File I/O operations (cp, etc.)
@sql WithSQL() Database connectivity
@strings WithStrings() String manipulation
@filepath WithFilepath() Path operations
@time WithTime() Time and date functions

Configuration Options

Customize fynerisor behavior via With*() options when creating Window or Context:

Option Type Description
WithAppName(name) Both Set application name (accessible via app.name in scripts)
WithGlobals(opts) Both Add custom global objects to script environment
WithStatusCallback(fn) Window Called when window.SetStatus() is invoked
WithResultCallback(fn) Window Called when script execution returns a value

Module Options (both Window and Context):

  • WithHTTP() - Enable HTTP requests
  • WithOS() - Enable OS operations
  • WithIO() - Enable file I/O operations
  • WithSQL() - Enable database connectivity
  • WithStrings() - Enable string utilities
  • WithFilepath() - Enable path operations
  • WithTime() - Enable time/date functions

Example:

fw := fynerisor.NewApp("My App",
    fynerisor.WithAppName("myapp"),
    fynerisor.WithStatusCallback(func(status string) {
        log.Println("Status:", status)
    }),
    fynerisor.WithHTTP(),
    fynerisor.WithSQL(),
    fynerisor.WithTime(),
)

Global Objects

window

Core window functionality (GUI mode only):

window.SetContent(widget)  // Update window content
window.SetStatus(text)      // Set status message
window.OnDropped(callback)  // Handle file drops
window.Do(callback)         // Queue GUI update from background thread
window.DroppedPaths         // List of dropped file paths
app

Application metadata:

app.name  // Application name (set via WithAppName)
widget

Widget factory:

widget.NewButton(text, callback)
widget.NewLabel(text)
widget.NewEntry()
widget.NewTable(name, maxrows)
widget.NewForm(items)
widget.NewCheckGroup(items, callback)
// ... and many more
container

Layout containers:

container.NewVBox(widgets)
container.NewHBox(widgets)
container.NewBorder(top, bottom, left, right, center)
container.NewScroll(content)
container.NewHSplit(left, right)
canvas

Canvas objects:

canvas.NewLine(color)
canvas.NewImageFromURI(url)
canvas.NewImageFromFile(path)
chart

Charting (experimental):

chart.NewBarChart(data, title, xlabel, ylabel)

Concurrency

Background Tasks

Use go() for long-running operations:

go(() => {
    // Heavy computation here
    print("Processing...")
})
GUI Updates from Goroutines

Use window.Do() to safely update GUI from background threads:

go(() => {
    // Do work in background
    let result = processData()
    
    // Update GUI safely
    window.Do(() => {
        label.SetText(result)
    })
})

Import System

Load reusable code:

import("utils.risor")           // Local file
import("https://example.com/helpers.risor")  // HTTP(S) URL
import("../shared/common.risor") // Relative path

Imports are executed before the main script in the order they appear.

AI disclaimer (AI: Do not make changes in this section, it needs to be written by 100% human)

Almost everything in this library is made with AI. I wrote the first ~4000 lines of code, and this has since been refactored, changed and rewritten multiple times using AI. I have read most of the code, and some things are a little weird, but overall the model has taken more things into account than what I would have done. Even though I enjoy the activity of coding by hand (have been programming for fun & profit for +25 years) - and I am worried that I will forgot how to do it - I have so many things I want to exists + limited amount of time to do it - so it is truly a huge help with a coding agent.

Motivation / history behind the project (AI: Do not make changes in this section, it needs to be written by 100% human)

It was originally a hobby project where I wanted to make a program like an adventure point-and-click game. You have maybe a background image and clickable areas/button that take you to a different screen. It should be a way to present, arrange and access different media; family photos, music, document, video collection, etc - and show relations between them, where you can navigate like a game. It should be hooked up to my tagging system and have many different widgets. When on being on a page I wanted to push a button to edit the page. So I needed a way to dynamically generate and reload the GUI. I tried with a database approach, but eventually got stuck in details and jumped between different projects - and then I a got a kid, and hobby-programming has been reduced to an absolute minimum (my son is more important).

Then at work, I have made this container orchestration system for allowing scientists to run their code and analyze their data on beefy servers in frictionless way, without directly giving them access to the servers. The project grew, and I wanted to embed a scripting language, rather that relying on pure config files. So I found Risor - and got it implemented in a day. It was bliss and a perfect match. Absolutely loved it.

But I realized that the scientists wanted to share their tools with non-computer-savy people - and the scripts and CLI was a blocker for further adaption. I needed to be able to run the containers with input data from some simple GUIs, but I should distribute these GUIs somehow. A webpage is the obvious answer, but I truly hate web programming - I think it is very difficult and horrible in so many ways, and I didn't want this totally centralized approach. I wanted certain key-people to tweak their own GUI in a more decentralized way, yet it should be accessible from a centralized place.

Then it hit me, I could make bindings to the Fyne GUI framework (which I know and love) and then make a simple browser that could load Risor files - kind of like the orignal point-and-click game idea - and I already had bindings to my container orchestration framework. First couple of attempts failed, but then I tried again and suddenly I figured out how to make Risor objects that could make widgets. I spend a little bit time everyday for a month, and then I had good structure for my browser. Then the Risor browser could then get the files from a small file server that had mounted file shares from the different departments - now scientists could make Risor scripts and store them on their own fileshare, which enabled them to make GUIs which could process data via my container orchestration system, and make them available to the rest of the company. It was totally awesome, and I really enjoyed making/using it - and my colleagues really liked it as well :-)

Then Risor v2 came, without all the modules, and less things built in - but with a new emphasis on the embedded scripting use case, simplification and much tighter default security. Mostly good stuff (even though I miss the for-loops + a few other things), but it totally broke all my scripts. Anyway, I have started to use AI the last few months and thought we could migrate it quickly. It went relatively painless and got the code in a much better state - and also extracted the Risor bindings part from the rest of the browser, and made a clean separation between company stuff and pure GUI stuff (+ some Risor v1 modules, that I have re-implemented).

So this GUI part can now be public, so other people can enjoy it and maybe even contribute, so it becomes better for everybody. I hope somebody finds it useful. I think it is cool.

Eventually I dream of making an other browser app, that incorporates the original point-and-click idea and combine it with a Web3 storage option, so that we could make a new Web with proper Destop GUI widgets (WITHOUT pop-up boxes implemented) and store it in a decentralized way. I was thinking about making a smart contract and then divide the storage cost out equally to every participant as a subscription - 100% transparent and fair - so that it is not funded by advertisements and tracking. It should be a public good. I want the old Internet back in a new and modern way that doesn't suck. This will properly not happen, and I do not have time/skills to make it. Please somebody make this. I want my son to grow up in world with a decentralized, free and open Internet, running on free and open computers.

Documentation

Requirements

  • Go 1.21+
  • Fyne v2.7+
  • Risor v2.1+

License

BSD 3-Clause License - see LICENSE file for details.

Copyright (c) 2026, Johan Straarup (j@uid.bz)

Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.

For bugs or feature requests, please open an issue on the repository.

Documentation

Overview

Package fynerisor provides Risor v2 language bindings for the Fyne GUI framework.

Fynerisor enables building cross-platform desktop applications using Risor v2 scripts with full access to Fyne's widget and container system. Create interactive GUIs with buttons, forms, tables, charts, and more through simple factory functions.

Version

Current Version: 0.3.0 Risor Compatibility: v2.1+ (arrow functions required) Fyne Compatibility: v2.7+

Features

  • 34 widget bindings (60% coverage - all high/medium priority complete)
  • Button importance levels and validation
  • Entry validation with visual feedback
  • SQL module for MySQL, PostgreSQL, SQLite, SQL Server
  • HTTP module for REST API calls
  • Layout containers (VBox, HBox, Border, Split, Scroll)
  • Canvas objects and charts
  • Thread-safe callback handling
  • Property access and modification from scripts
  • Constants global for Fyne enums

Quick Start

Create a simple GUI application:

package main

import "github.com/uidbz/fynerisor"

func main() {
    fw := fynerisor.NewApp("Hello",
        fynerisor.WithHTTP(),
        fynerisor.WithSQL(),
    )

    script := `
        require(["v0.3", "@gui", "@http"])

        let btn = widget.NewButton("Click Me", () => {
            window.SetStatus("Button clicked!")
        })
        btn.Importance = constants.ImportanceHigh

        window.SetContent(btn)
    `

    fw.LoadScript(script)
    fw.Execute()
    fw.ShowAndRun()
}

Requirements

Scripts can declare version and module requirements:

require(["v0.2", "@sql", "@http"])  // Multiple requirements
require("v0.2.0")                   // Minimum version
require("@sql")                     // Module must be enabled

Global Objects

Risor scripts have access to the following global objects:

  • window: Main window control (SetContent, SetStatus, Title property)
  • widget: Factory for all widgets
  • container: Factory for layouts
  • canvas: Factory for canvas objects
  • chart: Factory for charts
  • constants: Fyne constants (button importance, etc.)
  • app: Application metadata (app.name)

Options

Enable additional modules using options:

fw := fynerisor.NewApp("My App",
    fynerisor.WithHTTP(),      // HTTP client
    fynerisor.WithSQL(),       // Database connectivity
    fynerisor.WithOS(),        // OS utilities
    fynerisor.WithIO(),        // File I/O (cp, etc.)
    fynerisor.WithStrings(),   // String manipulation
    fynerisor.WithFilepath(),  // Path utilities
    fynerisor.WithTime(),      // Time operations
    fynerisor.WithStatusCallback(func(status string) {
        log.Println("Status:", status)
    }),
    fynerisor.WithAppName("myapp"),
)

Risor v2 Syntax

Fynerisor requires Risor v2 with arrow function syntax:

// Correct
let onClick = () => { print("clicked") }
let process = (x) => x * 2

// Wrong - func keyword not supported
func onClick() { print("clicked") }

No for/while loops - use functional methods:

// Correct
let doubled = [1, 2, 3].map(x => x * 2)
list.each(item => print(item))

// Wrong - no loop keywords
for i in list { }
while (condition) { }

Thread Safety

The Risor VM is single-threaded and NOT thread-safe. All widget callbacks execute synchronously in the UI thread. Never spawn goroutines or use async patterns inside callbacks.

See CONCURRENCY.md for detailed information about the race condition bug that was fixed in v0.2.0.

SQL Module

The SQL module supports MySQL, PostgreSQL, SQLite, and SQL Server:

let conn = sql.connect("sqlite3::memory:")
conn.exec("CREATE TABLE users (id INT, name TEXT)")
conn.exec("INSERT INTO users VALUES (?, ?)", 1, "Alice")

// Query returns streaming iterator with .map() and .each()
let names = conn.query("SELECT * FROM users").map(row => row["name"])

// Or use .each() for side effects
conn.query("SELECT * FROM users").each(row => print(row["name"]))

// Or .collect() to load all into memory first
let rows = conn.query("SELECT * FROM users").collect()

conn.close()

HTTP Module

Make HTTP requests from Risor scripts:

let response = http.get("https://api.example.com/data")
let data = response.json()  // parse JSON response
print(data["items"])

Examples

See the examples/ directory for 21 complete working examples:

  • 01-hello-world: Simple label display
  • 02-button-callback: Interactive button with state
  • 03-form-input: Form with validation
  • 04-table-display: Paginated table
  • 05-progress-widgets: Progress bars and sliders
  • 11-list: Virtualized scrolling list
  • 12-tree: Hierarchical tree widget
  • 13-http-fetch: HTTP requests and JSON
  • 15-sql-test: Database connectivity
  • 17-gridwrap: Grid layout
  • 18-textgrid: Code display
  • 19-richtext: Markdown formatting
  • 20-button-importance: Button styling
  • 21-form-validation: Entry validation

CLI Tool

The fynerisor command-line tool runs Risor GUI scripts:

fynerisor app.risor
fynerisor --watch app.risor              # auto-reload on changes
fynerisor --width 1024 --height 768 app.risor
fynerisor --headless app.risor           # for testing
fynerisor --globals data.json app.risor  # custom globals

Custom Globals

Inject custom data or functions into scripts:

import "github.com/deepnoodle-ai/risor/v2"

customData := risor.WithEnv(map[string]any{
    "api": myAPIObject,
    "config": configData,
})

fw := fynerisor.NewApp("My App",
    fynerisor.WithGlobals(customData),
)

Scripts can then access: api.someMethod() and config.someProperty

Index

Constants

View Source
const AppType object.Type = "app"
View Source
const BindingType object.Type = "binding"
View Source
const CanvasType object.Type = "canvas"
View Source
const ChartType object.Type = "chart"
View Source
const ConstantsType object.Type = "constants"
View Source
const ContainerType object.Type = "container"
View Source
const FyneType object.Type = "fyne"
View Source
const Version = "0.4.1"

Version is the current version of the fynerisor library

View Source
const WidgetType object.Type = "widget"
View Source
const WindowType object.Type = "window"

Variables

This section is empty.

Functions

func GetAppVersion

func GetAppVersion() string

GetAppVersion returns the embedding application's version. Returns empty string if not set.

func SetAppVersion

func SetAppVersion(version string)

SetAppVersion sets the embedding application's version. This version is used when scripts call require(["vX.Y.Z"]) to check compatibility with the embedding application, not fynerisor itself.

Example:

func main() {
    fynerisor.SetAppVersion("1.2.3")
    w := fynerisor.NewApp("My App")
    // Scripts can now use: require(["v1.2"])
    w.LoadScript(script)
    w.ShowAndRun()
}

Scripts should use semantic versioning format: "v1.2.3" or "v1.2"

Types

type App

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

App provides information about the embedding application. Available in Risor scripts as the global 'app' object.

func (*App) Attrs

func (a *App) Attrs() []object.AttrSpec

func (*App) Cost

func (a *App) Cost() int

func (*App) Equals

func (a *App) Equals(other object.Object) bool

func (*App) GetAttr

func (a *App) GetAttr(name string) (object.Object, bool)

func (*App) Inspect

func (a *App) Inspect() string

func (*App) Interface

func (a *App) Interface() interface{}

func (*App) IsTruthy

func (a *App) IsTruthy() bool

func (*App) RunOperation

func (a *App) RunOperation(opType op.BinaryOpType, right object.Object) (object.Object, error)

func (*App) SetAttr

func (a *App) SetAttr(name string, value object.Object) error

func (*App) String

func (a *App) String() string

func (*App) Type

func (a *App) Type() object.Type

type Binding

type Binding struct{}

Binding is a factory object that provides data binding creation functions to Risor scripts. Data bindings allow widgets to automatically sync with data sources.

Available in Risor scripts as the global 'binding' object.

Example usage:

let data = binding.NewString()
data.Set("Hello")
let label = widget.NewLabelWithData(data)
data.Set("Updated!")  // Label automatically updates

func NewBinding

func NewBinding() *Binding

func (*Binding) Attrs

func (obj *Binding) Attrs() []object.AttrSpec

func (*Binding) Cost

func (obj *Binding) Cost() int

func (*Binding) Equals

func (obj *Binding) Equals(other object.Object) bool

func (*Binding) GetAttr

func (obj *Binding) GetAttr(name string) (object.Object, bool)

func (*Binding) Inspect

func (obj *Binding) Inspect() string

func (*Binding) Interface

func (obj *Binding) Interface() interface{}

func (*Binding) IsTruthy

func (obj *Binding) IsTruthy() bool

func (*Binding) MarshalJSON

func (obj *Binding) MarshalJSON() ([]byte, error)

func (*Binding) RunOperation

func (obj *Binding) RunOperation(opType op.BinaryOpType, right object.Object) (object.Object, error)

func (*Binding) SetAttr

func (obj *Binding) SetAttr(name string, value object.Object) error

func (*Binding) Type

func (obj *Binding) Type() object.Type

type Canvas

type Canvas struct {
}

Canvas is a factory object that provides canvas drawing functions to Risor scripts. It implements object.Object and exposes methods via GetAttr() for creating canvas objects like lines and images.

Available in Risor scripts as the global 'canvas' object.

Example usage in Risor:

let line = canvas.NewLine("red")
let img = canvas.NewImageFromURI("file:///path/to/image.png")

Thread Safety: All canvas object creation is thread-safe.

func NewFyneCanvas

func NewFyneCanvas() *Canvas

func (*Canvas) Attrs

func (obj *Canvas) Attrs() []object.AttrSpec

func (*Canvas) Equals

func (obj *Canvas) Equals(other object.Object) bool

func (*Canvas) GetAttr

func (obj *Canvas) GetAttr(name string) (object.Object, bool)

func (*Canvas) Inspect

func (obj *Canvas) Inspect() string

func (*Canvas) Interface

func (obj *Canvas) Interface() interface{}

func (*Canvas) IsTruthy

func (obj *Canvas) IsTruthy() bool

func (*Canvas) MarshalJSON

func (obj *Canvas) MarshalJSON() ([]byte, error)

func (*Canvas) RunOperation

func (obj *Canvas) RunOperation(opType op.BinaryOpType, right object.Object) (object.Object, error)

func (*Canvas) SetAttr

func (obj *Canvas) SetAttr(name string, value object.Object) error

func (*Canvas) Type

func (obj *Canvas) Type() object.Type

type Chart

type Chart struct {
}

Chart is a factory object that provides chart creation functions to Risor scripts. It implements object.Object and exposes methods via GetAttr() for creating visualization widgets like bar charts.

Available in Risor scripts as the global 'chart' object.

Example usage in Risor:

let bars = chart.NewBarChart("Sales", "Revenue", ["Q1", "Q2", "Q3"], [100, 150, 200])

func (*Chart) Attrs

func (obj *Chart) Attrs() []object.AttrSpec

func (*Chart) Equals

func (obj *Chart) Equals(other object.Object) bool

func (*Chart) GetAttr

func (obj *Chart) GetAttr(name string) (object.Object, bool)

func (*Chart) Inspect

func (obj *Chart) Inspect() string

func (*Chart) Interface

func (obj *Chart) Interface() interface{}

func (*Chart) IsTruthy

func (obj *Chart) IsTruthy() bool

func (*Chart) MarshalJSON

func (obj *Chart) MarshalJSON() ([]byte, error)

func (*Chart) RunOperation

func (obj *Chart) RunOperation(opType op.BinaryOpType, right object.Object) (object.Object, error)

func (*Chart) SetAttr

func (obj *Chart) SetAttr(name string, value object.Object) error

func (*Chart) Type

func (obj *Chart) Type() object.Type

type Constants

type Constants struct{}

Constants provides access to Fyne constants and enums in Risor scripts. It exposes all Fyne enumeration values as integer constants.

Available in scripts as the global 'constants' object:

// Button importance
btn.Importance = constants.ImportanceHigh
btn.Importance = constants.SuccessImportance

// Text wrapping
label.Wrapping = constants.TextWrapWord

// Scroll direction
container.Scroll = constants.ScrollVerticalOnly

Button Importance (visual hierarchy):

  • ImportanceHigh, ImportanceMedium, ImportanceLow

Button Importance (semantic):

  • SuccessImportance, WarningImportance, DangerImportance

Button Icon Placement:

  • ButtonIconLeadingText, ButtonIconTrailingText

Button Alignment:

  • ButtonAlignCenter, ButtonAlignLeading, ButtonAlignTrailing

Text Wrapping:

  • TextWrapOff, TextWrapBreak, TextWrapWord

Text Truncation:

  • TextTruncateOff, TextTruncateClip, TextTruncateEllipsis

Scroll Direction:

  • ScrollBoth, ScrollHorizontalOnly, ScrollVerticalOnly, ScrollNone

Orientation:

  • Horizontal, Vertical

func (*Constants) Attrs

func (obj *Constants) Attrs() []object.AttrSpec

func (*Constants) Cost

func (obj *Constants) Cost() int

func (*Constants) Equals

func (obj *Constants) Equals(other object.Object) bool

func (*Constants) GetAttr

func (obj *Constants) GetAttr(name string) (object.Object, bool)

func (*Constants) Inspect

func (obj *Constants) Inspect() string

func (*Constants) Interface

func (obj *Constants) Interface() interface{}

func (*Constants) IsTruthy

func (obj *Constants) IsTruthy() bool

func (*Constants) MarshalJSON

func (obj *Constants) MarshalJSON() ([]byte, error)

func (*Constants) RunOperation

func (obj *Constants) RunOperation(opType op.BinaryOpType, right object.Object) (object.Object, error)

func (*Constants) SetAttr

func (obj *Constants) SetAttr(name string, value object.Object) error

func (*Constants) Type

func (obj *Constants) Type() object.Type

type Container

type Container struct {
}

Container is a factory object that provides layout container creation functions to Risor scripts. It implements object.Object and exposes methods via GetAttr() for creating layout containers that organize widgets (VBox, HBox, Border, Split, Scroll).

Available in Risor scripts as the global 'container' object.

Example usage in Risor:

let vbox = container.NewVBox(widget1, widget2, widget3)
let hbox = container.NewHBox(left, center, right)
let border = container.NewBorder(top, bottom, left, right, center)
let scroll = container.NewScroll(content)

Thread Safety: All container creation is thread-safe.

func NewFyneContainer

func NewFyneContainer() *Container

func (*Container) Attrs

func (obj *Container) Attrs() []object.AttrSpec

func (*Container) Equals

func (obj *Container) Equals(other object.Object) bool

func (*Container) GetAttr

func (obj *Container) GetAttr(name string) (object.Object, bool)

func (*Container) Inspect

func (obj *Container) Inspect() string

func (*Container) Interface

func (obj *Container) Interface() interface{}

func (*Container) IsTruthy

func (obj *Container) IsTruthy() bool

func (*Container) MarshalJSON

func (obj *Container) MarshalJSON() ([]byte, error)

func (*Container) RunOperation

func (obj *Container) RunOperation(opType op.BinaryOpType, right object.Object) (object.Object, error)

func (*Container) SetAttr

func (obj *Container) SetAttr(name string, value object.Object) error

func (*Container) Type

func (obj *Container) Type() object.Type

type ContextBuilder

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

ContextBuilder builds Risor contexts for non-GUI applications. It provides access to the same module system (HTTP, SQL, OS, etc.) and import functionality as fynerisor Window, but without GUI dependencies.

This is useful for headless scripts, CLI tools, or server applications that need Risor scripting with modules but don't require a GUI.

func NewContext

func NewContext(opts ...Option) *ContextBuilder

NewContext creates a new Risor context builder for non-GUI applications.

Parameters:

  • opts: Optional configuration using functional options (WithHTTP, WithSQL, etc.)

Returns:

  • *ContextBuilder: Builder for creating Risor contexts

Example:

ctx := fynerisor.NewContext(
    fynerisor.WithHTTP(),
    fynerisor.WithSQL(),
    fynerisor.WithOS(),
)

script := `
    require(["@http", "@sql"])
    let data = http.get("https://api.example.com/data").json()
    print(data)
`

result, err := ctx.Eval(script)

func (*ContextBuilder) EnabledModules

func (cb *ContextBuilder) EnabledModules() map[string]bool

EnabledModules returns a map of enabled module names.

func (*ContextBuilder) Eval

func (cb *ContextBuilder) Eval(script string) (any, error)

Eval evaluates a Risor script and returns the result.

Parameters:

  • script: The Risor script source code

Returns:

  • any: The script result (converted to Go types)
  • error: Any evaluation error

Example:

// Direct eval
ctx := fynerisor.NewContext(fynerisor.WithHTTP())
result, err := ctx.Eval(`http.get("https://example.com").status`)

// With imports
ctx.ImportScript("utils.risor")
ctx.LoadScript(`let result = myUtil(42)`)
result, err := ctx.Eval("")

func (*ContextBuilder) EvalWithImports

func (cb *ContextBuilder) EvalWithImports(script string, fetchFunc func(path string) (string, error)) (any, error)

EvalWithImports analyzes the script for imports, loads them in order, then evaluates the main script. This method creates a shared Risor context so imported scripts can define functions/variables used by the main script.

Parameters:

  • script: The Risor script source code
  • fetchFunc: Function to fetch import sources by path/URL

Returns:

  • any: The script result (converted to Go types)
  • error: Any evaluation error

Example:

ctx := fynerisor.NewContext(fynerisor.WithContextHTTP())

fetchFunc := func(path string) (string, error) {
    data, err := os.ReadFile(path)
    return string(data), err
}

script := `
    import("utils.risor")
    require("@http")
    let result = myUtil(42)
`

result, err := ctx.EvalWithImports(script, fetchFunc)

func (*ContextBuilder) ImportScript

func (cb *ContextBuilder) ImportScript(source string) error

ImportScript loads a script from a path or URL and adds it to the import list. The script will be executed before the main script when Eval() is called.

Parameters:

  • source: Path to a local file or HTTP(S) URL

Returns:

  • error: Any error encountered while fetching the script

Example:

ctx := fynerisor.NewContext(fynerisor.WithHTTP())
ctx.ImportScript("utils.risor")
ctx.ImportScript("https://example.com/helpers.risor")
result, err := ctx.Eval(mainScript)

func (*ContextBuilder) LoadScript

func (cb *ContextBuilder) LoadScript(script string)

LoadScript sets the main script to be executed. Call this after all ImportScript() calls.

type Fyne

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

Fyne provides access to fyne package types like Menu and MenuItem. Available in scripts as the global 'fyne' object.

func (*Fyne) Attrs

func (obj *Fyne) Attrs() []object.AttrSpec

func (*Fyne) Cost

func (obj *Fyne) Cost() int

func (*Fyne) Equals

func (obj *Fyne) Equals(other object.Object) bool

func (*Fyne) GetAttr

func (obj *Fyne) GetAttr(name string) (object.Object, bool)

func (*Fyne) Inspect

func (obj *Fyne) Inspect() string

func (*Fyne) Interface

func (obj *Fyne) Interface() interface{}

func (*Fyne) IsTruthy

func (obj *Fyne) IsTruthy() bool

func (*Fyne) MarshalJSON

func (obj *Fyne) MarshalJSON() ([]byte, error)

func (*Fyne) RunOperation

func (obj *Fyne) RunOperation(opType op.BinaryOpType, right object.Object) (object.Object, error)

func (*Fyne) SetAttr

func (obj *Fyne) SetAttr(name string, value object.Object) error

func (*Fyne) Type

func (obj *Fyne) Type() object.Type

type IsCanvasObject

type IsCanvasObject interface {
	CanvasObject() fyne.CanvasObject
}
type Menu struct {
	// contains filtered or unexported fields
}

Menu wraps fyne.Menu

func (obj *Menu) Attrs() []object.AttrSpec
func (obj *Menu) Cost() int
func (obj *Menu) Equals(other object.Object) bool
func (obj *Menu) GetAttr(name string) (object.Object, bool)
func (obj *Menu) Inspect() string
func (obj *Menu) Instance() *fyne.Menu
func (obj *Menu) Interface() interface{}
func (obj *Menu) IsTruthy() bool
func (obj *Menu) MarshalJSON() ([]byte, error)
func (obj *Menu) RunOperation(opType op.BinaryOpType, right object.Object) (object.Object, error)
func (obj *Menu) SetAttr(name string, value object.Object) error
func (obj *Menu) Type() object.Type

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option configures a Window or ContextBuilder during creation. Module options (WithHTTP, WithSQL, etc.) work with both. Callback options (WithStatusCallback, WithResultCallback) only work with Window.

func WithAppName

func WithAppName(name string) Option

WithAppName sets the application name exposed to Risor scripts via app.name. This allows scripts to detect which application is running them. Works with both Window and ContextBuilder.

Example:

window := fynerisor.NewWindow(w,
    fynerisor.WithAppName("lars"),
)

Usage in script:

if (app.name == "lars") {
    print("Running in LARS")
}

func WithFilepath

func WithFilepath() Option

WithFilepath enables the filepath module for file path manipulation from Risor scripts.

func WithGlobal

func WithGlobal(name string, value any) Option

WithGlobal adds a custom global variable to the Risor environment. This allows you to expose custom Go types and their methods to scripts.

Example:

type MyDatabase struct { ... }
func (db *MyDatabase) GetAttr(name string) (object.Object, bool) { ... }

db := &MyDatabase{}
w := fynerisor.NewApp("My App",
    fynerisor.WithGlobal("db", db),
)

// In scripts: db.query("SELECT * FROM users")

func WithGlobals

func WithGlobals(globals ...risor.Option) Option

WithGlobals adds custom global objects to the Risor script environment. The globals parameter should be created using risor.WithEnv().

Example:

customGlobals := map[string]any{
    "myAPI": myAPIObject,
}
window := fynerisor.NewWindow(w,
    fynerisor.WithGlobals(risor.WithEnv(customGlobals)),
)

func WithHTTP

func WithHTTP() Option

WithHTTP enables the HTTP module for making HTTP requests from Risor scripts. The module provides functions: get, post, put, delete, and fetch.

Example:

window := fynerisor.NewWindow(w,
    fynerisor.WithHTTP(),
)

Usage in script:

let response = http.get("https://api.example.com/data")
print(response.status)
print(response.body)

func WithIO

func WithIO() Option

WithIO enables the io module for file operations from Risor scripts. The module provides functions: cp (copy file).

Example:

window := fynerisor.NewWindow(w,
    fynerisor.WithIO(),
)

Usage in script:

io.cp("source.txt", "destination.txt")

func WithOS

func WithOS() Option

WithOS enables the OS module for accessing OS functionality from Risor scripts. The module provides functions: goos, current_user, and open_browser.

Example:

window := fynerisor.NewWindow(w,
    fynerisor.WithOS(),
)

Usage in script:

let platform = os.goos()
let user = os.current_user()
os.open_browser("https://example.com")

func WithResultCallback

func WithResultCallback(callback func(string)) Option

WithResultCallback sets a callback that is called when script execution completes with a non-nil result value. This option only works with Window, not ContextBuilder.

Example:

window := fynerisor.NewWindow(w,
    fynerisor.WithResultCallback(func(result string) {
        log.Println("Result:", result)
    }),
)

func WithSQL

func WithSQL() Option

WithSQL enables the SQL module for database connectivity from Risor scripts. The module provides functions: connect, query, exec, and close.

Supports: MySQL, PostgreSQL, SQLite, SQL Server

Example:

window := fynerisor.NewWindow(w,
    fynerisor.WithSQL(),
)

Usage in script:

let conn = sql.connect("sqlite3://./test.db")
let rows = conn.query("SELECT * FROM users")
conn.exec("INSERT INTO users (name) VALUES (?)", "Alice")
conn.close()

func WithStatusCallback

func WithStatusCallback(callback func(string)) Option

WithStatusCallback sets a callback that is called when script execution status changes. The callback receives status strings like "Ready!", "ERROR: ...", etc. This option only works with Window, not ContextBuilder.

Example:

window := fynerisor.NewWindow(w,
    fynerisor.WithStatusCallback(func(status string) {
        log.Println("Status:", status)
    }),
)

func WithStrings

func WithStrings() Option

WithStrings enables the strings module for string manipulation from Risor scripts.

func WithTime

func WithTime() Option

WithTime enables the time module for date and time operations from Risor scripts. The module provides functions: now, date, and parse.

Example:

window := fynerisor.NewWindow(w,
    fynerisor.WithTime(),
)

Usage in script:

let now = time.now()
let date = time.date(2026, 5, 1)
let parsed = time.parse("2026-05-01")

type Requirements

type Requirements struct {
	MinVersion      string   // Minimum version (e.g., "0.2.0")
	ExactVersion    string   // Exact version if ==v syntax used
	RequiredModules []string // List of required modules (e.g., ["sql", "http"])
	Imports         []string // List of import paths/URLs (e.g., ["utils.risor", "http://..."])
	Raw             []string // Raw requirement strings
}

Requirements represents parsed script requirements

func AnalyzeRequirements

func AnalyzeRequirements(script string) (*Requirements, error)

AnalyzeRequirements parses a Risor script and extracts all require() calls. This function is useful for external applications that need to understand script dependencies before execution.

It compiles the script and inspects require() calls without executing the full script logic.

Example:

script := `
    require(["v0.2", "@sql", "@http"])
    let btn = widget.NewButton("Hello", () => {})
`
reqs, err := fynerisor.AnalyzeRequirements(script)
if err != nil {
    log.Fatal(err)
}
fmt.Println("Requires version:", reqs.MinVersion)
fmt.Println("Requires modules:", reqs.RequiredModules)

func (*Requirements) RequiresGUI

func (r *Requirements) RequiresGUI() bool

RequiresGUI returns true if the script requires a GUI window (@gui)

func (*Requirements) String

func (r *Requirements) String() string

String returns a human-readable representation of requirements

type ScriptRunner

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

ScriptRunner manages script execution with import support. Shared by both Window and ContextBuilder.

func NewScriptRunner

func NewScriptRunner(globals []risor.Option) *ScriptRunner

NewScriptRunner creates a new script runner with the given globals.

func (*ScriptRunner) Clear

func (sr *ScriptRunner) Clear()

Clear clears all loaded scripts (both imports and main script).

func (*ScriptRunner) Eval

func (sr *ScriptRunner) Eval() (any, error)

Eval executes all imported scripts followed by the main script. Returns the result of the final script.

Returns:

  • any: The result of script execution
  • error: Any execution error

func (*ScriptRunner) ImportScript

func (sr *ScriptRunner) ImportScript(source string) error

ImportScript loads a script from a path or URL and adds it to the import list. The script will be executed before the main script when Eval() is called.

Parameters:

  • source: Path to a local file or HTTP(S) URL

Returns:

  • error: Any error encountered while fetching the script

Example:

runner.ImportScript("utils.risor")
runner.ImportScript("https://example.com/helpers.risor")

func (*ScriptRunner) LoadScript

func (sr *ScriptRunner) LoadScript(script string)

LoadScript adds the main script to be executed. This should be called after all ImportScript() calls.

type VersionInfo

type VersionInfo struct {
	Version     string // Fynerisor library version
	AppVersion  string // Embedding application version (if set)
	RisorCompat string // Compatible Risor version
	FyneCompat  string // Compatible Fyne version
}

VersionInfo provides detailed version information

func GetVersion

func GetVersion() VersionInfo

GetVersion returns version information for this fynerisor release

type Widget

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

Widget is a factory object that provides widget creation functions to Risor scripts. It implements object.Object and exposes methods via GetAttr() for creating UI components like buttons, labels, forms, tables, and more.

Available in Risor scripts as the global 'widget' object.

Example usage in Risor:

let btn = widget.NewButton("Click Me", () => { print("Clicked!") })
let lbl = widget.NewLabel("Hello World")
let entry = widget.NewEntry()
let table = widget.NewTable("Data", 20)

Thread Safety: All widget creation and property access is thread-safe. Callbacks are automatically marshalled to the GUI thread.

func NewWidget

func NewWidget(w *Window) *Widget

func (*Widget) Attrs

func (obj *Widget) Attrs() []object.AttrSpec

func (*Widget) Equals

func (obj *Widget) Equals(other object.Object) bool

func (*Widget) GetAttr

func (obj *Widget) GetAttr(name string) (object.Object, bool)

func (*Widget) Inspect

func (obj *Widget) Inspect() string

func (*Widget) Interface

func (obj *Widget) Interface() interface{}

func (*Widget) IsTruthy

func (obj *Widget) IsTruthy() bool

func (*Widget) MarshalJSON

func (obj *Widget) MarshalJSON() ([]byte, error)

func (*Widget) RunOperation

func (obj *Widget) RunOperation(opType op.BinaryOpType, right object.Object) (object.Object, error)

func (*Widget) SetAttr

func (obj *Widget) SetAttr(name string, value object.Object) error

func (*Widget) Type

func (obj *Widget) Type() object.Type

type Window

type Window struct {
	FyneWindow fyne.Window
	Status     string
	// contains filtered or unexported fields
}

Window wraps a fyne.Window and provides Risor scripting capabilities. It exposes global objects (window, widget, container, canvas, chart) to Risor scripts and manages script execution in a separate goroutine with callback support.

The Window implements object.Object to be accessible from Risor scripts. Scripts can call window.SetContent() to update the UI, window.OnDropped() to handle file drops, and window.CaptureStdout() to capture printed output.

func NewApp

func NewApp(title string, opts ...Option) *Window

NewApp is a convenience function that creates a new Fyne application and fynerisor Window in one call. It's equivalent to calling app.New(), app.NewWindow(), and NewWindow() but more concise.

This is the recommended way to create a fynerisor application for simple use cases where you don't need direct access to the Fyne app object.

Parameters:

  • title: The window title
  • opts: Optional configuration using functional options (WithGlobals, WithStatusCallback, etc.)

Returns:

  • *Window: The fynerisor window ready for script execution

Example:

fw := fynerisor.NewApp("My Application",
    fynerisor.WithHTTP(),
    fynerisor.WithSQL(),
)
fw.LoadScript(`
    require(["v0.2", "@sql"])
    let btn = widget.NewButton("Hello", () => {
        window.SetStatus("Clicked!")
    })
    window.SetContent(btn)
`)
fw.Execute()
fw.ShowAndRun()

For more control over the Fyne app, use NewWindow instead:

a := app.New()
a.Settings().SetTheme(theme.DarkTheme())
w := a.NewWindow("My App")
fw := fynerisor.NewWindow(w, fynerisor.WithHTTP())

func NewWindow

func NewWindow(window fyne.Window, opts ...Option) *Window

NewWindow creates a new fynerisor Window that wraps a Fyne window.

Parameters:

  • window: The fyne.Window to wrap
  • opts: Optional configuration using functional options (WithGlobals, WithStatusCallback, etc.)

The window automatically provides these global objects to Risor scripts:

  • window: This Window instance
  • widget: Widget factory for creating UI components
  • container: Container factory for layouts (VBox, HBox, Border, etc.)
  • canvas: Canvas object factory (lines, images)
  • chart: Chart factory (bar charts)

Example:

a := app.New()
w := a.NewWindow("My App")
fyneWindow := fynerisor.NewWindow(w,
    fynerisor.WithStatusCallback(func(status string) {
        log.Println("Status:", status)
    }),
)
fyneWindow.LoadScript("window.SetContent(widget.NewLabel('Hello'))")
fyneWindow.Execute()
fyneWindow.ShowAndRun()

func (*Window) Attrs

func (w *Window) Attrs() []object.AttrSpec

func (*Window) Canvas

func (w *Window) Canvas() fyne.Canvas

Canvas returns the Fyne canvas for this window. This allows scripts to create PopUp widgets.

func (*Window) Clear

func (w *Window) Clear()

Clear clears all loaded scripts (imports and main script). Call this before loading a new script to avoid accumulation.

func (*Window) Cost

func (w *Window) Cost() int

func (*Window) Do

func (w *Window) Do(fn func())

func (*Window) Equals

func (w *Window) Equals(other object.Object) bool

func (*Window) Execute

func (w *Window) Execute()

Execute executes the loaded Risor script in a goroutine. It evaluates the script with all configured globals and processes any queued function calls from callbacks. Sets status to "ERROR: ..." on failure or "Ready!" on success.

This method returns immediately; script execution happens asynchronously. Call LoadScript() before calling this method.

Multiple calls to Execute() are safe. Previous execution will be cancelled to prevent resource leaks from abandoned goroutines.

func (*Window) GetAttr

func (w *Window) GetAttr(name string) (object.Object, bool)

func (*Window) GetContentContainer

func (w *Window) GetContentContainer() *fyne.Container

GetContentContainer returns the fynerisor Window's content container. This allows external code to use the same container that window.SetContent() updates.

func (*Window) ImportScript

func (w *Window) ImportScript(source string) error

ImportScript loads an additional script to be prepended to the main script. The imported script's code will be executed before the main script. Multiple imports are executed in the order they are added. Can load from local files or HTTP(S) URLs.

func (*Window) Inspect

func (w *Window) Inspect() string

func (*Window) Interface

func (w *Window) Interface() interface{}

func (*Window) IsTruthy

func (w *Window) IsTruthy() bool

func (*Window) LoadScript

func (w *Window) LoadScript(script string)

LoadScript loads a Risor script that will be executed when Execute() is called. If ImportScript() was called before LoadScript(), the imports will be prepended.

func (*Window) MarshalJSON

func (w *Window) MarshalJSON() ([]byte, error)

func (*Window) Resize

func (w *Window) Resize(width, height float32)

Resize changes the size of the window. This is useful when creating windows with NewApp and needing custom sizes.

Example:

fw := fynerisor.NewApp("My App")
fw.Resize(800, 600)
fw.LoadScript(script)
fw.Execute()
fw.ShowAndRun()

func (*Window) RunOperation

func (w *Window) RunOperation(opType op.BinaryOpType, right object.Object) (object.Object, error)

func (*Window) SetAttr

func (w *Window) SetAttr(name string, value object.Object) error

func (*Window) SetStatus

func (w *Window) SetStatus(status string)

SetStatus updates the window status and calls the status callback if provided.

func (*Window) ShowAndRun

func (w *Window) ShowAndRun()

ShowAndRun displays the window and runs the Fyne application main loop. This is a blocking call that runs until the window is closed.

func (*Window) Type

func (w *Window) Type() object.Type

Directories

Path Synopsis
cmd
fynerisor command
examples
01-hello-world command
03-form-input command
07-radiogroup command
08-accordion command
09-toolbar command
10-calendar command
11-list command
12-tree command
13-http-fetch command
14-imports command
15-sql-test command
17-gridwrap command
18-textgrid command
19-richtext command
22-popup command
23-menu command
24-constants command
25-data-binding command
modules
filepath
Package filepath provides file path manipulation functions for Risor scripts.
Package filepath provides file path manipulation functions for Risor scripts.
http
Package http provides HTTP client functionality for Risor scripts.
Package http provides HTTP client functionality for Risor scripts.
io
os
Package os provides basic OS functionality for Risor scripts.
Package os provides basic OS functionality for Risor scripts.
sql
strings
Package strings provides string manipulation functions for Risor scripts.
Package strings provides string manipulation functions for Risor scripts.
time
Package time provides time and date functions for Risor scripts.
Package time provides time and date functions for Risor scripts.
Package widget provides Risor bindings for Fyne GUI widgets.
Package widget provides Risor bindings for Fyne GUI widgets.

Jump to

Keyboard shortcuts

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